diff options
author | smitsohu <smitsohu@gmail.com> | 2018-08-02 21:23:50 +0200 |
---|---|---|
committer | smitsohu <smitsohu@gmail.com> | 2018-08-02 21:23:50 +0200 |
commit | a920087f837dcf236acf9bc7a63494c34d72cc93 (patch) | |
tree | 9313a646b93a254fb2fefe43a88c486301749436 /src | |
parent | Merge branch 'master' of https://github.com/netblue30/firejail (diff) | |
download | firejail-a920087f837dcf236acf9bc7a63494c34d72cc93.tar.gz firejail-a920087f837dcf236acf9bc7a63494c34d72cc93.tar.zst firejail-a920087f837dcf236acf9bc7a63494c34d72cc93.zip |
port whitelist path creation to "at" family of functions
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/fs_home.c | 2 | ||||
-rw-r--r-- | src/firejail/fs_whitelist.c | 197 |
2 files changed, 131 insertions, 68 deletions
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c index 3afa3bf0c..09931bd56 100644 --- a/src/firejail/fs_home.c +++ b/src/firejail/fs_home.c | |||
@@ -290,6 +290,8 @@ void fs_private(void) { | |||
290 | if (u == 0 && arg_allusers) // allow --allusers when starting the sandbox as root | 290 | if (u == 0 && arg_allusers) // allow --allusers when starting the sandbox as root |
291 | ; | 291 | ; |
292 | else { | 292 | else { |
293 | if (arg_allusers) | ||
294 | fwarning("--allusers disabled by --private or --whitelist\n"); | ||
293 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | 295 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) |
294 | errExit("mounting home directory"); | 296 | errExit("mounting home directory"); |
295 | fs_logger("tmpfs /home"); | 297 | fs_logger("tmpfs /home"); |
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index 0178e3c5b..df0c21e12 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -51,46 +51,75 @@ char *parse_nowhitelist(int nowhitelist_flag, char *ptr1) { | |||
51 | 51 | ||
52 | static int mkpath(const char* path, mode_t mode) { | 52 | static int mkpath(const char* path, mode_t mode) { |
53 | assert(path && *path); | 53 | assert(path && *path); |
54 | |||
55 | mode |= 0111; | 54 | mode |= 0111; |
56 | 55 | ||
57 | // create directories with uid/gid as root or as current user if inside home directory | 56 | // create directories with uid/gid as root or as current user if inside home directory |
58 | uid_t uid = getuid(); | 57 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) == 0) { |
59 | gid_t gid = getgid(); | 58 | EUID_USER(); |
60 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) != 0) { | ||
61 | uid = 0; | ||
62 | gid = 0; | ||
63 | } | 59 | } |
64 | 60 | ||
65 | // work on a copy of the path | 61 | // work on a copy of the path |
66 | char *file_path = strdup(path); | 62 | char *dup = strdup(path); |
67 | if (!file_path) | 63 | if (!dup) |
68 | errExit("strdup"); | 64 | errExit("strdup"); |
69 | 65 | ||
70 | char* p; | 66 | // don't create the last path element |
67 | char *p = strrchr(dup, '/'); | ||
68 | if (!p) | ||
69 | errExit("strrchr"); | ||
70 | *p = '\0'; | ||
71 | |||
72 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
73 | if (parentfd == -1) | ||
74 | errExit("open"); | ||
75 | |||
76 | // traverse the path, return -1 if a symlink is encountered | ||
71 | int done = 0; | 77 | int done = 0; |
72 | for (p=strchr(file_path+1, '/'); p; p=strchr(p+1, '/')) { | 78 | int fd = -1; |
73 | *p='\0'; | 79 | char *tok = strtok(dup, "/"); |
74 | if (mkdir(file_path, mode)==-1) { | 80 | if (!tok) |
81 | errExit("strtok"); | ||
82 | while (tok) { | ||
83 | // skip all instances of "/./" | ||
84 | if (strcmp(tok, ".") == 0) { | ||
85 | tok = strtok(NULL, "/"); | ||
86 | continue; | ||
87 | } | ||
88 | // create the directory if necessary | ||
89 | if (mkdirat(parentfd, tok, mode) == -1) { | ||
75 | if (errno != EEXIST) { | 90 | if (errno != EEXIST) { |
76 | *p='/'; | 91 | if (arg_debug || arg_debug_whitelists) |
77 | free(file_path); | 92 | perror("mkdir"); |
93 | close(parentfd); | ||
94 | free(dup); | ||
95 | EUID_ROOT(); | ||
78 | return -1; | 96 | return -1; |
79 | } | 97 | } |
80 | } | 98 | } |
81 | else { | 99 | else |
82 | if (set_perms(file_path, uid, gid, mode)) | ||
83 | errExit("set_perms"); | ||
84 | done = 1; | 100 | done = 1; |
101 | // open the directory | ||
102 | fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
103 | if (fd == -1) { | ||
104 | if (arg_debug || arg_debug_whitelists) | ||
105 | perror("open"); | ||
106 | close(parentfd); | ||
107 | free(dup); | ||
108 | EUID_ROOT(); | ||
109 | return -1; | ||
85 | } | 110 | } |
86 | 111 | // move on to next path segment | |
87 | *p='/'; | 112 | close(parentfd); |
113 | parentfd = fd; | ||
114 | tok = strtok(NULL, "/"); | ||
88 | } | 115 | } |
116 | |||
89 | if (done) | 117 | if (done) |
90 | fs_logger2("mkpath", path); | 118 | fs_logger2("mkpath", path); |
91 | 119 | ||
92 | free(file_path); | 120 | free(dup); |
93 | return 0; | 121 | EUID_ROOT(); |
122 | return fd; | ||
94 | } | 123 | } |
95 | 124 | ||
96 | static void whitelist_path(ProfileEntry *entry) { | 125 | static void whitelist_path(ProfileEntry *entry) { |
@@ -184,13 +213,18 @@ static void whitelist_path(ProfileEntry *entry) { | |||
184 | } | 213 | } |
185 | assert(wfile); | 214 | assert(wfile); |
186 | 215 | ||
187 | // check if the file exists, confirm again there is no symlink | 216 | if (arg_debug || arg_debug_whitelists) |
217 | printf("Whitelisting %s\n", path); | ||
218 | |||
219 | // confirm again the mount source exists and there is no symlink | ||
188 | struct stat wfilestat; | 220 | struct stat wfilestat; |
189 | #ifndef TEST_MOUNTINFO | 221 | #ifndef TEST_MOUNTINFO |
190 | EUID_USER(); | 222 | EUID_USER(); |
191 | int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 223 | int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
192 | EUID_ROOT(); | 224 | EUID_ROOT(); |
193 | if (fd == -1) { | 225 | if (fd == -1) { |
226 | if (arg_debug || arg_debug_whitelists) | ||
227 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
194 | free(wfile); | 228 | free(wfile); |
195 | return; | 229 | return; |
196 | } | 230 | } |
@@ -198,57 +232,64 @@ static void whitelist_path(ProfileEntry *entry) { | |||
198 | errExit("fstat"); | 232 | errExit("fstat"); |
199 | close(fd); | 233 | close(fd); |
200 | if (S_ISLNK(wfilestat.st_mode)) { | 234 | if (S_ISLNK(wfilestat.st_mode)) { |
235 | if (arg_debug || arg_debug_whitelists) | ||
236 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
201 | free(wfile); | 237 | free(wfile); |
202 | return; | 238 | return; |
203 | } | 239 | } |
204 | #endif | 240 | #endif |
205 | 241 | ||
206 | if (arg_debug || arg_debug_whitelists) | 242 | // create path of the mount target if necessary |
207 | printf("Whitelisting %s\n", path); | 243 | int fd2 = mkpath(path, 0755); |
244 | if (fd2 == -1) { | ||
245 | // something went wrong during path creation or a symlink was found; | ||
246 | // if there is a symlink somewhere in the path of the mount target, | ||
247 | // assume the file is whitelisted already | ||
248 | if (arg_debug || arg_debug_whitelists) | ||
249 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
250 | free(wfile); | ||
251 | return; | ||
252 | } | ||
208 | 253 | ||
209 | // create the path if necessary | 254 | // get file name of the mount target |
210 | struct stat s; | 255 | const char *file = gnu_basename(path); |
211 | if (stat(path, &s) == -1) { | 256 | |
212 | mkpath(path, 0755); | 257 | // create the mount target if necessary and open it, a symlink is rejected |
213 | if (S_ISDIR(wfilestat.st_mode)) { | 258 | int fd3 = -1; |
214 | int rv = mkdir(path, 0755); | 259 | if (S_ISDIR(wfilestat.st_mode)) { |
215 | if (rv) { | 260 | // directory foo can exist already: |
216 | fprintf(stderr, "Error: cannot create directory %s\n", path); | 261 | // firejail --whitelist=/foo/bar --whitelist=/foo |
217 | exit(1); | 262 | if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { |
218 | } | 263 | if (arg_debug || arg_debug_whitelists) { |
219 | } | 264 | perror("mkdir"); |
220 | else { | 265 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); |
221 | int fd2 = open(path, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); | ||
222 | if (fd2 == -1) { | ||
223 | fprintf(stderr, "Error: cannot create empty file %s\n", path); | ||
224 | exit(1); | ||
225 | } | 266 | } |
226 | close(fd2); | 267 | close(fd2); |
268 | free(wfile); | ||
269 | return; | ||
227 | } | 270 | } |
271 | fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
228 | } | 272 | } |
229 | else { | 273 | else { |
230 | if (!S_ISDIR(s.st_mode)) { | 274 | // create an empty file, fails with EEXIST if it is whitelisted already: |
231 | free(wfile); | 275 | // firejail --whitelist=/foo --whitelist=/foo/bar |
232 | return; // the file is already present | 276 | fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); |
233 | } | ||
234 | } | 277 | } |
235 | 278 | ||
236 | fs_logger2("whitelist", path); | ||
237 | |||
238 | // get a file descriptor for path; if path contains anything other than directories | ||
239 | // or a regular file, assume it is whitelisted already | ||
240 | int fd3 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | ||
241 | if (fd3 == -1) { | 279 | if (fd3 == -1) { |
280 | if (arg_debug || arg_debug_whitelists) { | ||
281 | if (errno != EEXIST) { | ||
282 | perror("open"); | ||
283 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
284 | } | ||
285 | } | ||
286 | close(fd2); | ||
242 | free(wfile); | 287 | free(wfile); |
243 | return; | 288 | return; |
244 | } | 289 | } |
245 | if (fstat(fd3, &s) == -1) | 290 | close(fd2); |
246 | errExit("fstat"); | 291 | |
247 | if (!(S_ISDIR(s.st_mode) || S_ISREG(s.st_mode))) { | 292 | fs_logger2("whitelist", path); |
248 | free(wfile); | ||
249 | close(fd3); | ||
250 | return; | ||
251 | } | ||
252 | 293 | ||
253 | // mount via the link in /proc/self/fd | 294 | // mount via the link in /proc/self/fd |
254 | char *proc; | 295 | char *proc; |
@@ -272,6 +313,7 @@ static void whitelist_path(ProfileEntry *entry) { | |||
272 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 313 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
273 | if (fd4 == -1) | 314 | if (fd4 == -1) |
274 | errExit("safe_fd"); | 315 | errExit("safe_fd"); |
316 | struct stat s; | ||
275 | if (fstat(fd4, &s) == -1) | 317 | if (fstat(fd4, &s) == -1) |
276 | errExit("fstat"); | 318 | errExit("fstat"); |
277 | if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino) | 319 | if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino) |
@@ -501,6 +543,14 @@ void fs_whitelist(void) { | |||
501 | goto errexit; | 543 | goto errexit; |
502 | } | 544 | } |
503 | 545 | ||
546 | // no trailing slash and no trailing dot | ||
547 | char *p = strrchr(new_name, '/'); | ||
548 | if (!p) | ||
549 | errExit("strrchr"); | ||
550 | if (*(p + 1) == '\0') | ||
551 | goto errexit; | ||
552 | if (*(p + 1) == '.' && *(p + 2) == '\0') | ||
553 | goto errexit; | ||
504 | 554 | ||
505 | // extract the absolute path of the file | 555 | // extract the absolute path of the file |
506 | // realpath function will fail with ENOENT if the file is not found | 556 | // realpath function will fail with ENOENT if the file is not found |
@@ -577,7 +627,6 @@ void fs_whitelist(void) { | |||
577 | continue; | 627 | continue; |
578 | } | 628 | } |
579 | 629 | ||
580 | |||
581 | // check for supported directories | 630 | // check for supported directories |
582 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { | 631 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { |
583 | // whitelisting home directory is disabled if --private option is present | 632 | // whitelisting home directory is disabled if --private option is present |
@@ -597,7 +646,7 @@ void fs_whitelist(void) { | |||
597 | 646 | ||
598 | // both path and absolute path are under /home | 647 | // both path and absolute path are under /home |
599 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { | 648 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { |
600 | // avoid naming issues, also entire home dirs are not allowed | 649 | // entire home directory is not allowed |
601 | if (*(fname + strlen(cfg.homedir)) != '/') | 650 | if (*(fname + strlen(cfg.homedir)) != '/') |
602 | goto errexit; | 651 | goto errexit; |
603 | } | 652 | } |
@@ -744,7 +793,7 @@ void fs_whitelist(void) { | |||
744 | entry->link = new_name; | 793 | entry->link = new_name; |
745 | else { | 794 | else { |
746 | free(new_name); | 795 | free(new_name); |
747 | new_name = NULL; | 796 | entry->link = NULL; |
748 | } | 797 | } |
749 | 798 | ||
750 | // change file name in entry->data | 799 | // change file name in entry->data |
@@ -774,7 +823,7 @@ void fs_whitelist(void) { | |||
774 | if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | 823 | if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) |
775 | errExit("mount bind"); | 824 | errExit("mount bind"); |
776 | 825 | ||
777 | // mount a tmpfs and initialize /home/user | 826 | // mount a tmpfs and initialize /home/user, overrides --allusers |
778 | fs_private(); | 827 | fs_private(); |
779 | } | 828 | } |
780 | else | 829 | else |
@@ -986,14 +1035,29 @@ void fs_whitelist(void) { | |||
986 | // if the link is already there, do not bother | 1035 | // if the link is already there, do not bother |
987 | if (lstat(entry->link, &s) != 0) { | 1036 | if (lstat(entry->link, &s) != 0) { |
988 | // create the path if necessary | 1037 | // create the path if necessary |
989 | mkpath(entry->link, 0755); | 1038 | int fd = mkpath(entry->link, 0755); |
990 | 1039 | if (fd == -1) { | |
991 | int rv = symlink(entry->data + 10, entry->link); | 1040 | if (arg_debug || arg_debug_whitelists) |
992 | if (rv) | 1041 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); |
993 | fprintf(stderr, "Warning cannot create symbolic link %s\n", entry->link); | 1042 | free(entry->link); |
1043 | entry = entry->next; | ||
1044 | continue; | ||
1045 | } | ||
1046 | // get file name of symlink | ||
1047 | const char *file = gnu_basename(entry->link); | ||
1048 | // create the link | ||
1049 | int rv = symlinkat(entry->data + 10, fd, file); | ||
1050 | if (rv) { | ||
1051 | if (arg_debug || arg_debug_whitelists) { | ||
1052 | perror("symlink"); | ||
1053 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); | ||
1054 | } | ||
1055 | } | ||
994 | else if (arg_debug || arg_debug_whitelists) | 1056 | else if (arg_debug || arg_debug_whitelists) |
995 | printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10); | 1057 | printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10); |
1058 | close(fd); | ||
996 | } | 1059 | } |
1060 | free(entry->link); | ||
997 | } | 1061 | } |
998 | 1062 | ||
999 | entry = entry->next; | 1063 | entry = entry->next; |
@@ -1076,9 +1140,6 @@ void fs_whitelist(void) { | |||
1076 | fs_logger2("tmpfs", RUN_WHITELIST_MODULE_DIR); | 1140 | fs_logger2("tmpfs", RUN_WHITELIST_MODULE_DIR); |
1077 | } | 1141 | } |
1078 | 1142 | ||
1079 | if (new_name) | ||
1080 | free(new_name); | ||
1081 | |||
1082 | return; | 1143 | return; |
1083 | 1144 | ||
1084 | errexit: | 1145 | errexit: |