diff options
author | 2019-12-30 00:04:04 +0100 | |
---|---|---|
committer | 2019-12-30 16:56:47 +0100 | |
commit | ce3c1988578f6b18488a91132d355cf13a37e522 (patch) | |
tree | 3898a7fe71c84eb2ef1374b67404d760c637d5de /src/firejail/dhcp.c | |
parent | Add capability filter for network services, additive filter (diff) | |
download | firejail-ce3c1988578f6b18488a91132d355cf13a37e522.tar.gz firejail-ce3c1988578f6b18488a91132d355cf13a37e522.tar.zst firejail-ce3c1988578f6b18488a91132d355cf13a37e522.zip |
Run dhclient inside the sandbox
* In order to ensure that network interfaces are already configured when
the sandboxed launches, we run dhclient in forking mode (no -d switch),
which makes the dhclient command exit when it successfully acquired a lease.
The dhclient daemon process keeps running in the background.
* We read the pid file for dhclient to find out the pid of the daemon process.
Because dhclient only writes the pid file in the child process potentially
after the forking parent process exits, there is some handling for possible
race conditions.
* All lease files and pid files are under /run/firejail/dhclient/
* The v4 and v6 dhclient has a separate lease as recommended.
* The v4 client is set to generate a DUID, which is also used by the v6 client
so that the server can associate the two leases if needed.
* /etc/resolv.conf is created in the sandbox just like with the --dns option,
by mirroring /etc. When DHCP is used, /etc/resolv.conf is normally empty so
that dhclient can overwrite it the nameservers from the DHCP server.
Current limitations:
* The dhclient processes in the background are not terminated properly
(by SIGTERM or dhclient -x), nor is the DHCP lease released (by dclient -r).
The reason for this is that firejail drops all capabilities and privileges
before the application in the sandbox is launched, which makes it impossible
to launch dhclient to release the lease or kill the dhclient processes still
running with the effective user id of root. Instead the dhclient daemons
die with the sandbox. According to the dhclient man page, releasing the lease
is not required by the DHCP specification, so this is not a problem, however
some ISPs may require releasing leases.
A possible workaround would be to fork another process upon sandbox
initialization that invokes dhclient -r when the sandbox is ready to exit.
This would require communication with the main firejail process through
a pipe, while keeping and required privileges. As this would add some
complexity but the benefits have limited applicability (compatibility with
esoteric DHCP server configurations), I chose not to implement this.
* When only an IPv6 address is requested, the interface may possible not have
a link-local address when we run dhclient. This causes dhclient -6 fail,
since DHCPv6 uses link-local addressing instead of layer 2 addressing,
see e.g., https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=783387
In a future commit, waiting for a link-local address will be added.
Diffstat (limited to 'src/firejail/dhcp.c')
-rw-r--r-- | src/firejail/dhcp.c | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/src/firejail/dhcp.c b/src/firejail/dhcp.c new file mode 100644 index 000000000..c9bbb4d8f --- /dev/null +++ b/src/firejail/dhcp.c | |||
@@ -0,0 +1,142 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2019 Firejail Authors | ||
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 <sys/types.h> | ||
22 | #include <sys/wait.h> | ||
23 | #include <errno.h> | ||
24 | #include <stddef.h> | ||
25 | #include <stdio.h> | ||
26 | #include <string.h> | ||
27 | |||
28 | pid_t dhclient4_pid = 0; | ||
29 | pid_t dhclient6_pid = 0; | ||
30 | |||
31 | typedef struct { | ||
32 | char *version_arg; | ||
33 | char *pid_file; | ||
34 | char *leases_file; | ||
35 | uint8_t generate_duid; | ||
36 | char *duid_leases_file; | ||
37 | pid_t *pid; | ||
38 | ptrdiff_t arg_offset; | ||
39 | } Dhclient; | ||
40 | |||
41 | static const Dhclient dhclient4 = { .version_arg = "-4", | ||
42 | .pid_file = RUN_DHCLIENT_4_PID_FILE, | ||
43 | .leases_file = RUN_DHCLIENT_4_LEASES_FILE, | ||
44 | .generate_duid = 1, | ||
45 | .pid = &dhclient4_pid, | ||
46 | .arg_offset = offsetof(Bridge, arg_ip_dhcp) | ||
47 | }; | ||
48 | |||
49 | static const Dhclient dhclient6 = { .version_arg = "-6", | ||
50 | .pid_file = RUN_DHCLIENT_6_PID_FILE, | ||
51 | .leases_file = RUN_DHCLIENT_6_LEASES_FILE, | ||
52 | .duid_leases_file = RUN_DHCLIENT_4_LEASES_FILE, | ||
53 | .pid = &dhclient6_pid, | ||
54 | .arg_offset = offsetof(Bridge, arg_ip6_dhcp) | ||
55 | }; | ||
56 | |||
57 | static void dhcp_run_dhclient(const Dhclient *client) { | ||
58 | char *argv[256] = { "dhclient", | ||
59 | client->version_arg, | ||
60 | "-pf", client->pid_file, | ||
61 | "-lf", client->leases_file, | ||
62 | }; | ||
63 | int i = 6; | ||
64 | if (client->generate_duid) | ||
65 | argv[i++] = "-i"; | ||
66 | if (client->duid_leases_file) { | ||
67 | argv[i++] = "-df"; | ||
68 | argv[i++] = client->duid_leases_file; | ||
69 | } | ||
70 | if (arg_debug) | ||
71 | argv[i++] = "-v"; | ||
72 | if (*(uint8_t *) ((char *) &cfg.bridge0 + client->arg_offset)) | ||
73 | argv[i++] = cfg.bridge0.devsandbox; | ||
74 | if (*(uint8_t *) ((char *) &cfg.bridge1 + client->arg_offset)) | ||
75 | argv[i++] = cfg.bridge1.devsandbox; | ||
76 | if (*(uint8_t *) ((char *) &cfg.bridge2 + client->arg_offset)) | ||
77 | argv[i++] = cfg.bridge2.devsandbox; | ||
78 | if (*(uint8_t *) ((char *) &cfg.bridge3 + client->arg_offset)) | ||
79 | argv[i++] = cfg.bridge3.devsandbox; | ||
80 | |||
81 | sbox_run_v(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_CAPS_NET_SERVICE | SBOX_SECCOMP, argv); | ||
82 | } | ||
83 | |||
84 | static pid_t dhcp_read_pidfile(const Dhclient *client) { | ||
85 | // We have to run dhclient as a forking daemon (not pass the -d option), | ||
86 | // because we want to be notified of a successful DHCP lease by the parent process exit. | ||
87 | // However, try to be extra paranoid with race conditions, | ||
88 | // because dhclient only writes the daemon pid into the pidfile | ||
89 | // after its parent process has exited. | ||
90 | int tries = 0; | ||
91 | pid_t found = 0; | ||
92 | while (found == 0 && tries < 10) { | ||
93 | if (tries >= 1) | ||
94 | usleep(100000); | ||
95 | FILE *pidfile = fopen(client->pid_file, "r"); | ||
96 | if (pidfile) { | ||
97 | long pid; | ||
98 | if (fscanf(pidfile, "%ld", &pid) == 1) { | ||
99 | char *pidname = pid_proc_comm((pid_t) pid); | ||
100 | if (pidname && strcmp(pidname, "dhclient") == 0) | ||
101 | found = (pid_t) pid; | ||
102 | } | ||
103 | fclose(pidfile); | ||
104 | } | ||
105 | ++tries; | ||
106 | } | ||
107 | if (found == 0) { | ||
108 | fprintf(stderr, "Error: Cannot get dhclient %s PID from %s\n", | ||
109 | client->version_arg, client->pid_file); | ||
110 | exit(1); | ||
111 | } | ||
112 | return found; | ||
113 | } | ||
114 | |||
115 | static void dhcp_start_dhclient(const Dhclient *client) { | ||
116 | dhcp_run_dhclient(client); | ||
117 | *(client->pid) = dhcp_read_pidfile(client); | ||
118 | } | ||
119 | |||
120 | void dhcp_start(void) { | ||
121 | if (!any_dhcp()) | ||
122 | return; | ||
123 | |||
124 | EUID_ROOT(); | ||
125 | if (mkdir(RUN_DHCLIENT_DIR, 0700)) | ||
126 | errExit("mkdir"); | ||
127 | |||
128 | if (any_ip_dhcp()) { | ||
129 | dhcp_start_dhclient(&dhclient4); | ||
130 | if (arg_debug) | ||
131 | printf("Running dhclient -4 in the background as pid %ld\n", (long) dhclient4_pid); | ||
132 | } | ||
133 | if (any_ip6_dhcp()) { | ||
134 | dhcp_start_dhclient(&dhclient6); | ||
135 | if (arg_debug) | ||
136 | printf("Running dhclient -6 in the background as pid %ld\n", (long) dhclient6_pid); | ||
137 | if (dhclient4_pid == dhclient6_pid) { | ||
138 | fprintf(stderr, "Error: dhclient -4 and -6 have the same PID: %ld\n", (long) dhclient4_pid); | ||
139 | exit(1); | ||
140 | } | ||
141 | } | ||
142 | } | ||