diff options
Diffstat (limited to 'src/firejail/profile.c')
-rw-r--r-- | src/firejail/profile.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/src/firejail/profile.c b/src/firejail/profile.c new file mode 100644 index 000000000..343907584 --- /dev/null +++ b/src/firejail/profile.c | |||
@@ -0,0 +1,444 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com) | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | #include "firejail.h" | ||
21 | #include <dirent.h> | ||
22 | #include <sys/stat.h> | ||
23 | |||
24 | #define MAX_READ 8192 // line buffer for profile files | ||
25 | |||
26 | // find and read the profile specified by name from dir directory | ||
27 | int profile_find(const char *name, const char *dir) { | ||
28 | assert(name); | ||
29 | assert(dir); | ||
30 | |||
31 | int rv = 0; | ||
32 | DIR *dp; | ||
33 | char *pname; | ||
34 | if (asprintf(&pname, "%s.profile", name) == -1) | ||
35 | errExit("asprintf"); | ||
36 | |||
37 | dp = opendir (dir); | ||
38 | if (dp != NULL) { | ||
39 | struct dirent *ep; | ||
40 | while ((ep = readdir(dp)) != NULL) { | ||
41 | if (strcmp(ep->d_name, pname) == 0) { | ||
42 | if (arg_debug) | ||
43 | printf("Found %s profile in %s directory\n", name, dir); | ||
44 | char *etcpname; | ||
45 | if (asprintf(&etcpname, "%s/%s", dir, pname) == -1) | ||
46 | errExit("asprintf"); | ||
47 | profile_read(etcpname, NULL, NULL); | ||
48 | free(etcpname); | ||
49 | rv = 1; | ||
50 | break; | ||
51 | } | ||
52 | } | ||
53 | (void) closedir (dp); | ||
54 | } | ||
55 | |||
56 | free(pname); | ||
57 | return rv; | ||
58 | } | ||
59 | |||
60 | |||
61 | //*************************************************** | ||
62 | // run-time profiles | ||
63 | //*************************************************** | ||
64 | static void check_file_name(char *ptr, int lineno) { | ||
65 | if (strncmp(ptr, "${HOME}", 7) == 0) | ||
66 | ptr += 7; | ||
67 | else if (strncmp(ptr, "${PATH}", 7) == 0) | ||
68 | ptr += 7; | ||
69 | |||
70 | int len = strlen(ptr); | ||
71 | // file globbing ('*') is allowed | ||
72 | if (strcspn(ptr, "\\&!?\"'<>%^(){}[];, ") != len) { | ||
73 | if (lineno == 0) | ||
74 | fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); | ||
75 | else | ||
76 | fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); | ||
77 | exit(1); | ||
78 | } | ||
79 | } | ||
80 | |||
81 | |||
82 | // check profile line; if line == 0, this was generated from a command line option | ||
83 | // return 1 if the command is to be added to the linked list of profile commands | ||
84 | // return 0 if the command was already executed inside the function | ||
85 | int profile_check_line(char *ptr, int lineno) { | ||
86 | // seccomp, caps, private, user namespace | ||
87 | if (strcmp(ptr, "noroot") == 0) { | ||
88 | check_user_namespace(); | ||
89 | return 0; | ||
90 | } | ||
91 | else if (strcmp(ptr, "seccomp") == 0) { | ||
92 | arg_seccomp = 1; | ||
93 | return 0; | ||
94 | } | ||
95 | else if (strcmp(ptr, "caps") == 0) { | ||
96 | arg_caps_default_filter = 1; | ||
97 | return 0; | ||
98 | } | ||
99 | else if (strcmp(ptr, "caps.drop all") == 0) { | ||
100 | arg_caps_drop_all = 1; | ||
101 | return 0; | ||
102 | } | ||
103 | else if (strcmp(ptr, "shell none") == 0) { | ||
104 | arg_shell_none = 1; | ||
105 | return 0; | ||
106 | } | ||
107 | else if (strcmp(ptr, "private") == 0) { | ||
108 | arg_private = 1; | ||
109 | return 0; | ||
110 | } | ||
111 | else if (strcmp(ptr, "private-dev") == 0) { | ||
112 | arg_private_dev = 1; | ||
113 | return 0; | ||
114 | } | ||
115 | else if (strcmp(ptr, "nogroups") == 0) { | ||
116 | arg_nogroups = 1; | ||
117 | return 0; | ||
118 | } | ||
119 | else if (strcmp(ptr, "netfilter") == 0) { | ||
120 | arg_netfilter = 1; | ||
121 | return 0; | ||
122 | } | ||
123 | else if (strncmp(ptr, "netfilter ", 10) == 0) { | ||
124 | arg_netfilter = 1; | ||
125 | arg_netfilter_file = strdup(ptr + 10); | ||
126 | if (!arg_netfilter_file) | ||
127 | errExit("strdup"); | ||
128 | check_netfilter_file(arg_netfilter_file); | ||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | // seccomp drop list on top of default list | ||
133 | if (strncmp(ptr, "seccomp ", 8) == 0) { | ||
134 | arg_seccomp = 1; | ||
135 | #ifdef HAVE_SECCOMP | ||
136 | arg_seccomp_list = strdup(ptr + 8); | ||
137 | if (!arg_seccomp_list) | ||
138 | errExit("strdup"); | ||
139 | #endif | ||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | // seccomp drop list without default list | ||
144 | if (strncmp(ptr, "seccomp.drop ", 13) == 0) { | ||
145 | arg_seccomp = 1; | ||
146 | #ifdef HAVE_SECCOMP | ||
147 | arg_seccomp_list_drop = strdup(ptr + 13); | ||
148 | if (!arg_seccomp_list_drop) | ||
149 | errExit("strdup"); | ||
150 | #endif | ||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | // seccomp keep list | ||
155 | if (strncmp(ptr, "seccomp.keep ", 13) == 0) { | ||
156 | arg_seccomp = 1; | ||
157 | #ifdef HAVE_SECCOMP | ||
158 | arg_seccomp_list_keep= strdup(ptr + 13); | ||
159 | if (!arg_seccomp_list_keep) | ||
160 | errExit("strdup"); | ||
161 | #endif | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | // caps drop list | ||
166 | if (strncmp(ptr, "caps.drop ", 10) == 0) { | ||
167 | arg_caps_drop = 1; | ||
168 | arg_caps_list = strdup(ptr + 10); | ||
169 | if (!arg_caps_list) | ||
170 | errExit("strdup"); | ||
171 | // verify seccomp list and exit if problems | ||
172 | if (caps_check_list(arg_caps_list, NULL)) | ||
173 | exit(1); | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | // caps keep list | ||
178 | if (strncmp(ptr, "caps.keep ", 10) == 0) { | ||
179 | arg_caps_keep = 1; | ||
180 | arg_caps_list = strdup(ptr + 10); | ||
181 | if (!arg_caps_list) | ||
182 | errExit("strdup"); | ||
183 | // verify seccomp list and exit if problems | ||
184 | if (caps_check_list(arg_caps_list, NULL)) | ||
185 | exit(1); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | // dns | ||
190 | if (strncmp(ptr, "dns ", 4) == 0) { | ||
191 | uint32_t dns; | ||
192 | if (atoip(ptr + 4, &dns)) { | ||
193 | fprintf(stderr, "Error: invalid DNS server IP address\n"); | ||
194 | return 1; | ||
195 | } | ||
196 | |||
197 | if (cfg.dns1 == 0) | ||
198 | cfg.dns1 = dns; | ||
199 | else if (cfg.dns2 == 0) | ||
200 | cfg.dns2 = dns; | ||
201 | else if (cfg.dns3 == 0) | ||
202 | cfg.dns3 = dns; | ||
203 | else { | ||
204 | fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); | ||
205 | return 1; | ||
206 | } | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | // cpu affinity | ||
211 | if (strncmp(ptr, "cpu ", 4) == 0) { | ||
212 | read_cpu_list(ptr + 4); | ||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | // cgroup | ||
217 | if (strncmp(ptr, "cgroup ", 7) == 0) { | ||
218 | set_cgroup(ptr + 7); | ||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | // private directory | ||
223 | if (strncmp(ptr, "private ", 8) == 0) { | ||
224 | cfg.home_private = ptr + 8; | ||
225 | fs_check_private_dir(); | ||
226 | arg_private = 1; | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | // private list of files and directories | ||
231 | if (strncmp(ptr, "private.keep ", 13) == 0) { | ||
232 | cfg.home_private_keep = ptr + 13; | ||
233 | fs_check_home_list(); | ||
234 | arg_private = 1; | ||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | // filesystem bind | ||
239 | if (strncmp(ptr, "bind ", 5) == 0) { | ||
240 | if (getuid() != 0) { | ||
241 | fprintf(stderr, "Error: --bind option is available only if running as root\n"); | ||
242 | exit(1); | ||
243 | } | ||
244 | |||
245 | // extract two directories | ||
246 | char *dname1 = ptr + 5; | ||
247 | char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories | ||
248 | if (dname2 == NULL) { | ||
249 | fprintf(stderr, "Error: mising second directory for bind\n"); | ||
250 | exit(1); | ||
251 | } | ||
252 | |||
253 | // check directories | ||
254 | check_file_name(dname1, lineno); | ||
255 | check_file_name(dname2, lineno); | ||
256 | if (strstr(dname1, "..") || strstr(dname2, "..")) { | ||
257 | fprintf(stderr, "Error: invalid file name.\n"); | ||
258 | exit(1); | ||
259 | } | ||
260 | |||
261 | // insert comma back | ||
262 | *(dname2 - 1) = ','; | ||
263 | return 1; | ||
264 | } | ||
265 | |||
266 | // rlimit | ||
267 | if (strncmp(ptr, "rlimit", 6) == 0) { | ||
268 | if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { | ||
269 | ptr += 14; | ||
270 | if (not_unsigned(ptr)) { | ||
271 | fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); | ||
272 | exit(1); | ||
273 | } | ||
274 | sscanf(ptr, "%u", &cfg.rlimit_nofile); | ||
275 | arg_rlimit_nofile = 1; | ||
276 | } | ||
277 | else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { | ||
278 | ptr += 13; | ||
279 | if (not_unsigned(ptr)) { | ||
280 | fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); | ||
281 | exit(1); | ||
282 | } | ||
283 | sscanf(ptr, "%u", &cfg.rlimit_nproc); | ||
284 | arg_rlimit_nproc = 1; | ||
285 | } | ||
286 | else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { | ||
287 | ptr += 13; | ||
288 | if (not_unsigned(ptr)) { | ||
289 | fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); | ||
290 | exit(1); | ||
291 | } | ||
292 | sscanf(ptr, "%u", &cfg.rlimit_fsize); | ||
293 | arg_rlimit_fsize = 1; | ||
294 | } | ||
295 | else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { | ||
296 | ptr += 18; | ||
297 | if (not_unsigned(ptr)) { | ||
298 | fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); | ||
299 | exit(1); | ||
300 | } | ||
301 | sscanf(ptr, "%u", &cfg.rlimit_sigpending); | ||
302 | arg_rlimit_sigpending = 1; | ||
303 | } | ||
304 | else { | ||
305 | fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); | ||
306 | exit(1); | ||
307 | } | ||
308 | |||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | // rest of filesystem | ||
313 | if (strncmp(ptr, "blacklist ", 10) == 0) | ||
314 | ptr += 10; | ||
315 | else if (strncmp(ptr, "read-only ", 10) == 0) | ||
316 | ptr += 10; | ||
317 | else if (strncmp(ptr, "tmpfs ", 6) == 0) | ||
318 | ptr += 6; | ||
319 | else { | ||
320 | if (lineno == 0) | ||
321 | fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); | ||
322 | else | ||
323 | fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); | ||
324 | exit(1); | ||
325 | } | ||
326 | |||
327 | // some characters just don't belong in filenames | ||
328 | check_file_name(ptr, lineno); | ||
329 | if (strstr(ptr, "..")) { | ||
330 | if (lineno == 0) | ||
331 | fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); | ||
332 | else | ||
333 | fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); | ||
334 | exit(1); | ||
335 | } | ||
336 | return 1; | ||
337 | } | ||
338 | |||
339 | // add a profile entry in cfg.profile list; use str to populate the list | ||
340 | void profile_add(char *str) { | ||
341 | ProfileEntry *prf = malloc(sizeof(ProfileEntry)); | ||
342 | if (!prf) | ||
343 | errExit("malloc"); | ||
344 | prf->next = NULL; | ||
345 | prf->data = str; | ||
346 | |||
347 | // add prf to the list | ||
348 | if (cfg.profile == NULL) { | ||
349 | cfg.profile = prf; | ||
350 | return; | ||
351 | } | ||
352 | ProfileEntry *ptr = cfg.profile; | ||
353 | while (ptr->next != NULL) | ||
354 | ptr = ptr->next; | ||
355 | ptr->next = prf; | ||
356 | } | ||
357 | |||
358 | // read a profile file | ||
359 | static int include_level = 0; | ||
360 | // skip1, skip2 - if the string is found in the line, the line is not interpreted | ||
361 | void profile_read(const char *fname, const char *skip1, const char *skip2) { | ||
362 | // exit program if maximum include level was reached | ||
363 | if (include_level > MAX_INCLUDE_LEVEL) { | ||
364 | fprintf(stderr, "Error: maximum profile include level was reached\n"); | ||
365 | exit(1); | ||
366 | } | ||
367 | |||
368 | if (strlen(fname) == 0) { | ||
369 | fprintf(stderr, "Error: invalid profile file\n"); | ||
370 | exit(1); | ||
371 | } | ||
372 | |||
373 | // open profile file: | ||
374 | FILE *fp = fopen(fname, "r"); | ||
375 | if (fp == NULL) { | ||
376 | fprintf(stderr, "Error: cannot open profile file\n"); | ||
377 | exit(1); | ||
378 | } | ||
379 | |||
380 | fprintf(stderr, "Reading profile %s\n", fname); | ||
381 | |||
382 | // read the file line by line | ||
383 | char buf[MAX_READ + 1]; | ||
384 | int lineno = 0; | ||
385 | while (fgets(buf, MAX_READ, fp)) { | ||
386 | ++lineno; | ||
387 | // remove empty space - ptr in allocated memory | ||
388 | char *ptr = line_remove_spaces(buf); | ||
389 | if (ptr == NULL) | ||
390 | continue; | ||
391 | |||
392 | // comments | ||
393 | if (*ptr == '#' || *ptr == '\0') { | ||
394 | free(ptr); | ||
395 | continue; | ||
396 | } | ||
397 | |||
398 | // process include | ||
399 | if (strncmp(ptr, "include ", 8) == 0) { | ||
400 | include_level++; | ||
401 | |||
402 | // extract profile filename and new skip params | ||
403 | char *newprofile = ptr + 8; // profile name | ||
404 | char *newskip1 = NULL; // new skip1 | ||
405 | char *newskip2 = NULL; // new skip2 | ||
406 | char *p = newprofile; | ||
407 | while (*p != '\0') { | ||
408 | if (*p == ' ') { | ||
409 | *p = '\0'; | ||
410 | if (newskip1 == NULL) | ||
411 | newskip1 = p + 1; | ||
412 | else if (newskip2 == NULL) | ||
413 | newskip2 = p + 1; | ||
414 | } | ||
415 | p++; | ||
416 | } | ||
417 | |||
418 | // recursivity | ||
419 | profile_read(newprofile, newskip1, newskip2); | ||
420 | include_level--; | ||
421 | free(ptr); | ||
422 | continue; | ||
423 | } | ||
424 | |||
425 | // skip | ||
426 | if (skip1) { | ||
427 | if (strstr(ptr, skip1)) { | ||
428 | free(ptr); | ||
429 | continue; | ||
430 | } | ||
431 | } | ||
432 | if (skip2) { | ||
433 | if (strstr(ptr, skip2)) { | ||
434 | free(ptr); | ||
435 | continue; | ||
436 | } | ||
437 | } | ||
438 | |||
439 | // verify syntax, exit in case of error | ||
440 | if (profile_check_line(ptr, lineno)) | ||
441 | profile_add(ptr); | ||
442 | } | ||
443 | fclose(fp); | ||
444 | } | ||