diff options
Diffstat (limited to 'src/firejail/bandwidth.c')
-rw-r--r-- | src/firejail/bandwidth.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/src/firejail/bandwidth.c b/src/firejail/bandwidth.c new file mode 100644 index 000000000..25dd7a0fb --- /dev/null +++ b/src/firejail/bandwidth.c | |||
@@ -0,0 +1,483 @@ | |||
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 | #define _GNU_SOURCE | ||
21 | #include <stdio.h> | ||
22 | #include <sys/types.h> | ||
23 | #include <sys/stat.h> | ||
24 | #include <unistd.h> | ||
25 | #include <net/if.h> | ||
26 | #include "firejail.h" | ||
27 | |||
28 | //*********************************** | ||
29 | // interface bandwidth linked list | ||
30 | //*********************************** | ||
31 | typedef struct ifbw_t { | ||
32 | struct ifbw_t *next; | ||
33 | char *txt; | ||
34 | } IFBW; | ||
35 | IFBW *ifbw = NULL; | ||
36 | |||
37 | |||
38 | #if 0 | ||
39 | static void ifbw_print(void) { | ||
40 | IFBW *ptr = ifbw; | ||
41 | while (ptr) { | ||
42 | printf("#%s#\n", ptr->txt); | ||
43 | ptr = ptr->next; | ||
44 | } | ||
45 | } | ||
46 | #endif | ||
47 | |||
48 | static void ifbw_add(IFBW *ptr) { | ||
49 | assert(ptr); | ||
50 | |||
51 | if (ifbw != NULL) | ||
52 | ptr->next = ifbw; | ||
53 | ifbw = ptr; | ||
54 | } | ||
55 | |||
56 | |||
57 | IFBW *ifbw_find(const char *dev) { | ||
58 | assert(dev); | ||
59 | int len = strlen(dev); | ||
60 | assert(len); | ||
61 | |||
62 | if (ifbw == NULL) | ||
63 | return NULL; | ||
64 | |||
65 | IFBW *ptr = ifbw; | ||
66 | while (ptr) { | ||
67 | if (strncmp(ptr->txt, dev, len) == 0 && ptr->txt[len] == ':') | ||
68 | return ptr; | ||
69 | ptr = ptr->next; | ||
70 | } | ||
71 | |||
72 | return NULL; | ||
73 | } | ||
74 | |||
75 | void ifbw_remove(IFBW *r) { | ||
76 | if (ifbw == NULL) | ||
77 | return; | ||
78 | |||
79 | // remove the first element | ||
80 | if (ifbw == r) { | ||
81 | ifbw = ifbw->next; | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | // walk the list | ||
86 | IFBW *ptr = ifbw->next; | ||
87 | IFBW *prev = ifbw; | ||
88 | while (ptr) { | ||
89 | if (ptr == r) { | ||
90 | prev->next = ptr->next; | ||
91 | return; | ||
92 | } | ||
93 | |||
94 | prev = ptr; | ||
95 | ptr = ptr->next; | ||
96 | } | ||
97 | |||
98 | return; | ||
99 | } | ||
100 | |||
101 | int fibw_count(viod) { | ||
102 | int rv = 0; | ||
103 | IFBW *ptr = ifbw; | ||
104 | |||
105 | while (ptr) { | ||
106 | rv++; | ||
107 | ptr = ptr->next; | ||
108 | } | ||
109 | |||
110 | return rv; | ||
111 | } | ||
112 | |||
113 | |||
114 | //*********************************** | ||
115 | // shm file handling | ||
116 | //*********************************** | ||
117 | void shm_create_firejail_dir(void) { | ||
118 | struct stat s; | ||
119 | if (stat("/dev/shm/firejail", &s) == -1) { | ||
120 | /* coverity[toctou] */ | ||
121 | if (mkdir("/dev/shm/firejail", 0777) == -1) | ||
122 | errExit("mkdir"); | ||
123 | if (chown("/dev/shm/firejail", 0, 0) == -1) | ||
124 | errExit("chown"); | ||
125 | } | ||
126 | else { // check /dev/shm/firejail directory belongs to root end exit if doesn't! | ||
127 | if (s.st_uid != 0 || s.st_gid != 0) { | ||
128 | fprintf(stderr, "Error: non-root %s directory, exiting...\n", "/dev/shm/firejail"); | ||
129 | exit(1); | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | static void shm_create_bandwidth_file(pid_t pid) { | ||
135 | char *fname; | ||
136 | if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1) | ||
137 | errExit("asprintf"); | ||
138 | |||
139 | // if the file already exists, do nothing | ||
140 | struct stat s; | ||
141 | if (stat(fname, &s) == 0) { | ||
142 | free(fname); | ||
143 | return; | ||
144 | } | ||
145 | |||
146 | // create an empty file and set mod and ownership | ||
147 | /* coverity[toctou] */ | ||
148 | FILE *fp = fopen(fname, "w"); | ||
149 | if (fp) { | ||
150 | fclose(fp); | ||
151 | |||
152 | /* coverity[toctou] */ | ||
153 | if (chmod(fname, 0644) == -1) | ||
154 | errExit("chmod"); | ||
155 | /* coverity[toctou] */ | ||
156 | if (chown(fname, 0, 0) == -1) | ||
157 | errExit("chown"); | ||
158 | } | ||
159 | else { | ||
160 | fprintf(stderr, "Error: cannot create bandwidth file in /dev/shm/firejail directory\n"); | ||
161 | exit(1); | ||
162 | } | ||
163 | |||
164 | free(fname); | ||
165 | } | ||
166 | |||
167 | // delete shm bandwidth file | ||
168 | void bandwidth_shm_del_file(pid_t pid) { | ||
169 | char *fname; | ||
170 | if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1) | ||
171 | errExit("asprintf"); | ||
172 | unlink(fname); | ||
173 | free(fname); | ||
174 | } | ||
175 | |||
176 | void network_shm_del_file(pid_t pid) { | ||
177 | char *fname; | ||
178 | if (asprintf(&fname, "/dev/shm/firejail/%d-netmap", (int) pid) == -1) | ||
179 | errExit("asprintf"); | ||
180 | unlink(fname); | ||
181 | free(fname); | ||
182 | } | ||
183 | |||
184 | void network_shm_set_file(pid_t pid) { | ||
185 | char *fname; | ||
186 | if (asprintf(&fname, "/dev/shm/firejail/%d-netmap", (int) pid) == -1) | ||
187 | errExit("asprintf"); | ||
188 | |||
189 | // create an empty file and set mod and ownership | ||
190 | FILE *fp = fopen(fname, "w"); | ||
191 | if (fp) { | ||
192 | if (cfg.bridge0.configured) | ||
193 | fprintf(fp, "%s:%s\n", cfg.bridge0.dev, cfg.bridge0.devsandbox); | ||
194 | if (cfg.bridge1.configured) | ||
195 | fprintf(fp, "%s:%s\n", cfg.bridge1.dev, cfg.bridge1.devsandbox); | ||
196 | if (cfg.bridge2.configured) | ||
197 | fprintf(fp, "%s:%s\n", cfg.bridge2.dev, cfg.bridge2.devsandbox); | ||
198 | if (cfg.bridge3.configured) | ||
199 | fprintf(fp, "%s:%s\n", cfg.bridge3.dev, cfg.bridge3.devsandbox); | ||
200 | fclose(fp); | ||
201 | |||
202 | if (chmod(fname, 0644) == -1) | ||
203 | errExit("chmod"); | ||
204 | if (chown(fname, 0, 0) == -1) | ||
205 | errExit("chown"); | ||
206 | } | ||
207 | else { | ||
208 | fprintf(stderr, "Error: cannot create network map file in /dev/shm/firejail directory\n"); | ||
209 | exit(1); | ||
210 | } | ||
211 | |||
212 | free(fname); | ||
213 | } | ||
214 | |||
215 | |||
216 | void shm_read_bandwidth_file(pid_t pid) { | ||
217 | assert(ifbw == NULL); | ||
218 | |||
219 | char *fname; | ||
220 | if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1) | ||
221 | errExit("asprintf"); | ||
222 | |||
223 | FILE *fp = fopen(fname, "r"); | ||
224 | if (fp) { | ||
225 | char buf[1024]; | ||
226 | while (fgets(buf, 1024,fp)) { | ||
227 | // remove '\n' | ||
228 | char *ptr = strchr(buf, '\n'); | ||
229 | if (ptr) | ||
230 | *ptr = '\0'; | ||
231 | if (strlen(buf) == 0) | ||
232 | continue; | ||
233 | |||
234 | // create a new IFBW entry | ||
235 | IFBW *ifbw_new = malloc(sizeof(IFBW)); | ||
236 | if (!ifbw_new) | ||
237 | errExit("malloc"); | ||
238 | memset(ifbw_new, 0, sizeof(IFBW)); | ||
239 | ifbw_new->txt = strdup(buf); | ||
240 | if (!ifbw_new->txt) | ||
241 | errExit("strdup"); | ||
242 | |||
243 | // add it to the linked list | ||
244 | ifbw_add(ifbw_new); | ||
245 | } | ||
246 | |||
247 | fclose(fp); | ||
248 | } | ||
249 | } | ||
250 | |||
251 | void shm_write_bandwidth_file(pid_t pid) { | ||
252 | if (ifbw == NULL) | ||
253 | return; // nothing to do | ||
254 | |||
255 | char *fname; | ||
256 | if (asprintf(&fname, "/dev/shm/firejail/%d-bandwidth", (int) pid) == -1) | ||
257 | errExit("asprintf"); | ||
258 | |||
259 | FILE *fp = fopen(fname, "w"); | ||
260 | if (fp) { | ||
261 | IFBW *ptr = ifbw; | ||
262 | while (ptr) { | ||
263 | fprintf(fp, "%s\n", ptr->txt); | ||
264 | ptr = ptr->next; | ||
265 | } | ||
266 | fclose(fp); | ||
267 | } | ||
268 | else { | ||
269 | fprintf(stderr, "Error: cannot write bandwidht file %s\n", fname); | ||
270 | exit(1); | ||
271 | } | ||
272 | } | ||
273 | |||
274 | //*********************************** | ||
275 | // add or remove interfaces | ||
276 | //*********************************** | ||
277 | |||
278 | // remove interface from shm file | ||
279 | void bandwidth_shm_remove(pid_t pid, const char *dev) { | ||
280 | // create bandwidth directory & file in case they are not in the filesystem yet | ||
281 | shm_create_firejail_dir(); | ||
282 | shm_create_bandwidth_file(pid); | ||
283 | |||
284 | // read bandwidth file | ||
285 | shm_read_bandwidth_file(pid); | ||
286 | |||
287 | // find the element and remove it | ||
288 | IFBW *elem = ifbw_find(dev); | ||
289 | if (elem) { | ||
290 | ifbw_remove(elem); | ||
291 | shm_write_bandwidth_file(pid) ; | ||
292 | } | ||
293 | |||
294 | // remove the file if there are no entries in the list | ||
295 | if (ifbw == NULL) { | ||
296 | bandwidth_shm_del_file(pid); | ||
297 | } | ||
298 | } | ||
299 | |||
300 | // add interface to shm file | ||
301 | void bandwidth_shm_set(pid_t pid, const char *dev, int down, int up) { | ||
302 | // create bandwidth directory & file in case they are not in the filesystem yet | ||
303 | shm_create_firejail_dir(); | ||
304 | shm_create_bandwidth_file(pid); | ||
305 | |||
306 | // create the new text entry | ||
307 | char *txt; | ||
308 | if (asprintf(&txt, "%s: RX %dKB/s, TX %dKB/s", dev, down, up) == -1) | ||
309 | errExit("asprintf"); | ||
310 | |||
311 | // read bandwidth file | ||
312 | shm_read_bandwidth_file(pid); | ||
313 | |||
314 | // look for an existing entry and replace the text | ||
315 | IFBW *ptr = ifbw_find(dev); | ||
316 | if (ptr) { | ||
317 | assert(ptr->txt); | ||
318 | free(ptr->txt); | ||
319 | ptr->txt = txt; | ||
320 | } | ||
321 | // ... or add a new entry | ||
322 | else { | ||
323 | IFBW *ifbw_new = malloc(sizeof(IFBW)); | ||
324 | if (!ifbw_new) | ||
325 | errExit("malloc"); | ||
326 | memset(ifbw_new, 0, sizeof(IFBW)); | ||
327 | ifbw_new->txt = txt; | ||
328 | |||
329 | // add it to the linked list | ||
330 | ifbw_add(ifbw_new); | ||
331 | } | ||
332 | shm_write_bandwidth_file(pid) ; | ||
333 | } | ||
334 | |||
335 | |||
336 | //*********************************** | ||
337 | // command execution | ||
338 | //*********************************** | ||
339 | void bandwidth_name(const char *name, const char *command, const char *dev, int down, int up) { | ||
340 | if (!name || strlen(name) == 0) { | ||
341 | fprintf(stderr, "Error: invalid sandbox name\n"); | ||
342 | exit(1); | ||
343 | } | ||
344 | pid_t pid; | ||
345 | if (name2pid(name, &pid)) { | ||
346 | fprintf(stderr, "Error: cannot find sandbox %s\n", name); | ||
347 | exit(1); | ||
348 | } | ||
349 | |||
350 | bandwidth_pid(pid, command, dev, down, up); | ||
351 | } | ||
352 | |||
353 | void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, int up) { | ||
354 | //************************ | ||
355 | // verify sandbox | ||
356 | //************************ | ||
357 | char *comm = pid_proc_comm(pid); | ||
358 | if (!comm) { | ||
359 | fprintf(stderr, "Error: cannot find sandbox\n"); | ||
360 | exit(1); | ||
361 | } | ||
362 | |||
363 | // remove \n and check for firejail sandbox | ||
364 | char *ptr = strchr(comm, '\n'); | ||
365 | if (ptr) | ||
366 | *ptr = '\0'; | ||
367 | if (strcmp(comm, "firejail") != 0) { | ||
368 | fprintf(stderr, "Error: cannot find sandbox\n"); | ||
369 | exit(1); | ||
370 | } | ||
371 | free(comm); | ||
372 | |||
373 | // check network namespace | ||
374 | char *cmd = pid_proc_cmdline(pid); | ||
375 | if (!cmd || strstr(cmd, "--net") == NULL) { | ||
376 | fprintf(stderr, "Error: the sandbox doesn't use a new network namespace\n"); | ||
377 | exit(1); | ||
378 | } | ||
379 | free(cmd); | ||
380 | |||
381 | |||
382 | //************************ | ||
383 | // join the network namespace | ||
384 | //************************ | ||
385 | pid_t child; | ||
386 | if (find_child(pid, &child) == -1) { | ||
387 | fprintf(stderr, "Error: cannot join the network namespace\n"); | ||
388 | exit(1); | ||
389 | } | ||
390 | if (join_namespace(child, "net")) { | ||
391 | fprintf(stderr, "Error: cannot join the network namespace\n"); | ||
392 | exit(1); | ||
393 | } | ||
394 | |||
395 | // set shm file | ||
396 | if (strcmp(command, "set") == 0) | ||
397 | bandwidth_shm_set(pid, dev, down, up); | ||
398 | else if (strcmp(command, "clear") == 0) | ||
399 | bandwidth_shm_remove(pid, dev); | ||
400 | |||
401 | //************************ | ||
402 | // build command | ||
403 | //************************ | ||
404 | char *devname = NULL; | ||
405 | if (dev) { | ||
406 | // read shm network map file | ||
407 | char *fname; | ||
408 | if (asprintf(&fname, "/dev/shm/firejail/%d-netmap", (int) pid) == -1) | ||
409 | errExit("asprintf"); | ||
410 | FILE *fp = fopen(fname, "r"); | ||
411 | if (!fp) { | ||
412 | fprintf(stderr, "Error: cannot read netowk map filel %s\n", fname); | ||
413 | exit(1); | ||
414 | } | ||
415 | |||
416 | char buf[1024]; | ||
417 | int len = strlen(dev); | ||
418 | while (fgets(buf, 1024, fp)) { | ||
419 | // remove '\n' | ||
420 | char *ptr = strchr(buf, '\n'); | ||
421 | if (ptr) | ||
422 | *ptr = '\0'; | ||
423 | if (*buf == '\0') | ||
424 | break; | ||
425 | |||
426 | if (strncmp(buf, dev, len) == 0 && buf[len] == ':') { | ||
427 | devname = strdup(buf + len + 1); | ||
428 | if (!devname) | ||
429 | errExit("strdup"); | ||
430 | // check device in namespace | ||
431 | if (if_nametoindex(devname) == 0) { | ||
432 | fprintf(stderr, "Error: cannot find network device %s\n", devname); | ||
433 | exit(1); | ||
434 | } | ||
435 | break; | ||
436 | } | ||
437 | } | ||
438 | free(fname); | ||
439 | fclose(fp); | ||
440 | } | ||
441 | |||
442 | // build fshaper.sh command | ||
443 | cmd = NULL; | ||
444 | if (devname) { | ||
445 | if (strcmp(command, "set") == 0) { | ||
446 | if (asprintf(&cmd, "%s/lib/firejail/fshaper.sh --%s %s %d %d", | ||
447 | PREFIX, command, devname, down, up) == -1) | ||
448 | errExit("asprintf"); | ||
449 | } | ||
450 | else { | ||
451 | if (asprintf(&cmd, "%s/lib/firejail/fshaper.sh --%s %s", | ||
452 | PREFIX, command, devname) == -1) | ||
453 | errExit("asprintf"); | ||
454 | } | ||
455 | } | ||
456 | else { | ||
457 | if (asprintf(&cmd, "%s/lib/firejail/fshaper.sh --%s", PREFIX, command) == -1) | ||
458 | errExit("asprintf"); | ||
459 | } | ||
460 | assert(cmd); | ||
461 | |||
462 | // wipe out environment variables | ||
463 | environ = NULL; | ||
464 | |||
465 | //************************ | ||
466 | // build command | ||
467 | //************************ | ||
468 | // elevate privileges | ||
469 | if (setreuid(0, 0)) | ||
470 | errExit("setreuid"); | ||
471 | if (setregid(0, 0)) | ||
472 | errExit("setregid"); | ||
473 | |||
474 | char *arg[4]; | ||
475 | arg[0] = "/bin/bash"; | ||
476 | arg[1] = "-c"; | ||
477 | arg[2] = cmd; | ||
478 | arg[3] = NULL; | ||
479 | execvp("/bin/bash", arg); | ||
480 | |||
481 | // it will never get here | ||
482 | exit(0); | ||
483 | } | ||