From a50e86844715442008ef75aff0d466e19e473f04 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 26 Dec 2019 19:08:00 +0100 Subject: Add --ip=dhcp and --ip6=dhcp options Currently, --ip=dhcp is equivalent to --ip=none and --ip6=dhcp does nothing either, except for parsing correctly --- src/firejail/firejail.h | 2 ++ src/firejail/main.c | 27 +++++++++++++++++---------- src/firejail/profile.c | 30 ++++++++++++++++++------------ 3 files changed, 37 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 03bcbda46..a5eeb4067 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -103,6 +103,8 @@ typedef struct bridge_t { // flags uint8_t arg_ip_none; // --ip=none + uint8_t arg_ip_dhcp; + uint8_t arg_ip6_dhcp; uint8_t macvlan; // set by --net=eth0 (or eth1, ...); reset by --net=br0 (or br1, ...) uint8_t configured; uint8_t scan; // set by --scan diff --git a/src/firejail/main.c b/src/firejail/main.c index 179f8ddf9..0b9ebc482 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -2144,7 +2144,10 @@ int main(int argc, char **argv) { // configure this IP address for the last bridge defined if (strcmp(argv[i] + 5, "none") == 0) br->arg_ip_none = 1; - else { + else if (strcmp(argv[i] + 5, "dhcp") == 0) { + br->arg_ip_none = 1; + br->arg_ip_dhcp = 1; + } else { if (atoip(argv[i] + 5, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); @@ -2184,20 +2187,24 @@ int main(int argc, char **argv) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } - if (br->ip6sandbox) { + if (br->arg_ip6_dhcp || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined - if (check_ip46_address(argv[i] + 6) == 0) { - fprintf(stderr, "Error: invalid IPv6 address\n"); - exit(1); - } - - br->ip6sandbox = strdup(argv[i] + 6); - if (br->ip6sandbox == NULL) - errExit("strdup"); + if (strcmp(argv[i] + 6, "dhcp") == 0) + br->arg_ip6_dhcp = 1; + else { + if (check_ip46_address(argv[i] + 6) == 0) { + fprintf(stderr, "Error: invalid IPv6 address\n"); + exit(1); + } + + br->ip6sandbox = strdup(argv[i] + 6); + if (br->ip6sandbox == NULL) + errExit("strdup"); + } } else exit_err_feature("networking"); diff --git a/src/firejail/profile.c b/src/firejail/profile.c index 9a724331b..959678501 100644 --- a/src/firejail/profile.c +++ b/src/firejail/profile.c @@ -672,7 +672,10 @@ int profile_check_line(char *ptr, int lineno, const char *fname) { // configure this IP address for the last bridge defined if (strcmp(ptr + 3, "none") == 0) br->arg_ip_none = 1; - else { + else if (strcmp(ptr + 3, "dhcp") == 0) { + br->arg_ip_none = 1; + br->arg_ip_dhcp = 1; + } else { if (atoip(ptr + 3, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); @@ -693,21 +696,24 @@ int profile_check_line(char *ptr, int lineno, const char *fname) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } - if (br->ip6sandbox) { + if (br->arg_ip6_dhcp || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } - // configure this IP address for the last bridge defined - if (check_ip46_address(ptr + 4) == 0) { - fprintf(stderr, "Error: invalid IPv6 address\n"); - exit(1); - } - - br->ip6sandbox = strdup(ptr + 4); - if (br->ip6sandbox == NULL) - errExit("strdup"); - + // configure this IP address for the last bridge defined + if (strcmp(ptr + 4, "dhcp") == 0) + br->arg_ip6_dhcp = 1; + else { + if (check_ip46_address(ptr + 4) == 0) { + fprintf(stderr, "Error: invalid IPv6 address\n"); + exit(1); + } + + br->ip6sandbox = strdup(ptr + 4); + if (br->ip6sandbox == NULL) + errExit("strdup"); + } } else warning_feature_disabled("networking"); -- cgit v1.2.3-54-g00ecf From 40d6081485d6f3e39f325cede1cef48f225c9cdc Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 26 Dec 2019 20:58:39 +0100 Subject: Do not try to set up default gateway without an IP address If the container has no IP address (because it will be assigned via DHCP later), setting up a default route fails with a warning message. While this is harmless, the default route should be omitted instead. --- src/firejail/network_main.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/firejail/network_main.c b/src/firejail/network_main.c index 6800bde8d..ad297efc7 100644 --- a/src/firejail/network_main.c +++ b/src/firejail/network_main.c @@ -246,6 +246,10 @@ void net_check_cfg(void) { if (cfg.defaultgw) check_default_gw(cfg.defaultgw); else { + // if the first network has no assigned address, + // do not try to set up a gateway, because it will fail + if (cfg.bridge0.arg_ip_none) + return; // first network is a regular bridge if (cfg.bridge0.macvlan == 0) cfg.defaultgw = cfg.bridge0.ip; -- cgit v1.2.3-54-g00ecf From d3d806ddb25249cf0c404904b3f5ffc7011204ce Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 27 Dec 2019 18:41:29 +0100 Subject: Allow resolv.conf be written by dhclient When dhclient is used to assign and IP to the container, it should be able to overwrite resolv.conf Therefore, we do the /etc mirroring similarly to the situation with manually configured nameservers. When both DHCP and manually set nameservers are in use, warn that the manual ones will be overwritten --- src/firejail/firejail.h | 18 ++++++++++++++++++ src/firejail/fs_hostname.c | 10 +++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index a5eeb4067..bfe680d24 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -239,6 +239,24 @@ static inline int any_interface_configured(void) { return 0; } +static inline int any_ip_dhcp(void) { + if (cfg.bridge0.arg_ip_dhcp || cfg.bridge1.arg_ip_dhcp || cfg.bridge2.arg_ip_dhcp || cfg.bridge3.arg_ip_dhcp) + return 1; + else + return 0; +} + +static inline int any_ip6_dhcp(void) { + if (cfg.bridge0.arg_ip6_dhcp || cfg.bridge1.arg_ip6_dhcp || cfg.bridge2.arg_ip6_dhcp || cfg.bridge3.arg_ip6_dhcp) + return 1; + else + return 0; +} + +static inline int any_dhcp(void) { + return any_ip_dhcp() || any_ip6_dhcp(); +} + extern int arg_private; // mount private /home extern int arg_private_cache; // private home/.cache extern int arg_debug; // print debug messages diff --git a/src/firejail/fs_hostname.c b/src/firejail/fs_hostname.c index 9da01b24c..60c65746b 100644 --- a/src/firejail/fs_hostname.c +++ b/src/firejail/fs_hostname.c @@ -89,7 +89,7 @@ errexit: } void fs_resolvconf(void) { - if (cfg.dns1 == NULL) + if (cfg.dns1 == NULL && !any_dhcp()) return; if (arg_debug) @@ -108,7 +108,8 @@ void fs_resolvconf(void) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // for resolv.conf we create a brand new file - if (strcmp(entry->d_name, "resolv.conf") == 0) + if (strcmp(entry->d_name, "resolv.conf") == 0 || + strcmp(entry->d_name, "resolv.conf.dhclient-new") == 0) continue; // printf("linking %s\n", entry->d_name); @@ -169,8 +170,11 @@ void fs_resolvconf(void) { exit(1); } - if (cfg.dns1) + if (cfg.dns1) { + if (any_dhcp()) + fwarning("network setup uses DHCP, nameservers will likely be overwritten\n"); fprintf(fp, "nameserver %s\n", cfg.dns1); + } if (cfg.dns2) fprintf(fp, "nameserver %s\n", cfg.dns2); if (cfg.dns3) -- cgit v1.2.3-54-g00ecf From 8dd73b29fd99aedf9000e9e0c3278de8cf89ac5d Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 27 Dec 2019 21:13:34 +0100 Subject: Add sbox_run_v to run programs with explicit argument lists Refactored sbox_run to pass the varargs argument list as an array to an auxiliary function. The auxiliary function allows running programs with dynamically built argument lists. --- src/firejail/firejail.h | 1 + src/firejail/sbox.c | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index bfe680d24..0311968c3 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -815,6 +815,7 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc, // run sbox int sbox_run(unsigned filter, int num, ...); +int sbox_run_v(unsigned filter, char * const arg[]); // run_files.c void delete_run_files(pid_t pid); diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c index e5739ecb5..a90cb7668 100644 --- a/src/firejail/sbox.c +++ b/src/firejail/sbox.c @@ -105,23 +105,34 @@ static struct sock_fprog prog = { }; int sbox_run(unsigned filtermask, int num, ...) { - EUID_ROOT(); - - int i; va_list valist; va_start(valist, num); // build argument list - char *arg[num + 1]; + char **arg = malloc((num + 1) * sizeof(char *)); + int i; for (i = 0; i < num; i++) arg[i] = va_arg(valist, char*); arg[i] = NULL; va_end(valist); + int status = sbox_run_v(filtermask, arg); + + free(arg); + + return status; +} + +int sbox_run_v(unsigned filtermask, char * const arg[]) { + EUID_ROOT(); + if (arg_debug) { printf("sbox run: "); - for (i = 0; i <= num; i++) + int i = 0; + while (arg[i]) { printf("%s ", arg[i]); + i++; + } printf("\n"); } @@ -171,7 +182,7 @@ int sbox_run(unsigned filtermask, int num, ...) { // close all other file descriptors int max = 20; // getdtablesize() is overkill for a firejail process - for (i = 3; i < max; i++) + for (int i = 3; i < max; i++) close(i); // close open files umask(027); -- cgit v1.2.3-54-g00ecf From 02d09e86293be87768e6f93560e012e4a02e8666 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 29 Dec 2019 23:19:15 +0100 Subject: Add capability filter for network services, additive filter The new capability filter SBOX_CAPS_NET_SERVICE allows forked processes to bind to low ports (privileged network services). Because dhcp clients require both low ports and network administration privileges, this patch also allows (bitwise) combination of capability filters (except SBOX_CAPS_NONE, which completely drops any capabilities) to grant both SBOX_CAPS_NETWORK and SBOX_CAPS_NET_SERVICE to a dhcp client. This way, fnet and fnetfilter calls still do not get CAP_NET_BIND_SERVICE. --- src/firejail/sbox.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c index a90cb7668..a1e65cd3c 100644 --- a/src/firejail/sbox.c +++ b/src/firejail/sbox.c @@ -190,23 +190,34 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) { // apply filters if (filtermask & SBOX_CAPS_NONE) { caps_drop_all(); - } - else if (filtermask & SBOX_CAPS_NETWORK) { + } else { + uint64_t set = 0; + if (filtermask & SBOX_CAPS_NETWORK) { #ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files - uint64_t set = ((uint64_t) 1) << CAP_NET_ADMIN; - set |= ((uint64_t) 1) << CAP_NET_RAW; - caps_set(set); + set |= ((uint64_t) 1) << CAP_NET_ADMIN; + set |= ((uint64_t) 1) << CAP_NET_RAW; #endif - } - else if (filtermask & SBOX_CAPS_HIDEPID) { + } + if (filtermask & SBOX_CAPS_HIDEPID) { #ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files - uint64_t set = ((uint64_t) 1) << CAP_SYS_PTRACE; - set |= ((uint64_t) 1) << CAP_SYS_PACCT; - caps_set(set); + set |= ((uint64_t) 1) << CAP_SYS_PTRACE; + set |= ((uint64_t) 1) << CAP_SYS_PACCT; #endif - } + } + if (filtermask & SBOX_CAPS_NET_SERVICE) { +#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files + set |= ((uint64_t) 1) << CAP_NET_BIND_SERVICE; + set |= ((uint64_t) 1) << CAP_NET_BROADCAST; +#endif + } + if (set != 0) { // some SBOX_CAPS_ flag was specified, drop all other capabilities +#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files + caps_set(set); +#endif + } + } - if (filtermask & SBOX_SECCOMP) { + if (filtermask & SBOX_SECCOMP) { if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("prctl(NO_NEW_PRIVS)"); } -- cgit v1.2.3-54-g00ecf From ce3c1988578f6b18488a91132d355cf13a37e522 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Mon, 30 Dec 2019 00:04:04 +0100 Subject: 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. --- src/firejail/dhcp.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++ src/firejail/firejail.h | 6 ++ src/firejail/sandbox.c | 7 +++ src/include/rundefs.h | 5 ++ 4 files changed, 160 insertions(+) create mode 100644 src/firejail/dhcp.c (limited to 'src') 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 @@ +/* + * Copyright (C) 2014-2019 Firejail Authors + * + * This file is part of firejail project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "firejail.h" +#include +#include +#include +#include +#include +#include + +pid_t dhclient4_pid = 0; +pid_t dhclient6_pid = 0; + +typedef struct { + char *version_arg; + char *pid_file; + char *leases_file; + uint8_t generate_duid; + char *duid_leases_file; + pid_t *pid; + ptrdiff_t arg_offset; +} Dhclient; + +static const Dhclient dhclient4 = { .version_arg = "-4", + .pid_file = RUN_DHCLIENT_4_PID_FILE, + .leases_file = RUN_DHCLIENT_4_LEASES_FILE, + .generate_duid = 1, + .pid = &dhclient4_pid, + .arg_offset = offsetof(Bridge, arg_ip_dhcp) +}; + +static const Dhclient dhclient6 = { .version_arg = "-6", + .pid_file = RUN_DHCLIENT_6_PID_FILE, + .leases_file = RUN_DHCLIENT_6_LEASES_FILE, + .duid_leases_file = RUN_DHCLIENT_4_LEASES_FILE, + .pid = &dhclient6_pid, + .arg_offset = offsetof(Bridge, arg_ip6_dhcp) +}; + +static void dhcp_run_dhclient(const Dhclient *client) { + char *argv[256] = { "dhclient", + client->version_arg, + "-pf", client->pid_file, + "-lf", client->leases_file, + }; + int i = 6; + if (client->generate_duid) + argv[i++] = "-i"; + if (client->duid_leases_file) { + argv[i++] = "-df"; + argv[i++] = client->duid_leases_file; + } + if (arg_debug) + argv[i++] = "-v"; + if (*(uint8_t *) ((char *) &cfg.bridge0 + client->arg_offset)) + argv[i++] = cfg.bridge0.devsandbox; + if (*(uint8_t *) ((char *) &cfg.bridge1 + client->arg_offset)) + argv[i++] = cfg.bridge1.devsandbox; + if (*(uint8_t *) ((char *) &cfg.bridge2 + client->arg_offset)) + argv[i++] = cfg.bridge2.devsandbox; + if (*(uint8_t *) ((char *) &cfg.bridge3 + client->arg_offset)) + argv[i++] = cfg.bridge3.devsandbox; + + sbox_run_v(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_CAPS_NET_SERVICE | SBOX_SECCOMP, argv); +} + +static pid_t dhcp_read_pidfile(const Dhclient *client) { + // We have to run dhclient as a forking daemon (not pass the -d option), + // because we want to be notified of a successful DHCP lease by the parent process exit. + // However, try to be extra paranoid with race conditions, + // because dhclient only writes the daemon pid into the pidfile + // after its parent process has exited. + int tries = 0; + pid_t found = 0; + while (found == 0 && tries < 10) { + if (tries >= 1) + usleep(100000); + FILE *pidfile = fopen(client->pid_file, "r"); + if (pidfile) { + long pid; + if (fscanf(pidfile, "%ld", &pid) == 1) { + char *pidname = pid_proc_comm((pid_t) pid); + if (pidname && strcmp(pidname, "dhclient") == 0) + found = (pid_t) pid; + } + fclose(pidfile); + } + ++tries; + } + if (found == 0) { + fprintf(stderr, "Error: Cannot get dhclient %s PID from %s\n", + client->version_arg, client->pid_file); + exit(1); + } + return found; +} + +static void dhcp_start_dhclient(const Dhclient *client) { + dhcp_run_dhclient(client); + *(client->pid) = dhcp_read_pidfile(client); +} + +void dhcp_start(void) { + if (!any_dhcp()) + return; + + EUID_ROOT(); + if (mkdir(RUN_DHCLIENT_DIR, 0700)) + errExit("mkdir"); + + if (any_ip_dhcp()) { + dhcp_start_dhclient(&dhclient4); + if (arg_debug) + printf("Running dhclient -4 in the background as pid %ld\n", (long) dhclient4_pid); + } + if (any_ip6_dhcp()) { + dhcp_start_dhclient(&dhclient6); + if (arg_debug) + printf("Running dhclient -6 in the background as pid %ld\n", (long) dhclient6_pid); + if (dhclient4_pid == dhclient6_pid) { + fprintf(stderr, "Error: dhclient -4 and -6 have the same PID: %ld\n", (long) dhclient4_pid); + exit(1); + } + } +} diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 0311968c3..4beae587e 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -812,6 +812,7 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc, #define SBOX_ALLOW_STDIN (1 << 5) // don't close stdin #define SBOX_STDIN_FROM_FILE (1 << 6) // open file and redirect it to stdin #define SBOX_CAPS_HIDEPID (1 << 7) // hidepid caps filter for running firemon +#define SBOX_CAPS_NET_SERVICE (1 << 8) // caps filter for programs running network services // run sbox int sbox_run(unsigned filter, int num, ...); @@ -827,4 +828,9 @@ void set_profile_run_file(pid_t pid, const char *fname); // dbus.c void dbus_disable(void); +// dhcp.c +extern pid_t dhclient4_pid; +extern pid_t dhclient6_pid; +void dhcp_start(void); + #endif diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 995e98f9f..bcd812ab2 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -337,6 +337,8 @@ static int monitor_application(pid_t app_pid) { continue; if (pid == 1) continue; + if (pid == dhclient4_pid || pid == dhclient6_pid) + continue; // todo: make this generic // Dillo browser leaves a dpid process running, we need to shut it down @@ -1015,6 +1017,11 @@ int sandbox(void* sandbox_arg) { fs_logger_print(); fs_logger_change_owner(); + //**************************** + // start dhcp client + //**************************** + dhcp_start(); + //**************************** // set application environment //**************************** diff --git a/src/include/rundefs.h b/src/include/rundefs.h index df135b9ca..8119f31e9 100644 --- a/src/include/rundefs.h +++ b/src/include/rundefs.h @@ -49,6 +49,11 @@ #define RUN_LIB_DIR RUN_MNT_DIR "/lib" #define RUN_LIB_FILE RUN_MNT_DIR "/libfiles" #define RUN_DNS_ETC RUN_MNT_DIR "/dns-etc" +#define RUN_DHCLIENT_DIR RUN_MNT_DIR "/dhclient" +#define RUN_DHCLIENT_4_LEASES_FILE RUN_DHCLIENT_DIR "/dhclient.leases" +#define RUN_DHCLIENT_6_LEASES_FILE RUN_DHCLIENT_DIR "/dhclient6.leases" +#define RUN_DHCLIENT_4_PID_FILE RUN_DHCLIENT_DIR "/dhclient.pid" +#define RUN_DHCLIENT_6_PID_FILE RUN_DHCLIENT_DIR "/dhclient6.pid" #define RUN_SECCOMP_DIR RUN_MNT_DIR "/seccomp" #define RUN_SECCOMP_LIST RUN_SECCOMP_DIR "/seccomp.list" // list of seccomp files installed -- cgit v1.2.3-54-g00ecf From c082d90be6396149404704e127f10ec7c9aa79ad Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Mon, 30 Dec 2019 20:56:03 +0100 Subject: Wait for link-local address for DHCPv6 dhclient -6 fails if the interface to be configures has no link-local address. This is especially problematic when only DHCPv6 is used (e.g., --ip=none --ip6=dhcp), because the wait for a DHCPv4 lease is usually ample time for the LL address to become available on the IPv6 link. The LL address must not be tenative. Therefore, this patch implements waiting for a non-tentative link-local address in fnet for DHCPv6 configured interfaces. The command fnet waitll waits for an LL address on the interface . Currently, the maximum waiting time is 30 seconds, and the kernel is polled through rtnetlink every 500 milliseconds. These values seem sufficient for virtual bridged networks, e.g., libvirt NAT networks. --- src/firejail/dhcp.c | 16 +++++++ src/fnet/fnet.h | 1 + src/fnet/interface.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/fnet/main.c | 4 ++ 4 files changed, 143 insertions(+) (limited to 'src') diff --git a/src/firejail/dhcp.c b/src/firejail/dhcp.c index c9bbb4d8f..7ce9a2b18 100644 --- a/src/firejail/dhcp.c +++ b/src/firejail/dhcp.c @@ -117,6 +117,21 @@ static void dhcp_start_dhclient(const Dhclient *client) { *(client->pid) = dhcp_read_pidfile(client); } +static void dhcp_waitll(const char *ifname) { + sbox_run(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_SECCOMP, 3, PATH_FNET, "waitll", ifname); +} + +static void dhcp_waitll_all() { + if (cfg.bridge0.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge0.devsandbox); + if (cfg.bridge1.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge1.devsandbox); + if (cfg.bridge2.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge2.devsandbox); + if (cfg.bridge3.arg_ip6_dhcp) + dhcp_waitll(cfg.bridge3.devsandbox); +} + void dhcp_start(void) { if (!any_dhcp()) return; @@ -131,6 +146,7 @@ void dhcp_start(void) { printf("Running dhclient -4 in the background as pid %ld\n", (long) dhclient4_pid); } if (any_ip6_dhcp()) { + dhcp_waitll_all(); dhcp_start_dhclient(&dhclient6); if (arg_debug) printf("Running dhclient -6 in the background as pid %ld\n", (long) dhclient6_pid); diff --git a/src/fnet/fnet.h b/src/fnet/fnet.h index 4900967f7..4d0d62b39 100644 --- a/src/fnet/fnet.h +++ b/src/fnet/fnet.h @@ -47,6 +47,7 @@ int net_get_mac(const char *ifname, unsigned char mac[6]); void net_if_ip(const char *ifname, uint32_t ip, uint32_t mask, int mtu); int net_if_mac(const char *ifname, const unsigned char mac[6]); void net_if_ip6(const char *ifname, const char *addr6); +void net_if_waitll(const char *ifname); // arp.c diff --git a/src/fnet/interface.c b/src/fnet/interface.c index 7e7cceeed..1c07ec8f7 100644 --- a/src/fnet/interface.c +++ b/src/fnet/interface.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include static void check_if_name(const char *ifname) { if (strlen(ifname) > IFNAMSIZ) { @@ -370,3 +372,123 @@ void net_if_ip6(const char *ifname, const char *addr6) { close(sock); } + +static int net_netlink_address_tentative(struct nlmsghdr *current_header) { + struct ifaddrmsg *msg = NLMSG_DATA(current_header); + struct rtattr *rta = IFA_RTA(msg); + size_t msg_len = IFA_PAYLOAD(current_header); + int has_flags = 0; + while (RTA_OK(rta, msg_len)) { + if (rta->rta_type == IFA_FLAGS) { + has_flags = 1; + uint32_t *flags = RTA_DATA(rta); + if (*flags & IFA_F_TENTATIVE) + return 1; + } + rta = RTA_NEXT(rta, msg_len); + } + // According to , if an IFA_FLAGS attribute is present, + // the field ifa_flags should be ignored. + return !has_flags && (msg->ifa_flags & IFA_F_TENTATIVE); +} + +static int net_netlink_if_has_ll(int sock, int index) { + struct { + struct nlmsghdr header; + struct ifaddrmsg message; + } req; + memset(&req, 0, sizeof(req)); + req.header.nlmsg_len = NLMSG_LENGTH(sizeof(req.message)); + req.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.header.nlmsg_type = RTM_GETADDR; + req.message.ifa_family = AF_INET6; + if (send(sock, &req, req.header.nlmsg_len, 0) != req.header.nlmsg_len) + errExit("send"); + + int found = 0; + int all_parts_processed = 0; + while (!all_parts_processed) { + char buf[16384]; + ssize_t len = recv(sock, buf, sizeof(buf), 0); + if (len < 0) + errExit("recv"); + if (len < sizeof(struct nlmsghdr)) { + fprintf(stderr, "Received incomplete netlink message\n"); + exit(1); + } + + struct nlmsghdr *current_header = (struct nlmsghdr *) buf; + while (NLMSG_OK(current_header, len)) { + switch (current_header->nlmsg_type) { + case RTM_NEWADDR: { + struct ifaddrmsg *msg = NLMSG_DATA(current_header); + if (!found && msg->ifa_index == index && msg->ifa_scope == RT_SCOPE_LINK && + !net_netlink_address_tentative(current_header)) + found = 1; + } + break; + case NLMSG_NOOP: + break; + case NLMSG_DONE: + all_parts_processed = 1; + break; + case NLMSG_ERROR: { + struct nlmsgerr *err = NLMSG_DATA(current_header); + fprintf(stderr, "Netlink error: %d\n", err->error); + exit(1); + } + break; + default: + fprintf(stderr, "Unknown netlink message type: %u\n", current_header->nlmsg_type); + exit(1); + break; + } + + current_header = NLMSG_NEXT(current_header, len); + } + } + + return found; +} + +// wait for a link-local IPv6 address for DHCPv6 +// ex: firejail --net=br0 --ip6=dhcp +void net_if_waitll(const char *ifname) { + // find interface index + int inet6_sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if (inet6_sock < 0) { + fprintf(stderr, "Error fnet: IPv6 is not supported on this system\n"); + exit(1); + } + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(inet6_sock, SIOGIFINDEX, &ifr) < 0) { + perror("ioctl SIOGIFINDEX"); + exit(1); + } + close(inet6_sock); + int index = ifr.ifr_ifindex; + + // poll for link-local address + int netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (netlink_sock < 0) + errExit("socket"); + int tries = 0; + int found = 0; + while (tries < 60 && !found) { + if (tries >= 1) + usleep(500000); + + found = net_netlink_if_has_ll(netlink_sock, index); + + tries++; + } + close(netlink_sock); + + if (!found) { + fprintf(stderr, "Waiting for link-local IPv6 address of %s timed out\n", ifname); + exit(1); + } +} diff --git a/src/fnet/main.c b/src/fnet/main.c index 890f842f6..3ef500b5e 100644 --- a/src/fnet/main.c +++ b/src/fnet/main.c @@ -47,6 +47,7 @@ static void usage(void) { printf("\tfnet config mac addr\n"); printf("\tfnet config ipv6 dev ip\n"); printf("\tfnet ifup dev\n"); + printf("\tfnet waitll dev\n"); } int main(int argc, char **argv) { @@ -141,6 +142,9 @@ printf("\n"); else if (argc == 5 && strcmp(argv[1], "config") == 0 && strcmp(argv[2], "ipv6") == 0) { net_if_ip6(argv[3], argv[4]); } + else if (argc == 3 && strcmp(argv[1], "waitll") == 0) { + net_if_waitll(argv[2]); + } else { fprintf(stderr, "Error fnet: invalid arguments\n"); return 1; -- cgit v1.2.3-54-g00ecf