diff options
-rw-r--r-- | README | 9 | ||||
-rw-r--r-- | src/fcopy/main.c | 534 | ||||
-rw-r--r-- | src/firejail/fs_bin.c | 2 | ||||
-rw-r--r-- | src/firejail/x11.c | 536 | ||||
-rwxr-xr-x | test/fcopy/cmdline.exp | 8 | ||||
-rwxr-xr-x | test/fs/private-home.exp | 2 |
6 files changed, 583 insertions, 508 deletions
@@ -104,7 +104,14 @@ valoq (https://github.com/valoq) | |||
104 | Zack Weinberg (https://github.com/zackw) | 104 | Zack Weinberg (https://github.com/zackw) |
105 | - removed libconnect | 105 | - removed libconnect |
106 | - fixed memory corruption in noblacklist processing | 106 | - fixed memory corruption in noblacklist processing |
107 | - rework DISPLAY environment parsing, rework masking X11 sockets in /tmp/.X11-unix directory | 107 | - rework DISPLAY environment parsing |
108 | - rework masking X11 sockets in /tmp/.X11-unix directory | ||
109 | - rework xpra and xephyr detection | ||
110 | - rework abstract X11 socket detection | ||
111 | - rework X11 display number assignment | ||
112 | - rework X11 xorg processing | ||
113 | - rework fcopy, --follow-link support in fcopy | ||
114 | - follow link support in --private-bin | ||
108 | Igor Bukanov (https://github.com/ibukanov) | 115 | Igor Bukanov (https://github.com/ibukanov) |
109 | - found/fiixed privilege escalation in --hosts-file option | 116 | - found/fiixed privilege escalation in --hosts-file option |
110 | Cat (https://github.com/ecat3) | 117 | Cat (https://github.com/ecat3) |
diff --git a/src/fcopy/main.c b/src/fcopy/main.c index 43fc8fc99..9f19b6dd8 100644 --- a/src/fcopy/main.c +++ b/src/fcopy/main.c | |||
@@ -21,6 +21,9 @@ | |||
21 | #include "../include/common.h" | 21 | #include "../include/common.h" |
22 | #include <fcntl.h> | 22 | #include <fcntl.h> |
23 | #include <ftw.h> | 23 | #include <ftw.h> |
24 | #include <errno.h> | ||
25 | |||
26 | static int arg_follow_link = 0; | ||
24 | 27 | ||
25 | 28 | ||
26 | #define COPY_LIMIT (500 * 1024 *1024) | 29 | #define COPY_LIMIT (500 * 1024 *1024) |
@@ -34,245 +37,244 @@ static char *inpath = NULL; | |||
34 | 37 | ||
35 | // modified version of the function from util.c | 38 | // modified version of the function from util.c |
36 | static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) { | 39 | static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) { |
37 | assert(srcname); | 40 | assert(srcname); |
38 | assert(destname); | 41 | assert(destname); |
39 | mode &= 07777; | 42 | mode &= 07777; |
40 | 43 | ||
41 | // open source | 44 | // open source |
42 | int src = open(srcname, O_RDONLY); | 45 | int src = open(srcname, O_RDONLY); |
43 | if (src < 0) { | 46 | if (src < 0) { |
44 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname); | 47 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname); |
45 | return; | 48 | return; |
46 | } | 49 | } |
47 | 50 | ||
48 | // open destination | 51 | // open destination |
49 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); | 52 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); |
50 | if (dst < 0) { | 53 | if (dst < 0) { |
51 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); | 54 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); |
52 | close(src); | 55 | close(src); |
53 | return; | 56 | return; |
54 | } | 57 | } |
55 | 58 | ||
56 | // copy | 59 | // copy |
57 | ssize_t len; | 60 | ssize_t len; |
58 | static const int BUFLEN = 1024; | 61 | static const int BUFLEN = 1024; |
59 | unsigned char buf[BUFLEN]; | 62 | unsigned char buf[BUFLEN]; |
60 | while ((len = read(src, buf, BUFLEN)) > 0) { | 63 | while ((len = read(src, buf, BUFLEN)) > 0) { |
61 | int done = 0; | 64 | int done = 0; |
62 | while (done != len) { | 65 | while (done != len) { |
63 | int rv = write(dst, buf + done, len - done); | 66 | int rv = write(dst, buf + done, len - done); |
64 | if (rv == -1) | 67 | if (rv == -1) |
65 | goto errexit; | 68 | goto errexit; |
66 | done += rv; | 69 | done += rv; |
67 | } | 70 | } |
68 | } | 71 | } |
69 | fflush(0); | 72 | fflush(0); |
70 | 73 | ||
71 | if (fchown(dst, uid, gid) == -1) | 74 | if (fchown(dst, uid, gid) == -1) |
72 | goto errexit; | 75 | goto errexit; |
73 | if (fchmod(dst, mode) == -1) | 76 | if (fchmod(dst, mode) == -1) |
74 | goto errexit; | 77 | goto errexit; |
75 | 78 | ||
76 | close(src); | 79 | close(src); |
77 | close(dst); | 80 | close(dst); |
78 | 81 | ||
79 | return; | 82 | return; |
80 | 83 | ||
81 | errexit: | 84 | errexit: |
82 | close(src); | 85 | close(src); |
83 | close(dst); | 86 | close(dst); |
84 | unlink(destname); | 87 | unlink(destname); |
85 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); | 88 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); |
86 | } | 89 | } |
87 | 90 | ||
88 | 91 | ||
89 | 92 | ||
90 | // modified version of the function in firejail/util.c | 93 | // modified version of the function in firejail/util.c |
91 | static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { | 94 | static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { |
92 | assert(fname); | 95 | assert(fname); |
93 | mode &= 07777; | 96 | mode &= 07777; |
94 | 97 | ||
95 | if (mkdir(fname, mode) == -1 || | 98 | if (mkdir(fname, mode) == -1 || |
96 | chmod(fname, mode) == -1) { | 99 | chmod(fname, mode) == -1) { |
97 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); | 100 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); |
98 | errExit("mkdir/chmod"); | 101 | errExit("mkdir/chmod"); |
99 | } | 102 | } |
100 | if (chown(fname, uid, gid)) | 103 | if (chown(fname, uid, gid)) |
101 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); | 104 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); |
102 | } | 105 | } |
103 | 106 | ||
104 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { | 107 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { |
105 | (void) mode; | 108 | (void) mode; |
106 | (void) uid; | 109 | (void) uid; |
107 | (void) gid; | 110 | (void) gid; |
108 | char *rp = realpath(target, NULL); | 111 | char *rp = realpath(target, NULL); |
109 | if (rp) { | 112 | if (rp) { |
110 | if (symlink(rp, linkpath) == -1) | 113 | if (symlink(rp, linkpath) == -1) |
111 | goto errout; | 114 | goto errout; |
112 | free(rp); | 115 | free(rp); |
113 | } | 116 | } |
114 | else | 117 | else |
115 | goto errout; | 118 | goto errout; |
116 | 119 | ||
117 | return; | 120 | return; |
118 | errout: | 121 | errout: |
119 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); | 122 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); |
120 | } | 123 | } |
121 | 124 | ||
122 | static int first = 1; | 125 | static int first = 1; |
123 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { | 126 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { |
124 | (void) st; | 127 | (void) st; |
125 | (void) sftw; | 128 | (void) sftw; |
126 | assert(infname); | 129 | assert(infname); |
127 | assert(*infname != '\0'); | 130 | assert(*infname != '\0'); |
128 | assert(outpath); | 131 | assert(outpath); |
129 | assert(*outpath != '\0'); | 132 | assert(*outpath != '\0'); |
130 | assert(inpath); | 133 | assert(inpath); |
131 | 134 | ||
132 | // check size limit | 135 | // check size limit |
133 | if (size_limit_reached) | 136 | if (size_limit_reached) |
134 | return 0; | 137 | return 0; |
135 | 138 | ||
136 | 139 | ||
137 | char *outfname; | 140 | char *outfname; |
138 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) | 141 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) |
139 | errExit("asprintf"); | 142 | errExit("asprintf"); |
140 | 143 | ||
141 | //printf("outpaht %s\n", outpath); | 144 | // don't copy it if we already have the file |
142 | //printf("inpath %s\n", inpath); | 145 | struct stat s; |
143 | //printf("infname %s\n", infname); | 146 | if (stat(outfname, &s) == 0) { |
144 | //printf("outfname %s\n\n", outfname); | 147 | if (first) |
145 | 148 | first = 0; | |
146 | // don't copy it if we already have the file | 149 | else |
147 | struct stat s; | 150 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); |
148 | if (stat(outfname, &s) == 0) { | 151 | free(outfname); |
149 | if (first) | 152 | return 0; |
150 | first = 0; | 153 | } |
151 | else | 154 | |
152 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); | 155 | // extract mode and ownership |
153 | free(outfname); | 156 | if (stat(infname, &s) != 0) { |
154 | return 0; | 157 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); |
155 | } | 158 | free(outfname); |
156 | 159 | return 0; | |
157 | // extract mode and ownership | 160 | } |
158 | if (stat(infname, &s) != 0) { | 161 | uid_t uid = s.st_uid; |
159 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); | 162 | gid_t gid = s.st_gid; |
160 | free(outfname); | 163 | mode_t mode = s.st_mode; |
161 | return 0; | 164 | |
162 | } | 165 | // recalculate size |
163 | uid_t uid = s.st_uid; | 166 | if ((s.st_size + size_cnt) > COPY_LIMIT) { |
164 | gid_t gid = s.st_gid; | 167 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); |
165 | mode_t mode = s.st_mode; | 168 | size_limit_reached = 1; |
166 | 169 | free(outfname); | |
167 | // recalculate size | 170 | return 0; |
168 | if ((s.st_size + size_cnt) > COPY_LIMIT) { | 171 | } |
169 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); | 172 | |
170 | size_limit_reached = 1; | 173 | file_cnt++; |
171 | free(outfname); | 174 | size_cnt += s.st_size; |
172 | return 0; | 175 | |
173 | } | 176 | if(ftype == FTW_F) { |
174 | 177 | copy_file(infname, outfname, mode, uid, gid); | |
175 | file_cnt++; | 178 | } |
176 | size_cnt += s.st_size; | 179 | else if (ftype == FTW_D) { |
177 | 180 | mkdir_attr(outfname, mode, uid, gid); | |
178 | if(ftype == FTW_F) { | 181 | } |
179 | copy_file(infname, outfname, mode, uid, gid); | 182 | else if (ftype == FTW_SL) { |
180 | } | 183 | copy_link(infname, outfname, mode, uid, gid); |
181 | else if (ftype == FTW_D) { | 184 | } |
182 | mkdir_attr(outfname, mode, uid, gid); | 185 | |
183 | } | 186 | return(0); |
184 | else if (ftype == FTW_SL) { | ||
185 | copy_link(infname, outfname, mode, uid, gid); | ||
186 | } | ||
187 | |||
188 | return(0); | ||
189 | } | 187 | } |
190 | 188 | ||
191 | static char *check(const char *src) { | 189 | static char *check(const char *src) { |
192 | struct stat s; | 190 | struct stat s; |
193 | char *rsrc = realpath(src, NULL); | 191 | char *rsrc = realpath(src, NULL); |
194 | if (!rsrc || stat(rsrc, &s) == -1) | 192 | if (!rsrc || stat(rsrc, &s) == -1) |
195 | goto errexit; | 193 | goto errexit; |
196 | 194 | ||
197 | // check uid | 195 | // check uid |
198 | if (s.st_uid != getuid() || s.st_gid != getgid()) | 196 | if (s.st_uid != getuid() || s.st_gid != getgid()) |
199 | goto errexit; | 197 | goto errexit; |
200 | 198 | ||
201 | // dir, link, regular file | 199 | // dir, link, regular file |
202 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) | 200 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) |
203 | return rsrc; // normal exit from the function | 201 | return rsrc; // normal exit from the function |
204 | 202 | ||
205 | errexit: | 203 | errexit: |
206 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); | 204 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); |
207 | exit(1); | 205 | exit(1); |
208 | } | 206 | } |
209 | 207 | ||
210 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { | 208 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { |
211 | (void) s; | 209 | (void) s; |
212 | char *rsrc = check(src); | 210 | char *rsrc = check(src); |
213 | char *rdest = check(dest); | 211 | char *rdest = check(dest); |
214 | inpath = rsrc; | 212 | inpath = rsrc; |
215 | outpath = rdest; | 213 | outpath = rdest; |
216 | 214 | ||
217 | // walk | 215 | // walk |
218 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { | 216 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { |
219 | fprintf(stderr, "Error: unable to copy file\n"); | 217 | fprintf(stderr, "Error: unable to copy file\n"); |
220 | exit(1); | 218 | exit(1); |
221 | } | 219 | } |
222 | 220 | ||
223 | free(rsrc); | 221 | free(rsrc); |
224 | free(rdest); | 222 | free(rdest); |
225 | } | 223 | } |
226 | 224 | ||
227 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { | 225 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { |
228 | char *rsrc = check(src); | 226 | char *rsrc = check(src); |
229 | char *rdest = check(dest); | 227 | char *rdest = check(dest); |
230 | uid_t uid = s->st_uid; | 228 | uid_t uid = s->st_uid; |
231 | gid_t gid = s->st_gid; | 229 | gid_t gid = s->st_gid; |
232 | mode_t mode = s->st_mode; | 230 | mode_t mode = s->st_mode; |
233 | 231 | ||
234 | // build destination file name | 232 | // build destination file name |
235 | char *name; | 233 | char *name; |
236 | char *ptr = strrchr(rsrc, '/'); | 234 | char *ptr = (arg_follow_link)? strrchr(src, '/'): strrchr(rsrc, '/'); |
237 | ptr++; | 235 | ptr++; |
238 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | 236 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) |
239 | errExit("asprintf"); | 237 | errExit("asprintf"); |
240 | 238 | ||
241 | // copy | 239 | // copy |
242 | copy_file(rsrc, name, mode, uid, gid); | 240 | copy_file(rsrc, name, mode, uid, gid); |
243 | 241 | ||
244 | free(name); | 242 | free(name); |
245 | free(rsrc); | 243 | free(rsrc); |
246 | free(rdest); | 244 | free(rdest); |
247 | } | 245 | } |
248 | 246 | ||
249 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { | 247 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { |
250 | char *rsrc = check(src); // we drop the result and use the original name | 248 | char *rsrc = check(src); // we drop the result and use the original name |
251 | char *rdest = check(dest); | 249 | char *rdest = check(dest); |
252 | uid_t uid = s->st_uid; | 250 | uid_t uid = s->st_uid; |
253 | gid_t gid = s->st_gid; | 251 | gid_t gid = s->st_gid; |
254 | mode_t mode = s->st_mode; | 252 | mode_t mode = s->st_mode; |
255 | 253 | ||
256 | // build destination file name | 254 | // build destination file name |
257 | char *name; | 255 | char *name; |
258 | // char *ptr = strrchr(rsrc, '/'); | 256 | // char *ptr = strrchr(rsrc, '/'); |
259 | char *ptr = strrchr(src, '/'); | 257 | char *ptr = strrchr(src, '/'); |
260 | ptr++; | 258 | ptr++; |
261 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | 259 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) |
262 | errExit("asprintf"); | 260 | errExit("asprintf"); |
263 | 261 | ||
264 | // copy | 262 | // copy |
265 | copy_link(rsrc, name, mode, uid, gid); | 263 | copy_link(rsrc, name, mode, uid, gid); |
266 | 264 | ||
267 | free(name); | 265 | free(name); |
268 | free(rsrc); | 266 | free(rsrc); |
269 | free(rdest); | 267 | free(rdest); |
270 | } | 268 | } |
271 | 269 | ||
272 | static void usage(void) { | 270 | static void usage(void) { |
273 | printf("Usage: fcopy src dest\n"); | 271 | fputs("Usage: fcopy [--follow-link] src dest\n" |
274 | printf("Copy src file in dest directory. If src is a directory, copy all the files in\n"); | 272 | "\n" |
275 | printf("src recoursively. If the destination directory does not exist, it will be created.\n"); | 273 | "Copy SRC to DEST/SRC. SRC may be a file, directory, or symbolic link.\n" |
274 | "If SRC is a directory it is copied recursively. If it is a symlink,\n" | ||
275 | "the link itself is duplicated, unless --follow-link is given,\n" | ||
276 | "in which case the destination of the link is copied.\n" | ||
277 | "DEST must already exist and must be a directory.\n", stderr); | ||
276 | } | 278 | } |
277 | 279 | ||
278 | int main(int argc, char **argv) { | 280 | int main(int argc, char **argv) { |
@@ -285,56 +287,70 @@ for (i = 0; i < argc; i++) | |||
285 | printf("\n"); | 287 | printf("\n"); |
286 | } | 288 | } |
287 | #endif | 289 | #endif |
288 | if (argc != 3) { | 290 | char *src; |
289 | fprintf(stderr, "Error fcopy: files missing\n"); | 291 | char *dest; |
290 | usage(); | 292 | |
291 | exit(1); | 293 | if (argc == 3) { |
292 | } | 294 | src = argv[1]; |
293 | 295 | dest = argv[2]; | |
294 | // check the two files; remove ending / | 296 | arg_follow_link = 0; |
295 | char *src = argv[1]; | 297 | } |
296 | int len = strlen(src); | 298 | else if (argc == 4 && !strcmp(argv[1], "--follow-link")) { |
297 | if (src[len - 1] == '/') | 299 | src = argv[2]; |
298 | src[len - 1] = '\0'; | 300 | dest = argv[3]; |
299 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { | 301 | arg_follow_link = 1; |
300 | fprintf(stderr, "Error fcopy: invalid file name %s\n", src); | 302 | } |
301 | exit(1); | 303 | else { |
302 | } | 304 | fprintf(stderr, "Error: arguments missing\n"); |
303 | 305 | usage(); | |
304 | char *dest = argv[2]; | 306 | exit(1); |
305 | len = strlen(dest); | 307 | } |
306 | if (dest[len - 1] == '/') | 308 | |
307 | dest[len - 1] = '\0'; | 309 | // check the two files; remove ending / |
308 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { | 310 | int len = strlen(src); |
309 | fprintf(stderr, "Error fcopy: invalid file name %s\n", dest); | 311 | if (src[len - 1] == '/') |
310 | exit(1); | 312 | src[len - 1] = '\0'; |
311 | } | 313 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { |
312 | 314 | fprintf(stderr, "Error fcopy: invalid source file name %s\n", src); | |
313 | 315 | exit(1); | |
314 | // the destination should be a directory; | 316 | } |
315 | struct stat s; | 317 | |
316 | if (stat(dest, &s) == -1 || | 318 | len = strlen(dest); |
317 | !S_ISDIR(s.st_mode)) { | 319 | if (dest[len - 1] == '/') |
318 | fprintf(stderr, "Error fcopy: invalid destination directory\n"); | 320 | dest[len - 1] = '\0'; |
319 | exit(1); | 321 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { |
320 | } | 322 | fprintf(stderr, "Error fcopy: invalid dest file name %s\n", dest); |
321 | 323 | exit(1); | |
322 | // copy files | 324 | } |
323 | if (lstat(src, &s) == -1) { | 325 | |
324 | fprintf(stderr, "Error fcopy: cannot find source file\n"); | 326 | |
325 | exit(1); | 327 | // the destination should be a directory; |
326 | } | 328 | struct stat s; |
327 | 329 | if (stat(dest, &s) == -1) { | |
328 | if (S_ISDIR(s.st_mode)) | 330 | fprintf(stderr, "Error fcopy: dest dir %s: %s\n", dest, strerror(errno)); |
329 | duplicate_dir(src, dest, &s); | 331 | exit(1); |
330 | else if (S_ISREG(s.st_mode)) | 332 | } |
331 | duplicate_file(src, dest, &s); | 333 | if (!S_ISDIR(s.st_mode)) { |
332 | else if (S_ISLNK(s.st_mode)) | 334 | fprintf(stderr, "Error fcopy: dest %s is not a directory\n", dest); |
333 | duplicate_link(src, dest, &s); | 335 | exit(1); |
334 | else { | 336 | } |
335 | fprintf(stderr, "Error fcopy: source file unsupported\n"); | 337 | |
336 | exit(1); | 338 | // copy files |
337 | } | 339 | if ((arg_follow_link ? stat : lstat)(src, &s) == -1) { |
338 | 340 | fprintf(stderr, "Error fcopy: src %s: %s\n", src, strerror(errno)); | |
339 | return 0; | 341 | exit(1); |
342 | } | ||
343 | |||
344 | if (S_ISDIR(s.st_mode)) | ||
345 | duplicate_dir(src, dest, &s); | ||
346 | else if (S_ISREG(s.st_mode)) | ||
347 | duplicate_file(src, dest, &s); | ||
348 | else if (S_ISLNK(s.st_mode)) | ||
349 | duplicate_link(src, dest, &s); | ||
350 | else { | ||
351 | fprintf(stderr, "Error fcopy: src %s is an unsupported type of file\n", src); | ||
352 | exit(1); | ||
353 | } | ||
354 | |||
355 | return 0; | ||
340 | } | 356 | } |
diff --git a/src/firejail/fs_bin.c b/src/firejail/fs_bin.c index 547978b47..3473fca4c 100644 --- a/src/firejail/fs_bin.c +++ b/src/firejail/fs_bin.c | |||
@@ -111,7 +111,7 @@ static void duplicate(char *fname) { | |||
111 | errExit("asprintf"); | 111 | errExit("asprintf"); |
112 | 112 | ||
113 | // copy the file | 113 | // copy the file |
114 | sbox_run(SBOX_ROOT| SBOX_SECCOMP, 3, PATH_FCOPY, full_path, RUN_BIN_DIR); | 114 | sbox_run(SBOX_ROOT| SBOX_SECCOMP, 4, PATH_FCOPY, "--follow-link", full_path, RUN_BIN_DIR); |
115 | fs_logger2("clone", fname); | 115 | fs_logger2("clone", fname); |
116 | free(full_path); | 116 | free(full_path); |
117 | } | 117 | } |
diff --git a/src/firejail/x11.c b/src/firejail/x11.c index b551a2d2a..0fa789ff1 100644 --- a/src/firejail/x11.c +++ b/src/firejail/x11.c | |||
@@ -20,6 +20,8 @@ | |||
20 | #include "firejail.h" | 20 | #include "firejail.h" |
21 | #include <sys/types.h> | 21 | #include <sys/types.h> |
22 | #include <sys/stat.h> | 22 | #include <sys/stat.h> |
23 | #include <sys/socket.h> | ||
24 | #include <sys/un.h> | ||
23 | #include <fcntl.h> | 25 | #include <fcntl.h> |
24 | #include <unistd.h> | 26 | #include <unistd.h> |
25 | #include <signal.h> | 27 | #include <signal.h> |
@@ -31,80 +33,6 @@ | |||
31 | #include <limits.h> | 33 | #include <limits.h> |
32 | int mask_x11_abstract_socket = 0; | 34 | int mask_x11_abstract_socket = 0; |
33 | 35 | ||
34 | #ifdef HAVE_X11 | ||
35 | // return 1 if xpra is installed on the system | ||
36 | static int x11_check_xpra(void) { | ||
37 | struct stat s; | ||
38 | |||
39 | // check xpra | ||
40 | if (stat("/usr/bin/xpra", &s) == -1) | ||
41 | return 0; | ||
42 | |||
43 | return 1; | ||
44 | } | ||
45 | |||
46 | // return 1 if xephyr is installed on the system | ||
47 | static int x11_check_xephyr(void) { | ||
48 | struct stat s; | ||
49 | |||
50 | // check xephyr | ||
51 | if (stat("/usr/bin/Xephyr", &s) == -1) | ||
52 | return 0; | ||
53 | |||
54 | return 1; | ||
55 | } | ||
56 | |||
57 | // check for X11 abstract sockets | ||
58 | static int x11_abstract_sockets_present(void) { | ||
59 | char *path; | ||
60 | |||
61 | EUID_ROOT(); // grsecurity fix | ||
62 | FILE *fp = fopen("/proc/net/unix", "r"); | ||
63 | EUID_USER(); | ||
64 | |||
65 | if (!fp) | ||
66 | errExit("fopen"); | ||
67 | |||
68 | while (fscanf(fp, "%*s %*s %*s %*s %*s %*s %*s %ms\n", &path) != EOF) { | ||
69 | if (path && strncmp(path, "@/tmp/.X11-unix/", 16) == 0) { | ||
70 | free(path); | ||
71 | fclose(fp); | ||
72 | return 1; | ||
73 | } | ||
74 | } | ||
75 | |||
76 | free(path); | ||
77 | fclose(fp); | ||
78 | |||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | static int random_display_number(void) { | ||
83 | int i; | ||
84 | int found = 1; | ||
85 | int display; | ||
86 | for (i = 0; i < 100; i++) { | ||
87 | display = rand() % 1024; | ||
88 | if (display < 10) | ||
89 | continue; | ||
90 | char *fname; | ||
91 | if (asprintf(&fname, "/tmp/.X11-unix/X%d", display) == -1) | ||
92 | errExit("asprintf"); | ||
93 | struct stat s; | ||
94 | if (stat(fname, &s) == -1) { | ||
95 | found = 1; | ||
96 | break; | ||
97 | } | ||
98 | } | ||
99 | if (!found) { | ||
100 | fprintf(stderr, "Error: cannot pick up a random X11 display number, exiting...\n"); | ||
101 | exit(1); | ||
102 | } | ||
103 | |||
104 | return display; | ||
105 | } | ||
106 | #endif | ||
107 | |||
108 | 36 | ||
109 | // Parse the DISPLAY environment variable and return a display number. | 37 | // Parse the DISPLAY environment variable and return a display number. |
110 | // Returns -1 if DISPLAY is not set, or is set to anything other than :ddd. | 38 | // Returns -1 if DISPLAY is not set, or is set to anything other than :ddd. |
@@ -145,116 +73,132 @@ int x11_display(void) { | |||
145 | return (int)display; | 73 | return (int)display; |
146 | } | 74 | } |
147 | 75 | ||
148 | void fs_x11(void) { | ||
149 | #ifdef HAVE_X11 | ||
150 | int display = x11_display(); | ||
151 | if (display <= 0) | ||
152 | return; | ||
153 | 76 | ||
154 | char *x11file; | 77 | #ifdef HAVE_X11 |
155 | if (asprintf(&x11file, "/tmp/.X11-unix/X%d", display) == -1) | 78 | // check for X11 abstract sockets |
156 | errExit("asprintf"); | 79 | static int x11_abstract_sockets_present(void) { |
157 | struct stat x11stat; | ||
158 | if (stat(x11file, &x11stat) == -1 || !S_ISSOCK(x11stat.st_mode)) { | ||
159 | free(x11file); | ||
160 | return; | ||
161 | } | ||
162 | |||
163 | if (arg_debug || arg_debug_whitelists) | ||
164 | fprintf(stderr, "Masking all X11 sockets except %s\n", x11file); | ||
165 | |||
166 | // Move the real /tmp/.X11-unix to a scratch location | ||
167 | // so we can still access x11file after we mount a | ||
168 | // tmpfs over /tmp/.X11-unix. | ||
169 | int rv = mkdir(RUN_WHITELIST_X11_DIR, 0700); | ||
170 | if (rv == -1) | ||
171 | errExit("mkdir"); | ||
172 | if (set_perms(RUN_WHITELIST_X11_DIR, 0, 0, 0700)) | ||
173 | errExit("set_perms"); | ||
174 | |||
175 | if (mount("/tmp/.X11-unix", RUN_WHITELIST_X11_DIR, 0, MS_BIND|MS_REC, 0) < 0) | ||
176 | errExit("mount bind"); | ||
177 | |||
178 | // This directory must be mode 1777, or Xlib will barf. | ||
179 | if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs", | ||
180 | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME | MS_REC, | ||
181 | "mode=1777,uid=0,gid=0") < 0) | ||
182 | errExit("mounting tmpfs on /tmp/.X11-unix"); | ||
183 | fs_logger("tmpfs /tmp/.X11-unix"); | ||
184 | 80 | ||
185 | // create an empty file which will have the desired socket bind-mounted over it | 81 | EUID_ROOT(); // grsecurity fix |
186 | int fd = open(x11file, O_RDWR|O_CREAT|O_EXCL, x11stat.st_mode & ~S_IFMT); | 82 | FILE *fp = fopen("/proc/net/unix", "r"); |
187 | if (fd < 0) | 83 | if (!fp) |
188 | errExit(x11file); | 84 | errExit("fopen"); |
189 | if (fchown(fd, x11stat.st_uid, x11stat.st_gid)) | 85 | EUID_USER(); |
190 | errExit("fchown"); | ||
191 | close(fd); | ||
192 | 86 | ||
193 | // do the mount | 87 | char *linebuf = 0; |
194 | char *wx11file; | 88 | size_t bufsz = 0; |
195 | if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1) | 89 | int found = 0; |
196 | errExit("asprintf"); | 90 | errno = 0; |
197 | if (mount(wx11file, x11file, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
198 | errExit("mount bind"); | ||
199 | fs_logger2("whitelist", x11file); | ||
200 | 91 | ||
201 | free(x11file); | 92 | for (;;) { |
202 | free(wx11file); | 93 | if (getline(&linebuf, &bufsz, fp) == -1) { |
94 | if (errno) | ||
95 | errExit("getline"); | ||
96 | break; | ||
97 | } | ||
98 | // The last space-separated field in 'linebuf' is the | ||
99 | // pathname of the socket. Abstract sockets' pathnames | ||
100 | // all begin with '@/', normal ones begin with '/'. | ||
101 | char *p = strrchr(linebuf, ' '); | ||
102 | if (!p) { | ||
103 | fputs("error parsing /proc/net/unix\n", stderr); | ||
104 | exit(1); | ||
105 | } | ||
106 | if (strncmp(p+1, "@/tmp/.X11-unix/", 16) == 0) { | ||
107 | found = 1; | ||
108 | break; | ||
109 | } | ||
110 | } | ||
203 | 111 | ||
204 | // block access to RUN_WHITELIST_X11_DIR | 112 | free(linebuf); |
205 | if (mount(RUN_RO_DIR, RUN_WHITELIST_X11_DIR, 0, MS_BIND, 0) < 0) | 113 | fclose(fp); |
206 | errExit("mount"); | 114 | return found; |
207 | fs_logger2("blacklist", RUN_WHITELIST_X11_DIR); | 115 | } |
208 | 116 | ||
117 | // Choose a random, unallocated display number. This has an inherent | ||
118 | // and unavoidable TOCTOU race, since we cannot create either the | ||
119 | // socket or a lockfile ourselves. | ||
120 | static int random_display_number(void) { | ||
121 | int display; | ||
122 | int found = 0; | ||
123 | int i; | ||
209 | 124 | ||
210 | #if 0 | 125 | struct sockaddr_un sa; |
211 | // keep a copy of real /tmp/.X11-unix directory in WHITELIST_TMP_DIR | 126 | // The -1 here is because we need space to inject a |
212 | int rv = mkdir(RUN_WHITELIST_X11_DIR, 1777); | 127 | // leading nul byte. |
213 | if (rv == -1) | 128 | int sun_pathmax = (int)(sizeof sa.sun_path - 1); |
214 | errExit("mkdir"); | 129 | assert((size_t)sun_pathmax == sizeof sa.sun_path - 1); |
215 | if (set_perms(RUN_WHITELIST_X11_DIR, 0, 0, 1777)) | 130 | int sun_pathlen; |
216 | errExit("set_perms"); | ||
217 | 131 | ||
218 | if (mount("/tmp/.X11-unix", RUN_WHITELIST_X11_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | 132 | int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); |
219 | errExit("mount bind"); | 133 | if (sockfd == -1) |
134 | errExit("socket"); | ||
220 | 135 | ||
221 | // mount tmpfs on /tmp/.X11-unix | 136 | for (i = 0; i < 100; i++) { |
222 | if (arg_debug || arg_debug_whitelists) | 137 | // We try display numbers in the range 21 through 1000. |
223 | printf("Mounting tmpfs on /tmp/.X11-unix directory\n"); | 138 | // Normal X servers typically use displays in the 0-10 range; |
224 | if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=1777,gid=0") < 0) | 139 | // ssh's X11 forwarding uses 10-20, and login screens |
225 | errExit("mounting tmpfs on /tmp"); | 140 | // (e.g. gdm3) may use displays above 1000. |
226 | fs_logger("tmpfs /tmp/.X11-unix"); | 141 | display = rand() % 979 + 21; |
142 | |||
143 | // The display number might be claimed by a server listening | ||
144 | // in _either_ the normal or the abstract namespace; they | ||
145 | // don't necessarily do both. The easiest way to check is | ||
146 | // to try to connect, both ways. | ||
147 | memset(&sa, 0, sizeof sa); | ||
148 | sa.sun_family = AF_UNIX; | ||
149 | sun_pathlen = snprintf(sa.sun_path, sun_pathmax, | ||
150 | "/tmp/.X11-unix/X%d", display); | ||
151 | if (sun_pathlen >= sun_pathmax) { | ||
152 | fprintf(stderr, "sun_path too small for display :%d" | ||
153 | " (only %d bytes usable)\n", display, sun_pathmax); | ||
154 | exit(1); | ||
155 | } | ||
156 | |||
157 | if (connect(sockfd, (struct sockaddr *)&sa, | ||
158 | offsetof(struct sockaddr_un, sun_path) + sun_pathlen + 1) == 0) { | ||
159 | close(sockfd); | ||
160 | sockfd = socket(AF_UNIX, SOCK_STREAM, 0); | ||
161 | if (sockfd == -1) | ||
162 | errExit("socket"); | ||
163 | continue; | ||
164 | } | ||
165 | if (errno != ECONNREFUSED && errno != ENOENT) | ||
166 | errExit("connect"); | ||
167 | |||
168 | // Name not claimed in the normal namespace; now try it | ||
169 | // in the abstract namespace. Note that abstract-namespace | ||
170 | // names are NOT nul-terminated; they extend to the length | ||
171 | // specified as the third argument to 'connect'. | ||
172 | memmove(sa.sun_path + 1, sa.sun_path, sun_pathlen + 1); | ||
173 | sa.sun_path[0] = '\0'; | ||
174 | if (connect(sockfd, (struct sockaddr *)&sa, | ||
175 | offsetof(struct sockaddr_un, sun_path) + 1 + sun_pathlen) == 0) { | ||
176 | close(sockfd); | ||
177 | sockfd = socket(AF_UNIX, SOCK_STREAM, 0); | ||
178 | if (sockfd == -1) | ||
179 | errExit("socket"); | ||
180 | continue; | ||
181 | } | ||
182 | if (errno != ECONNREFUSED && errno != ENOENT) | ||
183 | errExit("connect"); | ||
184 | |||
185 | // This display number is unclaimed. Of course, it could | ||
186 | // be claimed before we get around to doing it... | ||
187 | found = 1; | ||
188 | break; | ||
189 | } | ||
190 | close(sockfd); | ||
227 | 191 | ||
228 | // create an empty file | 192 | if (!found) { |
229 | /* coverity[toctou] */ | 193 | fputs("Error: cannot find an unallocated X11 display number, " |
230 | FILE *fp = fopen(x11file, "w"); | 194 | "exiting...\n", stderr); |
231 | if (!fp) { | ||
232 | fprintf(stderr, "Error: cannot create empty file in x11 directory\n"); | ||
233 | exit(1); | 195 | exit(1); |
234 | } | 196 | } |
235 | // set file properties | 197 | return display; |
236 | SET_PERMS_STREAM(fp, s.st_uid, s.st_gid, s.st_mode); | 198 | } |
237 | fclose(fp); | ||
238 | |||
239 | // mount | ||
240 | char *wx11file; | ||
241 | if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1) | ||
242 | errExit("asprintf"); | ||
243 | if (mount(wx11file, x11file, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
244 | errExit("mount bind"); | ||
245 | fs_logger2("whitelist", x11file); | ||
246 | |||
247 | free(x11file); | ||
248 | free(wx11file); | ||
249 | |||
250 | // block access to RUN_WHITELIST_X11_DIR | ||
251 | if (mount(RUN_RO_DIR, RUN_WHITELIST_X11_DIR, "none", MS_BIND, "mode=400,gid=0") == -1) | ||
252 | errExit("mount"); | ||
253 | fs_logger2("blacklist", RUN_WHITELIST_X11_DIR); | ||
254 | #endif | 199 | #endif |
255 | 200 | ||
256 | #endif | 201 | |
257 | } | ||
258 | 202 | ||
259 | 203 | ||
260 | #ifdef HAVE_X11 | 204 | #ifdef HAVE_X11 |
@@ -277,7 +221,7 @@ void x11_start_xephyr(int argc, char **argv) { | |||
277 | drop_privs(0); | 221 | drop_privs(0); |
278 | 222 | ||
279 | // check xephyr | 223 | // check xephyr |
280 | if (x11_check_xephyr() == 0) { | 224 | if (!program_in_path("Xephyr")) { |
281 | fprintf(stderr, "\nError: Xephyr program was not found in /usr/bin directory, please install it:\n"); | 225 | fprintf(stderr, "\nError: Xephyr program was not found in /usr/bin directory, please install it:\n"); |
282 | fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xserver-xephyr\n"); | 226 | fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xserver-xephyr\n"); |
283 | fprintf(stderr, " Arch: sudo pacman -S xorg-server-xephyr\n"); | 227 | fprintf(stderr, " Arch: sudo pacman -S xorg-server-xephyr\n"); |
@@ -477,7 +421,7 @@ void x11_start_xpra(int argc, char **argv) { | |||
477 | drop_privs(0); | 421 | drop_privs(0); |
478 | 422 | ||
479 | // check xpra | 423 | // check xpra |
480 | if (x11_check_xpra() == 0) { | 424 | if (!program_in_path("xpra")) { |
481 | fprintf(stderr, "\nError: Xpra program was not found in /usr/bin directory, please install it:\n"); | 425 | fprintf(stderr, "\nError: Xpra program was not found in /usr/bin directory, please install it:\n"); |
482 | fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xpra\n"); | 426 | fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xpra\n"); |
483 | exit(0); | 427 | exit(0); |
@@ -670,9 +614,9 @@ void x11_start(int argc, char **argv) { | |||
670 | } | 614 | } |
671 | 615 | ||
672 | // check xpra | 616 | // check xpra |
673 | if (x11_check_xpra() == 1) | 617 | if (program_in_path("xpra")) |
674 | x11_start_xpra(argc, argv); | 618 | x11_start_xpra(argc, argv); |
675 | else if (x11_check_xephyr() == 1) | 619 | else if (program_in_path("Xephyr")) |
676 | x11_start_xephyr(argc, argv); | 620 | x11_start_xephyr(argc, argv); |
677 | else { | 621 | else { |
678 | fprintf(stderr, "\nError: Xpra or Xephyr not found in /usr/bin directory, please install one of them:\n"); | 622 | fprintf(stderr, "\nError: Xpra or Xephyr not found in /usr/bin directory, please install one of them:\n"); |
@@ -684,58 +628,39 @@ void x11_start(int argc, char **argv) { | |||
684 | 628 | ||
685 | #endif | 629 | #endif |
686 | 630 | ||
687 | void x11_block(void) { | 631 | // Porting notes: |
632 | // | ||
633 | // 1. merge #1100 from zackw: | ||
634 | // Attempting to run xauth -f directly on a file in /run/firejail/mnt/ directory fails on Debian 8 | ||
635 | // with this message: | ||
636 | // xauth: timeout in locking authority file /run/firejail/mnt/sec.Xauthority-Qt5Mu4 | ||
637 | // Failed to create untrusted X cookie: xauth: exit 1 | ||
638 | // For this reason we run xauth on a file in a tmpfs filesystem mounted on /tmp. This was | ||
639 | // a partial merge. | ||
640 | // | ||
641 | // 2. Since we cannot deal with the TOCTOU condition when mounting .Xauthority in user home | ||
642 | // directory, we need to make sure /usr/bin/xauth executable is the real thing, and not | ||
643 | // something picked up on $PATH. | ||
644 | // | ||
645 | void x11_xorg(void) { | ||
688 | #ifdef HAVE_X11 | 646 | #ifdef HAVE_X11 |
689 | mask_x11_abstract_socket = 1; | ||
690 | 647 | ||
691 | // check abstract socket presence and network namespace options | 648 | // check xauth utility is present in the system |
692 | if ((!arg_nonetwork && !cfg.bridge0.configured && !cfg.interface0.configured) | 649 | struct stat s; |
693 | && x11_abstract_sockets_present()) { | 650 | if (stat("/usr/bin/xauth", &s) == -1) { |
694 | fprintf(stderr, "ERROR: --x11=none specified, but abstract X11 socket still accessible.\n" | 651 | fprintf(stderr, "Error: xauth utility not found in PATH. Please install it:\n" |
695 | "Additional setup required. To block abstract X11 socket you can either:\n" | 652 | " Debian/Ubuntu/Mint: sudo apt-get install xauth\n"); |
696 | " * use network namespace in firejail (--net=none, --net=...)\n" | ||
697 | " * add \"-nolisten local\" to xserver options\n" | ||
698 | " (eg. to your display manager config, or /etc/X11/xinit/xserverrc)\n"); | ||
699 | exit(1); | 653 | exit(1); |
700 | } | 654 | } |
701 | 655 | if (s.st_uid != 0 && s.st_gid != 0) { | |
702 | // blacklist sockets | 656 | fprintf(stderr, "Error: invalid /usr/bin/xauth executable\n"); |
703 | profile_check_line("blacklist /tmp/.X11-unix", 0, NULL); | 657 | exit(1); |
704 | profile_add(strdup("blacklist /tmp/.X11-unix")); | ||
705 | |||
706 | // blacklist .Xauthority | ||
707 | profile_check_line("blacklist ${HOME}/.Xauthority", 0, NULL); | ||
708 | profile_add(strdup("blacklist ${HOME}/.Xauthority")); | ||
709 | char *xauthority = getenv("XAUTHORITY"); | ||
710 | if (xauthority) { | ||
711 | char *line; | ||
712 | if (asprintf(&line, "blacklist %s", xauthority) == -1) | ||
713 | errExit("asprintf"); | ||
714 | profile_check_line(line, 0, NULL); | ||
715 | profile_add(line); | ||
716 | } | ||
717 | |||
718 | // clear environment | ||
719 | env_store("DISPLAY", RMENV); | ||
720 | env_store("XAUTHORITY", RMENV); | ||
721 | #endif | ||
722 | } | ||
723 | |||
724 | void x11_xorg(void) { | ||
725 | #ifdef HAVE_X11 | ||
726 | // destination - create an empty ~/.Xauthotrity file if it doesn't exist already, and use it as a mount point | ||
727 | char *dest; | ||
728 | if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) | ||
729 | errExit("asprintf"); | ||
730 | struct stat s; | ||
731 | if (stat(dest, &s) == -1) { | ||
732 | // create an .Xauthority file | ||
733 | touch_file_as_user(dest, getuid(), getgid(), 0600); | ||
734 | } | 658 | } |
735 | 659 | ||
736 | // check xauth utility is present in the system | 660 | // get DISPLAY env |
737 | if (stat("/usr/bin/xauth", &s) == -1) { | 661 | char *display = getenv("DISPLAY"); |
738 | fprintf(stderr, "Error: cannot find /usr/bin/xauth executable\n"); | 662 | if (!display) { |
663 | fputs("Error: --x11=xorg requires an 'outer' X11 server to use.\n", stderr); | ||
739 | exit(1); | 664 | exit(1); |
740 | } | 665 | } |
741 | 666 | ||
@@ -743,7 +668,9 @@ void x11_xorg(void) { | |||
743 | if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) | 668 | if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) |
744 | errExit("mounting /tmp"); | 669 | errExit("mounting /tmp"); |
745 | 670 | ||
746 | // create a temporary .Xauthority file | 671 | // create the temporary .Xauthority file |
672 | if (arg_debug) | ||
673 | printf("Generating a new .Xauthority file\n"); | ||
747 | char tmpfname[] = "/tmp/.tmpXauth-XXXXXX"; | 674 | char tmpfname[] = "/tmp/.tmpXauth-XXXXXX"; |
748 | int fd = mkstemp(tmpfname); | 675 | int fd = mkstemp(tmpfname); |
749 | if (fd == -1) { | 676 | if (fd == -1) { |
@@ -758,38 +685,48 @@ void x11_xorg(void) { | |||
758 | if (child < 0) | 685 | if (child < 0) |
759 | errExit("fork"); | 686 | errExit("fork"); |
760 | if (child == 0) { | 687 | if (child == 0) { |
761 | // generate the new .Xauthority file using xauth utility | ||
762 | if (arg_debug) | ||
763 | printf("Generating a new .Xauthority file\n"); | ||
764 | drop_privs(1); | 688 | drop_privs(1); |
765 | |||
766 | char *display = getenv("DISPLAY"); | ||
767 | if (!display) | ||
768 | display = ":0.0"; | ||
769 | |||
770 | clearenv(); | 689 | clearenv(); |
771 | execlp("/usr/bin/xauth", "/usr/bin/xauth", "-f", tmpfname, | ||
772 | "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted", NULL); | ||
773 | |||
774 | #ifdef HAVE_GCOV | 690 | #ifdef HAVE_GCOV |
775 | __gcov_flush(); | 691 | __gcov_flush(); |
776 | #endif | 692 | #endif |
777 | _exit(0); | 693 | execlp("/usr/bin/xauth", "/usr/bin/xauth", "-f", tmpfname, |
694 | "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted", NULL); | ||
695 | |||
696 | _exit(127); | ||
697 | } | ||
698 | |||
699 | // wait for the xauth process to finish | ||
700 | int status; | ||
701 | if (waitpid(child, &status, 0) != child) | ||
702 | errExit("waitpid"); | ||
703 | if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { | ||
704 | /* success */ | ||
705 | } else if (WIFEXITED(status)) { | ||
706 | fprintf(stderr, "Failed to create untrusted X cookie: xauth: exit %d\n", | ||
707 | WEXITSTATUS(status)); | ||
708 | exit(1); | ||
709 | } else if (WIFSIGNALED(status)) { | ||
710 | fprintf(stderr, "Failed to create untrusted X cookie: xauth: %s\n", | ||
711 | strsignal(WTERMSIG(status))); | ||
712 | exit(1); | ||
713 | } else { | ||
714 | fprintf(stderr, "Failed to create untrusted X cookie: " | ||
715 | "xauth: un-decodable exit status %04x\n", status); | ||
716 | exit(1); | ||
778 | } | 717 | } |
779 | 718 | ||
780 | // wait for the child to finish | 719 | // ensure the file has the correct permissions and move it |
781 | waitpid(child, NULL, 0); | 720 | // into the correct location. |
782 | |||
783 | // check the file was created and set mode and ownership | ||
784 | if (stat(tmpfname, &s) == -1) { | 721 | if (stat(tmpfname, &s) == -1) { |
785 | fprintf(stderr, "Error: cannot create the new .Xauthority file\n"); | 722 | fprintf(stderr, "Error: .Xauthority file was mpt created\n"); |
786 | exit(1); | 723 | exit(1); |
787 | } | 724 | } |
788 | if (set_perms(tmpfname, getuid(), getgid(), 0600)) | 725 | if (set_perms(tmpfname, getuid(), getgid(), 0600)) |
789 | errExit("set_perms"); | 726 | errExit("set_perms"); |
790 | 727 | ||
791 | // move the temporary file in RUN_XAUTHORITY_SEC_FILE in order to have it deleted | 728 | // move the temporary file in RUN_XAUTHORITY_SEC_FILE in order to have it deleted |
792 | // automatically when the sandbox is closed | 729 | // automatically when the sandbox is closed (rename doesn't work) |
793 | if (copy_file(tmpfname, RUN_XAUTHORITY_SEC_FILE, getuid(), getgid(), 0600)) { // root needed | 730 | if (copy_file(tmpfname, RUN_XAUTHORITY_SEC_FILE, getuid(), getgid(), 0600)) { // root needed |
794 | fprintf(stderr, "Error: cannot create the new .Xauthority file\n"); | 731 | fprintf(stderr, "Error: cannot create the new .Xauthority file\n"); |
795 | exit(1); | 732 | exit(1); |
@@ -798,17 +735,132 @@ void x11_xorg(void) { | |||
798 | errExit("set_perms"); | 735 | errExit("set_perms"); |
799 | /* coverity[toctou] */ | 736 | /* coverity[toctou] */ |
800 | unlink(tmpfname); | 737 | unlink(tmpfname); |
738 | umount("/tmp"); | ||
801 | 739 | ||
740 | |||
741 | // Ensure there is already a file in the usual location, so that bind-mount below will work. | ||
742 | // todo: fix TOCTOU races, currently managed by imposing /usr/bin/xauth as executable | ||
743 | char *dest; | ||
744 | if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) | ||
745 | errExit("asprintf"); | ||
746 | if (stat(dest, &s) == -1) { | ||
747 | // create an .Xauthority file | ||
748 | touch_file_as_user(dest, getuid(), getgid(), 0600); | ||
749 | } | ||
750 | if (is_link(dest)) { | ||
751 | fprintf(stderr, "Error: .Xauthority is a symbolic link\n"); | ||
752 | exit(1); | ||
753 | } | ||
754 | |||
802 | // mount | 755 | // mount |
803 | if (mount(RUN_XAUTHORITY_SEC_FILE, dest, "none", MS_BIND, "mode=0600") == -1) { | 756 | if (mount(RUN_XAUTHORITY_SEC_FILE, dest, "none", MS_BIND, "mode=0600") == -1) { |
804 | fprintf(stderr, "Error: cannot mount the new .Xauthority file\n"); | 757 | fprintf(stderr, "Error: cannot mount the new .Xauthority file\n"); |
805 | exit(1); | 758 | exit(1); |
806 | } | 759 | } |
760 | // just in case... | ||
807 | if (set_perms(dest, getuid(), getgid(), 0600)) | 761 | if (set_perms(dest, getuid(), getgid(), 0600)) |
808 | errExit("set_perms"); | 762 | errExit("set_perms"); |
809 | free(dest); | 763 | free(dest); |
810 | |||
811 | // unmount /tmp | ||
812 | umount("/tmp"); | ||
813 | #endif | 764 | #endif |
814 | } | 765 | } |
766 | |||
767 | void fs_x11(void) { | ||
768 | #ifdef HAVE_X11 | ||
769 | int display = x11_display(); | ||
770 | if (display <= 0) | ||
771 | return; | ||
772 | |||
773 | char *x11file; | ||
774 | if (asprintf(&x11file, "/tmp/.X11-unix/X%d", display) == -1) | ||
775 | errExit("asprintf"); | ||
776 | struct stat x11stat; | ||
777 | if (stat(x11file, &x11stat) == -1 || !S_ISSOCK(x11stat.st_mode)) { | ||
778 | free(x11file); | ||
779 | return; | ||
780 | } | ||
781 | |||
782 | if (arg_debug || arg_debug_whitelists) | ||
783 | fprintf(stderr, "Masking all X11 sockets except %s\n", x11file); | ||
784 | |||
785 | // Move the real /tmp/.X11-unix to a scratch location | ||
786 | // so we can still access x11file after we mount a | ||
787 | // tmpfs over /tmp/.X11-unix. | ||
788 | int rv = mkdir(RUN_WHITELIST_X11_DIR, 0700); | ||
789 | if (rv == -1) | ||
790 | errExit("mkdir"); | ||
791 | if (set_perms(RUN_WHITELIST_X11_DIR, 0, 0, 0700)) | ||
792 | errExit("set_perms"); | ||
793 | |||
794 | if (mount("/tmp/.X11-unix", RUN_WHITELIST_X11_DIR, 0, MS_BIND|MS_REC, 0) < 0) | ||
795 | errExit("mount bind"); | ||
796 | |||
797 | // This directory must be mode 1777, or Xlib will barf. | ||
798 | if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs", | ||
799 | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME | MS_REC, | ||
800 | "mode=1777,uid=0,gid=0") < 0) | ||
801 | errExit("mounting tmpfs on /tmp/.X11-unix"); | ||
802 | fs_logger("tmpfs /tmp/.X11-unix"); | ||
803 | |||
804 | // create an empty file which will have the desired socket bind-mounted over it | ||
805 | int fd = open(x11file, O_RDWR|O_CREAT|O_EXCL, x11stat.st_mode & ~S_IFMT); | ||
806 | if (fd < 0) | ||
807 | errExit(x11file); | ||
808 | if (fchown(fd, x11stat.st_uid, x11stat.st_gid)) | ||
809 | errExit("fchown"); | ||
810 | close(fd); | ||
811 | |||
812 | // do the mount | ||
813 | char *wx11file; | ||
814 | if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1) | ||
815 | errExit("asprintf"); | ||
816 | if (mount(wx11file, x11file, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
817 | errExit("mount bind"); | ||
818 | fs_logger2("whitelist", x11file); | ||
819 | |||
820 | free(x11file); | ||
821 | free(wx11file); | ||
822 | |||
823 | // block access to RUN_WHITELIST_X11_DIR | ||
824 | if (mount(RUN_RO_DIR, RUN_WHITELIST_X11_DIR, 0, MS_BIND, 0) < 0) | ||
825 | errExit("mount"); | ||
826 | fs_logger2("blacklist", RUN_WHITELIST_X11_DIR); | ||
827 | #endif | ||
828 | } | ||
829 | |||
830 | void x11_block(void) { | ||
831 | #ifdef HAVE_X11 | ||
832 | mask_x11_abstract_socket = 1; | ||
833 | |||
834 | // check abstract socket presence and network namespace options | ||
835 | if ((!arg_nonetwork && !cfg.bridge0.configured && !cfg.interface0.configured) | ||
836 | && x11_abstract_sockets_present()) { | ||
837 | fprintf(stderr, "ERROR: --x11=none specified, but abstract X11 socket still accessible.\n" | ||
838 | "Additional setup required. To block abstract X11 socket you can either:\n" | ||
839 | " * use network namespace in firejail (--net=none, --net=...)\n" | ||
840 | " * add \"-nolisten local\" to xserver options\n" | ||
841 | " (eg. to your display manager config, or /etc/X11/xinit/xserverrc)\n"); | ||
842 | exit(1); | ||
843 | } | ||
844 | |||
845 | // blacklist sockets | ||
846 | profile_check_line("blacklist /tmp/.X11-unix", 0, NULL); | ||
847 | profile_add(strdup("blacklist /tmp/.X11-unix")); | ||
848 | |||
849 | // blacklist .Xauthority | ||
850 | profile_check_line("blacklist ${HOME}/.Xauthority", 0, NULL); | ||
851 | profile_add(strdup("blacklist ${HOME}/.Xauthority")); | ||
852 | char *xauthority = getenv("XAUTHORITY"); | ||
853 | if (xauthority) { | ||
854 | char *line; | ||
855 | if (asprintf(&line, "blacklist %s", xauthority) == -1) | ||
856 | errExit("asprintf"); | ||
857 | profile_check_line(line, 0, NULL); | ||
858 | profile_add(line); | ||
859 | } | ||
860 | |||
861 | // clear environment | ||
862 | env_store("DISPLAY", RMENV); | ||
863 | env_store("XAUTHORITY", RMENV); | ||
864 | #endif | ||
865 | } | ||
866 | |||
diff --git a/test/fcopy/cmdline.exp b/test/fcopy/cmdline.exp index 3ea33b01b..10dd8da58 100755 --- a/test/fcopy/cmdline.exp +++ b/test/fcopy/cmdline.exp | |||
@@ -10,7 +10,7 @@ match_max 100000 | |||
10 | send -- "/usr/lib/firejail/fcopy\r" | 10 | send -- "/usr/lib/firejail/fcopy\r" |
11 | expect { | 11 | expect { |
12 | timeout {puts "TESTING ERROR 0\n";exit} | 12 | timeout {puts "TESTING ERROR 0\n";exit} |
13 | "files missing" | 13 | "arguments missing" |
14 | } | 14 | } |
15 | expect { | 15 | expect { |
16 | timeout {puts "TESTING ERROR 1\n";exit} | 16 | timeout {puts "TESTING ERROR 1\n";exit} |
@@ -21,7 +21,7 @@ after 100 | |||
21 | send -- "/usr/lib/firejail/fcopy foo\r" | 21 | send -- "/usr/lib/firejail/fcopy foo\r" |
22 | expect { | 22 | expect { |
23 | timeout {puts "TESTING ERROR 2\n";exit} | 23 | timeout {puts "TESTING ERROR 2\n";exit} |
24 | "files missing" | 24 | "arguments missing" |
25 | } | 25 | } |
26 | expect { | 26 | expect { |
27 | timeout {puts "TESTING ERROR 3\n";exit} | 27 | timeout {puts "TESTING ERROR 3\n";exit} |
@@ -32,14 +32,14 @@ after 100 | |||
32 | send -- "/usr/lib/firejail/fcopy f%oo1 foo2\r" | 32 | send -- "/usr/lib/firejail/fcopy f%oo1 foo2\r" |
33 | expect { | 33 | expect { |
34 | timeout {puts "TESTING ERROR 4\n";exit} | 34 | timeout {puts "TESTING ERROR 4\n";exit} |
35 | "invalid file name" | 35 | "invalid source file name" |
36 | } | 36 | } |
37 | after 100 | 37 | after 100 |
38 | 38 | ||
39 | send -- "/usr/lib/firejail/fcopy foo1 f,oo2\r" | 39 | send -- "/usr/lib/firejail/fcopy foo1 f,oo2\r" |
40 | expect { | 40 | expect { |
41 | timeout {puts "TESTING ERROR 5\n";exit} | 41 | timeout {puts "TESTING ERROR 5\n";exit} |
42 | "invalid file name" | 42 | "invalid dest file name" |
43 | } | 43 | } |
44 | after 100 | 44 | after 100 |
45 | 45 | ||
diff --git a/test/fs/private-home.exp b/test/fs/private-home.exp index f2f30914d..259eb4f9e 100755 --- a/test/fs/private-home.exp +++ b/test/fs/private-home.exp | |||
@@ -89,7 +89,7 @@ expect { | |||
89 | "Child process initialized" | 89 | "Child process initialized" |
90 | } | 90 | } |
91 | after 100 | 91 | after 100 |
92 | send -- "file file ~/_firejail_test_link2\r" | 92 | send -- "file ~/_firejail_test_link2\r" |
93 | expect { | 93 | expect { |
94 | timeout {puts "TESTING ERROR 11\n";exit} | 94 | timeout {puts "TESTING ERROR 11\n";exit} |
95 | "broken symbolic link" | 95 | "broken symbolic link" |