aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar netblue30 <netblue30@protonmail.com>2022-03-05 07:31:37 -0500
committerLibravatar GitHub <noreply@github.com>2022-03-05 07:31:37 -0500
commita19fb8f4a36e0c3a15ecb94a1bdf95de9b15d6a5 (patch)
tree7f22b522424e17756461595f49ac77926f24a0e7
parentMerge pull request #4990 from chestnykh/user_profiles (diff)
parentwhitelist: avoid nested whitelist mounts (diff)
downloadfirejail-a19fb8f4a36e0c3a15ecb94a1bdf95de9b15d6a5.tar.gz
firejail-a19fb8f4a36e0c3a15ecb94a1bdf95de9b15d6a5.tar.zst
firejail-a19fb8f4a36e0c3a15ecb94a1bdf95de9b15d6a5.zip
Merge pull request #4985 from smitsohu/whitelist
whitelist restructuring
-rw-r--r--src/firejail/fs_whitelist.c141
-rw-r--r--src/firejail/mountinfo.c39
2 files changed, 101 insertions, 79 deletions
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c
index 2acde5837..3377b2592 100644
--- a/src/firejail/fs_whitelist.c
+++ b/src/firejail/fs_whitelist.c
@@ -49,21 +49,32 @@ static void whitelist_error(const char *path) {
49 exit(1); 49 exit(1);
50} 50}
51 51
52static int whitelist_mkpath(const char* path, mode_t mode) { 52static int whitelist_mkpath(const char *parentdir, const char *relpath, mode_t mode) {
53 // starting from top level directory
54 int parentfd = safer_openat(-1, parentdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
55 if (parentfd < 0)
56 errExit("open");
57
58 // top level directory mount id
59 int mountid = get_mount_id(parentfd);
60 if (mountid < 0) {
61 close(parentfd);
62 return -1;
63 }
64
53 // work on a copy of the path 65 // work on a copy of the path
54 char *dup = strdup(path); 66 char *dup = strdup(relpath);
55 if (!dup) 67 if (!dup)
56 errExit("strdup"); 68 errExit("strdup");
57 69
58 // only create leading directories, don't create the file 70 // only create leading directories, don't create the file
59 char *p = strrchr(dup, '/'); 71 char *p = strrchr(dup, '/');
60 assert(p); 72 if (!p) { // nothing to do
73 free(dup);
74 return parentfd;
75 }
61 *p = '\0'; 76 *p = '\0';
62 77
63 int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC);
64 if (parentfd == -1)
65 errExit("open");
66
67 // traverse the path, return -1 if a symlink is encountered 78 // traverse the path, return -1 if a symlink is encountered
68 int fd = -1; 79 int fd = -1;
69 int done = 0; 80 int done = 0;
@@ -91,30 +102,50 @@ static int whitelist_mkpath(const char* path, mode_t mode) {
91 free(dup); 102 free(dup);
92 return -1; 103 return -1;
93 } 104 }
105 // different mount id indicates earlier whitelist mount
106 if (get_mount_id(fd) != mountid) {
107 if (arg_debug || arg_debug_whitelists)
108 printf("Debug %d: whitelisted already\n", __LINE__);
109 close(parentfd);
110 close(fd);
111 free(dup);
112 return -1;
113 }
94 // move on to next path segment 114 // move on to next path segment
95 close(parentfd); 115 close(parentfd);
96 parentfd = fd; 116 parentfd = fd;
97 tok = strtok(NULL, "/"); 117 tok = strtok(NULL, "/");
98 } 118 }
99 119
100 if (done) 120 if (done) {
101 fs_logger2("mkpath", path); 121 char *abspath;
122 if (asprintf(&abspath, "%s/%s", parentdir, relpath) < 0)
123 errExit("asprintf");
124 fs_logger2("mkpath", abspath);
125 free(abspath);
126 }
102 127
103 free(dup); 128 free(dup);
104 return fd; 129 return fd;
105} 130}
106 131
107static void whitelist_file(int dirfd, const char *relpath, const char *path) { 132static void whitelist_file(const TopDir * const top, const char *path) {
108 EUID_ASSERT(); 133 EUID_ASSERT();
109 assert(relpath && path); 134 assert(top && path);
135
136 // check if path is inside top level directory
137 size_t top_pathlen = strlen(top->path);
138 if (strncmp(top->path, path, top_pathlen) != 0 || path[top_pathlen] != '/')
139 return;
140 const char *relpath = path + top_pathlen + 1;
110 141
111 // open mount source, using a file descriptor that refers to the 142 // open mount source, using a file descriptor that refers to the
112 // top level directory 143 // top level directory
113 // as the top level directory was opened before mounting the tmpfs 144 // as the top level directory was opened before mounting the tmpfs
114 // we still have full access to all directory contents 145 // we still have full access to all directory contents
115 // take care to not follow symbolic links (dirfd was obtained without 146 // take care to not follow symbolic links (top->fd was obtained without
116 // following a link, too) 147 // following a link, too)
117 int fd = safer_openat(dirfd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC); 148 int fd = safer_openat(top->fd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC);
118 if (fd == -1) { 149 if (fd == -1) {
119 if (arg_debug || arg_debug_whitelists) 150 if (arg_debug || arg_debug_whitelists)
120 printf("Debug %d: skip whitelist %s\n", __LINE__, path); 151 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
@@ -130,17 +161,15 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) {
130 return; 161 return;
131 } 162 }
132 163
164 // now modify the tmpfs:
133 // create mount target as root, except if inside home or run/user/$UID directory 165 // create mount target as root, except if inside home or run/user/$UID directory
134 if ((strncmp(path, cfg.homedir, homedir_len) != 0 || path[homedir_len] != '/') && 166 if (strcmp(top->path, cfg.homedir) != 0 &&
135 (strncmp(path, runuser, runuser_len) != 0 || path[runuser_len] != '/')) 167 strcmp(top->path, runuser) != 0)
136 EUID_ROOT(); 168 EUID_ROOT();
137 169
138 // create path of the mount target 170 // create path of the mount target
139 int fd2 = whitelist_mkpath(path, 0755); 171 int fd2 = whitelist_mkpath(top->path, relpath, 0755);
140 if (fd2 == -1) { 172 if (fd2 == -1) {
141 // something went wrong during path creation or a symlink was found;
142 // if there is a symlink somewhere in the path of the mount target,
143 // assume the file is whitelisted already
144 if (arg_debug || arg_debug_whitelists) 173 if (arg_debug || arg_debug_whitelists)
145 printf("Debug %d: skip whitelist %s\n", __LINE__, path); 174 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
146 close(fd); 175 close(fd);
@@ -149,13 +178,14 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) {
149 } 178 }
150 179
151 // get file name of the mount target 180 // get file name of the mount target
152 const char *file = gnu_basename(path); 181 const char *file = gnu_basename(relpath);
153 182
154 // create mount target itself and open it, a symlink is rejected 183 // create mount target itself if necessary
184 // and open it, a symlink is not allowed
155 int fd3 = -1; 185 int fd3 = -1;
156 if (S_ISDIR(s.st_mode)) { 186 if (S_ISDIR(s.st_mode)) {
157 // directory foo can exist already: 187 // directory bar can exist already:
158 // firejail --whitelist=~/foo/bar --whitelist=~/foo 188 // firejail --whitelist=/foo/bar/baz --whitelist=/foo/bar
159 if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { 189 if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) {
160 if (arg_debug || arg_debug_whitelists) { 190 if (arg_debug || arg_debug_whitelists) {
161 perror("mkdir"); 191 perror("mkdir");
@@ -169,8 +199,8 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) {
169 fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 199 fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
170 } 200 }
171 else 201 else
172 // create an empty file, fails with EEXIST if it is whitelisted already: 202 // create an empty file
173 // firejail --whitelist=/foo --whitelist=/foo/bar 203 // fails with EEXIST if it is whitelisted already
174 fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); 204 fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR);
175 205
176 if (fd3 == -1) { 206 if (fd3 == -1) {
@@ -212,16 +242,23 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) {
212 fs_logger2("whitelist", path); 242 fs_logger2("whitelist", path);
213} 243}
214 244
215static void whitelist_symlink(const char *link, const char *target) { 245static void whitelist_symlink(const TopDir * const top, const char *link, const char *target) {
216 EUID_ASSERT(); 246 EUID_ASSERT();
217 assert(link && target); 247 assert(top && link && target);
248
249 // confirm link is inside top level directory
250 // this should never fail
251 size_t top_pathlen = strlen(top->path);
252 assert(strncmp(top->path, link, top_pathlen) == 0 && link[top_pathlen] == '/');
218 253
219 // create files as root, except if inside home or run/user/$UID directory 254 const char *relpath = link + top_pathlen + 1;
220 if ((strncmp(link, cfg.homedir, homedir_len) != 0 || link[homedir_len] != '/') && 255
221 (strncmp(link, runuser, runuser_len) != 0 || link[runuser_len] != '/')) 256 // create link as root, except if inside home or run/user/$UID directory
257 if (strcmp(top->path, cfg.homedir) != 0 &&
258 strcmp(top->path, runuser) != 0)
222 EUID_ROOT(); 259 EUID_ROOT();
223 260
224 int fd = whitelist_mkpath(link, 0755); 261 int fd = whitelist_mkpath(top->path, relpath, 0755);
225 if (fd == -1) { 262 if (fd == -1) {
226 if (arg_debug || arg_debug_whitelists) 263 if (arg_debug || arg_debug_whitelists)
227 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link); 264 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link);
@@ -230,7 +267,7 @@ static void whitelist_symlink(const char *link, const char *target) {
230 } 267 }
231 268
232 // get file name of symlink 269 // get file name of symlink
233 const char *file = gnu_basename(link); 270 const char *file = gnu_basename(relpath);
234 271
235 // create the link 272 // create the link
236 if (symlinkat(target, fd, file) == -1) { 273 if (symlinkat(target, fd, file) == -1) {
@@ -285,7 +322,7 @@ static void globbing(const char *pattern) {
285} 322}
286 323
287// mount tmpfs on all top level directories 324// mount tmpfs on all top level directories
288static void tmpfs_topdirs(const TopDir *topdirs) { 325static void tmpfs_topdirs(const TopDir * const topdirs) {
289 int tmpfs_home = 0; 326 int tmpfs_home = 0;
290 int tmpfs_runuser = 0; 327 int tmpfs_runuser = 0;
291 328
@@ -329,9 +366,7 @@ static void tmpfs_topdirs(const TopDir *topdirs) {
329 fs_logger2("whitelist", RUN_FIREJAIL_DIR); 366 fs_logger2("whitelist", RUN_FIREJAIL_DIR);
330 367
331 // restore /run/user/$UID directory 368 // restore /run/user/$UID directory
332 // get path relative to /run 369 whitelist_file(&topdirs[i], runuser);
333 const char *rel = runuser + 5;
334 whitelist_file(topdirs[i].fd, rel, runuser);
335 } 370 }
336 else if (strcmp(topdirs[i].path, "/tmp") == 0) { 371 else if (strcmp(topdirs[i].path, "/tmp") == 0) {
337 // fix pam-tmpdir (#2685) 372 // fix pam-tmpdir (#2685)
@@ -371,12 +406,7 @@ static void tmpfs_topdirs(const TopDir *topdirs) {
371 // restore user home directory if it is masked by the tmpfs 406 // restore user home directory if it is masked by the tmpfs
372 // creates path owned by root 407 // creates path owned by root
373 // does nothing if user home directory doesn't exist 408 // does nothing if user home directory doesn't exist
374 size_t topdir_len = strlen(topdirs[i].path); 409 whitelist_file(&topdirs[i], cfg.homedir);
375 if (strncmp(topdirs[i].path, cfg.homedir, topdir_len) == 0 && cfg.homedir[topdir_len] == '/') {
376 // get path relative to top level directory
377 const char *rel = cfg.homedir + topdir_len + 1;
378 whitelist_file(topdirs[i].fd, rel, cfg.homedir);
379 }
380 } 410 }
381 411
382 // user home directory 412 // user home directory
@@ -720,9 +750,6 @@ void fs_whitelist(void) {
720 entry = entry->next; 750 entry = entry->next;
721 } 751 }
722 752
723 // release nowhitelist memory
724 free(nowhitelist);
725
726 // mount tmpfs on all top level directories 753 // mount tmpfs on all top level directories
727 tmpfs_topdirs(topdirs); 754 tmpfs_topdirs(topdirs);
728 755
@@ -732,24 +759,15 @@ void fs_whitelist(void) {
732 if (entry->wparam) { 759 if (entry->wparam) {
733 char *file = entry->wparam->file; 760 char *file = entry->wparam->file;
734 char *link = entry->wparam->link; 761 char *link = entry->wparam->link;
735 const char *topdir = entry->wparam->top->path; 762 const TopDir * const current_top = entry->wparam->top;
736 size_t topdir_len = strlen(topdir);
737 int dirfd = entry->wparam->top->fd;
738 763
739 // top level directories of link and file can differ 764 // top level directories of link and file can differ
740 // whitelist the file only if it is in same top level directory 765 // will whitelist the file only if it is in same top level directory
741 if (strncmp(file, topdir, topdir_len) == 0 && file[topdir_len] == '/') { 766 whitelist_file(current_top, file);
742 // get path relative to top level directory
743 const char *rel = file + topdir_len + 1;
744
745 if (arg_debug || arg_debug_whitelists)
746 printf("Debug %d: file: %s; dirfd: %d; topdir: %s; rel: %s\n", __LINE__, file, dirfd, topdir, rel);
747 whitelist_file(dirfd, rel, file);
748 }
749 767
750 // create the link if any 768 // create the link if any
751 if (link) { 769 if (link) {
752 whitelist_symlink(link, file); 770 whitelist_symlink(current_top, link, file);
753 free(link); 771 free(link);
754 } 772 }
755 773
@@ -762,12 +780,15 @@ void fs_whitelist(void) {
762 } 780 }
763 781
764 // release resources 782 // release resources
765 free(runuser);
766
767 size_t i; 783 size_t i;
784 for (i = 0; i < nowhitelist_c; i++)
785 free(nowhitelist[i]);
786 free(nowhitelist);
787
768 for (i = 0; i < TOP_MAX && topdirs[i].path; i++) { 788 for (i = 0; i < TOP_MAX && topdirs[i].path; i++) {
769 free(topdirs[i].path); 789 free(topdirs[i].path);
770 close(topdirs[i].fd); 790 close(topdirs[i].fd);
771 } 791 }
772 free(topdirs); 792 free(topdirs);
793 free(runuser);
773} 794}
diff --git a/src/firejail/mountinfo.c b/src/firejail/mountinfo.c
index 7d30d21d9..56c0bda30 100644
--- a/src/firejail/mountinfo.c
+++ b/src/firejail/mountinfo.c
@@ -146,11 +146,10 @@ MountData *get_last_mount(void) {
146 146
147// Returns mount id, or -1 if fd refers to a procfs or sysfs file 147// Returns mount id, or -1 if fd refers to a procfs or sysfs file
148static int get_mount_id_from_handle(int fd) { 148static int get_mount_id_from_handle(int fd) {
149 EUID_ASSERT();
150
151 char *proc; 149 char *proc;
152 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 150 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
153 errExit("asprintf"); 151 errExit("asprintf");
152
154 struct file_handle *fh = malloc(sizeof *fh); 153 struct file_handle *fh = malloc(sizeof *fh);
155 if (!fh) 154 if (!fh)
156 errExit("malloc"); 155 errExit("malloc");
@@ -172,40 +171,42 @@ static int get_mount_id_from_handle(int fd) {
172 171
173// Returns mount id, or -1 on kernels < 3.15 172// Returns mount id, or -1 on kernels < 3.15
174static int get_mount_id_from_fdinfo(int fd) { 173static int get_mount_id_from_fdinfo(int fd) {
175 EUID_ASSERT();
176 int rv = -1;
177
178 char *proc; 174 char *proc;
179 if (asprintf(&proc, "/proc/self/fdinfo/%d", fd) == -1) 175 if (asprintf(&proc, "/proc/self/fdinfo/%d", fd) == -1)
180 errExit("asprintf"); 176 errExit("asprintf");
181 EUID_ROOT(); 177
178 int called_as_root = 0;
179 if (geteuid() == 0)
180 called_as_root = 1;
181
182 if (called_as_root == 0)
183 EUID_ROOT();
184
182 FILE *fp = fopen(proc, "re"); 185 FILE *fp = fopen(proc, "re");
183 EUID_USER(); 186 if (!fp) {
184 if (!fp) 187 fprintf(stderr, "Error: cannot read proc file\n");
185 goto errexit; 188 exit(1);
189 }
186 190
191 if (called_as_root == 0)
192 EUID_USER();
193
194 int rv = -1;
187 char buf[MAX_BUF]; 195 char buf[MAX_BUF];
188 while (fgets(buf, MAX_BUF, fp)) { 196 while (fgets(buf, MAX_BUF, fp)) {
189 if (strncmp(buf, "mnt_id:", 7) == 0) { 197 if (sscanf(buf, "mnt_id: %d", &rv) == 1)
190 if (sscanf(buf + 7, "%d", &rv) == 1)
191 break; 198 break;
192 goto errexit;
193 }
194 } 199 }
195 200
196 free(proc); 201 free(proc);
197 fclose(fp); 202 fclose(fp);
198 return rv; 203 return rv;
199
200errexit:
201 fprintf(stderr, "Error: cannot read proc file\n");
202 exit(1);
203} 204}
204 205
205int get_mount_id(int fd) { 206int get_mount_id(int fd) {
206 int rv = get_mount_id_from_fdinfo(fd); 207 int rv = get_mount_id_from_handle(fd);
207 if (rv < 0) 208 if (rv < 0)
208 rv = get_mount_id_from_handle(fd); 209 rv = get_mount_id_from_fdinfo(fd);
209 return rv; 210 return rv;
210} 211}
211 212