diff options
-rw-r--r-- | src/firejail/fs_whitelist.c | 141 | ||||
-rw-r--r-- | src/firejail/mountinfo.c | 39 |
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 | ||
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 | |||
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 | ||
107 | static void whitelist_file(int dirfd, const char *relpath, const char *path) { | 132 | static 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 | ||
215 | static void whitelist_symlink(const char *link, const char *target) { | 245 | static 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 |
288 | static void tmpfs_topdirs(const TopDir *topdirs) { | 325 | static 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 |
148 | static int get_mount_id_from_handle(int fd) { | 148 | static 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 |
174 | static int get_mount_id_from_fdinfo(int fd) { | 173 | static 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 | |||
200 | errexit: | ||
201 | fprintf(stderr, "Error: cannot read proc file\n"); | ||
202 | exit(1); | ||
203 | } | 204 | } |
204 | 205 | ||
205 | int get_mount_id(int fd) { | 206 | int 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 | ||