diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/fs_whitelist.c | 125 |
1 files changed, 65 insertions, 60 deletions
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index 2acde5837..0b860c0d5 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -49,21 +49,25 @@ static void whitelist_error(const char *path) { | |||
49 | exit(1); | 49 | exit(1); |
50 | } | 50 | } |
51 | 51 | ||
52 | static int whitelist_mkpath(const char* path, mode_t mode) { | 52 | static 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 | |||
53 | // work on a copy of the path | 58 | // work on a copy of the path |
54 | char *dup = strdup(path); | 59 | char *dup = strdup(relpath); |
55 | if (!dup) | 60 | if (!dup) |
56 | errExit("strdup"); | 61 | errExit("strdup"); |
57 | 62 | ||
58 | // only create leading directories, don't create the file | 63 | // only create leading directories, don't create the file |
59 | char *p = strrchr(dup, '/'); | 64 | char *p = strrchr(dup, '/'); |
60 | assert(p); | 65 | if (!p) { // nothing to do |
66 | free(dup); | ||
67 | return parentfd; | ||
68 | } | ||
61 | *p = '\0'; | 69 | *p = '\0'; |
62 | 70 | ||
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 | 71 | // traverse the path, return -1 if a symlink is encountered |
68 | int fd = -1; | 72 | int fd = -1; |
69 | int done = 0; | 73 | int done = 0; |
@@ -97,24 +101,35 @@ static int whitelist_mkpath(const char* path, mode_t mode) { | |||
97 | tok = strtok(NULL, "/"); | 101 | tok = strtok(NULL, "/"); |
98 | } | 102 | } |
99 | 103 | ||
100 | if (done) | 104 | if (done) { |
101 | fs_logger2("mkpath", path); | 105 | char *abspath; |
106 | if (asprintf(&abspath, "%s/%s", parentdir, relpath) < 0) | ||
107 | errExit("asprintf"); | ||
108 | fs_logger2("mkpath", abspath); | ||
109 | free(abspath); | ||
110 | } | ||
102 | 111 | ||
103 | free(dup); | 112 | free(dup); |
104 | return fd; | 113 | return fd; |
105 | } | 114 | } |
106 | 115 | ||
107 | static void whitelist_file(int dirfd, const char *relpath, const char *path) { | 116 | static void whitelist_file(const TopDir * const top, const char *path) { |
108 | EUID_ASSERT(); | 117 | EUID_ASSERT(); |
109 | assert(relpath && path); | 118 | assert(top && path); |
119 | |||
120 | // check if path is inside top level directory | ||
121 | size_t top_pathlen = strlen(top->path); | ||
122 | if (strncmp(top->path, path, top_pathlen) != 0 || path[top_pathlen] != '/') | ||
123 | return; | ||
124 | const char *relpath = path + top_pathlen + 1; | ||
110 | 125 | ||
111 | // open mount source, using a file descriptor that refers to the | 126 | // open mount source, using a file descriptor that refers to the |
112 | // top level directory | 127 | // top level directory |
113 | // as the top level directory was opened before mounting the tmpfs | 128 | // as the top level directory was opened before mounting the tmpfs |
114 | // we still have full access to all directory contents | 129 | // we still have full access to all directory contents |
115 | // take care to not follow symbolic links (dirfd was obtained without | 130 | // take care to not follow symbolic links (top->fd was obtained without |
116 | // following a link, too) | 131 | // following a link, too) |
117 | int fd = safer_openat(dirfd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 132 | int fd = safer_openat(top->fd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
118 | if (fd == -1) { | 133 | if (fd == -1) { |
119 | if (arg_debug || arg_debug_whitelists) | 134 | if (arg_debug || arg_debug_whitelists) |
120 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); | 135 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
@@ -130,17 +145,15 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) { | |||
130 | return; | 145 | return; |
131 | } | 146 | } |
132 | 147 | ||
148 | // now modify the tmpfs: | ||
133 | // create mount target as root, except if inside home or run/user/$UID directory | 149 | // 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] != '/') && | 150 | if (strcmp(top->path, cfg.homedir) != 0 && |
135 | (strncmp(path, runuser, runuser_len) != 0 || path[runuser_len] != '/')) | 151 | strcmp(top->path, runuser) != 0) |
136 | EUID_ROOT(); | 152 | EUID_ROOT(); |
137 | 153 | ||
138 | // create path of the mount target | 154 | // create path of the mount target |
139 | int fd2 = whitelist_mkpath(path, 0755); | 155 | int fd2 = whitelist_mkpath(top->path, relpath, 0755); |
140 | if (fd2 == -1) { | 156 | 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) | 157 | if (arg_debug || arg_debug_whitelists) |
145 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); | 158 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
146 | close(fd); | 159 | close(fd); |
@@ -149,13 +162,14 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) { | |||
149 | } | 162 | } |
150 | 163 | ||
151 | // get file name of the mount target | 164 | // get file name of the mount target |
152 | const char *file = gnu_basename(path); | 165 | const char *file = gnu_basename(relpath); |
153 | 166 | ||
154 | // create mount target itself and open it, a symlink is rejected | 167 | // create mount target itself if necessary |
168 | // and open it, a symlink is not allowed | ||
155 | int fd3 = -1; | 169 | int fd3 = -1; |
156 | if (S_ISDIR(s.st_mode)) { | 170 | if (S_ISDIR(s.st_mode)) { |
157 | // directory foo can exist already: | 171 | // directory bar can exist already: |
158 | // firejail --whitelist=~/foo/bar --whitelist=~/foo | 172 | // firejail --whitelist=/foo/bar/baz --whitelist=/foo/bar |
159 | if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { | 173 | if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { |
160 | if (arg_debug || arg_debug_whitelists) { | 174 | if (arg_debug || arg_debug_whitelists) { |
161 | perror("mkdir"); | 175 | perror("mkdir"); |
@@ -169,8 +183,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); | 183 | fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); |
170 | } | 184 | } |
171 | else | 185 | else |
172 | // create an empty file, fails with EEXIST if it is whitelisted already: | 186 | // create an empty file |
173 | // firejail --whitelist=/foo --whitelist=/foo/bar | 187 | // 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); | 188 | fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); |
175 | 189 | ||
176 | if (fd3 == -1) { | 190 | if (fd3 == -1) { |
@@ -212,16 +226,23 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) { | |||
212 | fs_logger2("whitelist", path); | 226 | fs_logger2("whitelist", path); |
213 | } | 227 | } |
214 | 228 | ||
215 | static void whitelist_symlink(const char *link, const char *target) { | 229 | static void whitelist_symlink(const TopDir * const top, const char *link, const char *target) { |
216 | EUID_ASSERT(); | 230 | EUID_ASSERT(); |
217 | assert(link && target); | 231 | assert(top && link && target); |
232 | |||
233 | // confirm link is inside top level directory | ||
234 | // this should never fail | ||
235 | size_t top_pathlen = strlen(top->path); | ||
236 | assert(strncmp(top->path, link, top_pathlen) == 0 && link[top_pathlen] == '/'); | ||
237 | |||
238 | const char *relpath = link + top_pathlen + 1; | ||
218 | 239 | ||
219 | // create files as root, except if inside home or run/user/$UID directory | 240 | // create link as root, except if inside home or run/user/$UID directory |
220 | if ((strncmp(link, cfg.homedir, homedir_len) != 0 || link[homedir_len] != '/') && | 241 | if (strcmp(top->path, cfg.homedir) != 0 && |
221 | (strncmp(link, runuser, runuser_len) != 0 || link[runuser_len] != '/')) | 242 | strcmp(top->path, runuser) != 0) |
222 | EUID_ROOT(); | 243 | EUID_ROOT(); |
223 | 244 | ||
224 | int fd = whitelist_mkpath(link, 0755); | 245 | int fd = whitelist_mkpath(top->path, relpath, 0755); |
225 | if (fd == -1) { | 246 | if (fd == -1) { |
226 | if (arg_debug || arg_debug_whitelists) | 247 | if (arg_debug || arg_debug_whitelists) |
227 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link); | 248 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link); |
@@ -230,7 +251,7 @@ static void whitelist_symlink(const char *link, const char *target) { | |||
230 | } | 251 | } |
231 | 252 | ||
232 | // get file name of symlink | 253 | // get file name of symlink |
233 | const char *file = gnu_basename(link); | 254 | const char *file = gnu_basename(relpath); |
234 | 255 | ||
235 | // create the link | 256 | // create the link |
236 | if (symlinkat(target, fd, file) == -1) { | 257 | if (symlinkat(target, fd, file) == -1) { |
@@ -285,7 +306,7 @@ static void globbing(const char *pattern) { | |||
285 | } | 306 | } |
286 | 307 | ||
287 | // mount tmpfs on all top level directories | 308 | // mount tmpfs on all top level directories |
288 | static void tmpfs_topdirs(const TopDir *topdirs) { | 309 | static void tmpfs_topdirs(const TopDir * const topdirs) { |
289 | int tmpfs_home = 0; | 310 | int tmpfs_home = 0; |
290 | int tmpfs_runuser = 0; | 311 | int tmpfs_runuser = 0; |
291 | 312 | ||
@@ -329,9 +350,7 @@ static void tmpfs_topdirs(const TopDir *topdirs) { | |||
329 | fs_logger2("whitelist", RUN_FIREJAIL_DIR); | 350 | fs_logger2("whitelist", RUN_FIREJAIL_DIR); |
330 | 351 | ||
331 | // restore /run/user/$UID directory | 352 | // restore /run/user/$UID directory |
332 | // get path relative to /run | 353 | whitelist_file(&topdirs[i], runuser); |
333 | const char *rel = runuser + 5; | ||
334 | whitelist_file(topdirs[i].fd, rel, runuser); | ||
335 | } | 354 | } |
336 | else if (strcmp(topdirs[i].path, "/tmp") == 0) { | 355 | else if (strcmp(topdirs[i].path, "/tmp") == 0) { |
337 | // fix pam-tmpdir (#2685) | 356 | // fix pam-tmpdir (#2685) |
@@ -371,12 +390,7 @@ static void tmpfs_topdirs(const TopDir *topdirs) { | |||
371 | // restore user home directory if it is masked by the tmpfs | 390 | // restore user home directory if it is masked by the tmpfs |
372 | // creates path owned by root | 391 | // creates path owned by root |
373 | // does nothing if user home directory doesn't exist | 392 | // does nothing if user home directory doesn't exist |
374 | size_t topdir_len = strlen(topdirs[i].path); | 393 | 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 | } | 394 | } |
381 | 395 | ||
382 | // user home directory | 396 | // user home directory |
@@ -720,9 +734,6 @@ void fs_whitelist(void) { | |||
720 | entry = entry->next; | 734 | entry = entry->next; |
721 | } | 735 | } |
722 | 736 | ||
723 | // release nowhitelist memory | ||
724 | free(nowhitelist); | ||
725 | |||
726 | // mount tmpfs on all top level directories | 737 | // mount tmpfs on all top level directories |
727 | tmpfs_topdirs(topdirs); | 738 | tmpfs_topdirs(topdirs); |
728 | 739 | ||
@@ -732,24 +743,15 @@ void fs_whitelist(void) { | |||
732 | if (entry->wparam) { | 743 | if (entry->wparam) { |
733 | char *file = entry->wparam->file; | 744 | char *file = entry->wparam->file; |
734 | char *link = entry->wparam->link; | 745 | char *link = entry->wparam->link; |
735 | const char *topdir = entry->wparam->top->path; | 746 | const TopDir * const current_top = entry->wparam->top; |
736 | size_t topdir_len = strlen(topdir); | ||
737 | int dirfd = entry->wparam->top->fd; | ||
738 | 747 | ||
739 | // top level directories of link and file can differ | 748 | // top level directories of link and file can differ |
740 | // whitelist the file only if it is in same top level directory | 749 | // will whitelist the file only if it is in same top level directory |
741 | if (strncmp(file, topdir, topdir_len) == 0 && file[topdir_len] == '/') { | 750 | 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 | 751 | ||
750 | // create the link if any | 752 | // create the link if any |
751 | if (link) { | 753 | if (link) { |
752 | whitelist_symlink(link, file); | 754 | whitelist_symlink(current_top, link, file); |
753 | free(link); | 755 | free(link); |
754 | } | 756 | } |
755 | 757 | ||
@@ -762,12 +764,15 @@ void fs_whitelist(void) { | |||
762 | } | 764 | } |
763 | 765 | ||
764 | // release resources | 766 | // release resources |
765 | free(runuser); | ||
766 | |||
767 | size_t i; | 767 | size_t i; |
768 | for (i = 0; i < nowhitelist_c; i++) | ||
769 | free(nowhitelist[i]); | ||
770 | free(nowhitelist); | ||
771 | |||
768 | for (i = 0; i < TOP_MAX && topdirs[i].path; i++) { | 772 | for (i = 0; i < TOP_MAX && topdirs[i].path; i++) { |
769 | free(topdirs[i].path); | 773 | free(topdirs[i].path); |
770 | close(topdirs[i].fd); | 774 | close(topdirs[i].fd); |
771 | } | 775 | } |
772 | free(topdirs); | 776 | free(topdirs); |
777 | free(runuser); | ||
773 | } | 778 | } |