aboutsummaryrefslogtreecommitdiffstats
path: root/src/firejail/profile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/firejail/profile.c')
-rw-r--r--src/firejail/profile.c444
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
27int 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//***************************************************
64static 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
85int 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
340void 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
359static int include_level = 0;
360// skip1, skip2 - if the string is found in the line, the line is not interpreted
361void 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}