From 4dd1e92ba1c0687d3f5860ccc58c80d28c8905b8 Mon Sep 17 00:00:00 2001 From: netblue30 Date: Tue, 18 Jan 2022 14:14:44 -0500 Subject: nettrace fixes --- src/firejail/main.c | 34 ++++++++++++++++++---- src/firejail/netfilter.c | 5 +++- src/fnettrace/fnettrace.h | 9 +++--- src/fnettrace/hostnames | 2 +- src/fnettrace/main.c | 72 +++++++++++++++++++++++++++++++++++++++++------ src/fnettrace/radix.c | 52 +++++++++++++++++++++++++--------- src/man/firejail.txt | 46 +++++++++++++++++++++++++----- 7 files changed, 177 insertions(+), 43 deletions(-) diff --git a/src/firejail/main.c b/src/firejail/main.c index 21a289efb..d614ae1ac 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -409,9 +409,22 @@ static void run_cmd_and_exit(int i, int argc, char **argv) { } #endif #ifdef HAVE_NETWORK + else if (strcmp(argv[i], "--nettrace") == 0) { + if (checkcfg(CFG_NETWORK)) { + netfilter_trace(0); + } + else + exit_err_feature("networking"); + exit(0); + } else if (strncmp(argv[i], "--nettrace=", 11) == 0) { - pid_t pid = require_pid(argv[i] + 11); - netfilter_trace(pid); + if (checkcfg(CFG_NETWORK)) { + pid_t pid = require_pid(argv[i] + 11); + netfilter_trace(pid); + } + else + exit_err_feature("networking"); + exit(0); } else if (strncmp(argv[i], "--bandwidth=", 12) == 0) { if (checkcfg(CFG_NETWORK)) { @@ -2316,11 +2329,20 @@ int main(int argc, char **argv, char **envp) { continue; } #ifdef HAVE_NETWORK - else if (strcmp(argv[i], "--netlock") == 0) - arg_netlock = 1; + else if (strcmp(argv[i], "--netlock") == 0) { + if (checkcfg(CFG_NETWORK)) + arg_netlock = 1; + else + exit_err_feature("networking"); + } else if (strncmp(argv[i], "--netlock=", 10) == 0) { - pid_t pid = require_pid(argv[i] + 10); - netfilter_netlock(pid); + if (checkcfg(CFG_NETWORK)) { + pid_t pid = require_pid(argv[i] + 10); + netfilter_netlock(pid); + } + else + exit_err_feature("networking"); + exit(0); } else if (strncmp(argv[i], "--interface=", 12) == 0) { if (checkcfg(CFG_NETWORK)) { diff --git a/src/firejail/netfilter.c b/src/firejail/netfilter.c index 61164d740..939ab29fa 100644 --- a/src/firejail/netfilter.c +++ b/src/firejail/netfilter.c @@ -93,7 +93,10 @@ void netfilter_netlock(pid_t pid) { void netfilter_trace(pid_t pid) { EUID_ASSERT(); - enter_network_namespace(pid); + // a pid of 0 means the main system network namespace + if (pid) + enter_network_namespace(pid); + char *cmd; if (asprintf(&cmd, "%s/firejail/fnettrace", LIBDIR) == -1) errExit("asprintf"); diff --git a/src/fnettrace/fnettrace.h b/src/fnettrace/fnettrace.h index 59b9618a9..66b7378da 100644 --- a/src/fnettrace/fnettrace.h +++ b/src/fnettrace/fnettrace.h @@ -29,11 +29,10 @@ #include //#define DEBUG 1 -//#define NETLOCK_INTERVAL 15 -#define NETLOCK_INTERVAL 60 -#define DISPLAY_INTERVAL 2 -#define DISPLAY_TTL 4 -#define DISPLAY_BW_UNITS 20 +#define NETLOCK_INTERVAL 60 // seconds +#define DISPLAY_INTERVAL 2 // seconds +#define DISPLAY_TTL 4 // display intervals (4 * 2 seconds) +#define DISPLAY_BW_UNITS 20 // length of the bandwidth bar static inline void ansi_topleft(void) { diff --git a/src/fnettrace/hostnames b/src/fnettrace/hostnames index a808a3b09..e24ecf218 100644 --- a/src/fnettrace/hostnames +++ b/src/fnettrace/hostnames @@ -20,7 +20,7 @@ # # Static Internet Map # -# Unfortunately, we cannot do a hostname lookup. This will leak a lot of +# Unfortunately we cannot do a hostname lookup. This will leak a lot of # information about what network resources we access. # A static map, helped out by geoip package available on all Linux distros, # will have to do it for now! diff --git a/src/fnettrace/main.c b/src/fnettrace/main.c index d5772328c..331311ee8 100644 --- a/src/fnettrace/main.c +++ b/src/fnettrace/main.c @@ -26,7 +26,7 @@ static int arg_netfilter = 0; static char *arg_log = NULL; typedef struct hnode_t { - struct hnode_t *hnext; // used for hash table + struct hnode_t *hnext; // used for hash table and unused linked list struct hnode_t *dnext; // used to display stremas on the screen uint32_t ip_src; uint32_t bytes; // number of bytes received in the last display interval @@ -45,6 +45,36 @@ HNode *htable[HMAX] = {NULL}; // display linked list HNode *dlist = NULL; + +// speed up malloc/free +#define HNODE_MAX_MALLOC 16 +static HNode *hnode_unused = NULL; +static int hnode_malloc_cnt = 0; +HNode *hmalloc(void) { + if (hnode_unused == NULL) { + hnode_unused = malloc(sizeof(HNode) * HNODE_MAX_MALLOC); + if (!hnode_unused) + errExit("malloc"); + memset(hnode_unused, 0, sizeof(HNode) * HNODE_MAX_MALLOC); + HNode *ptr = hnode_unused; + int i; + for ( i = 1; i < HNODE_MAX_MALLOC; i++, ptr++) + ptr->hnext = hnode_unused + i; + } + + HNode *rv = hnode_unused; + hnode_unused = hnode_unused->hnext; + return rv; +} + +void hfree(HNode *ptr) { + assert(ptr); + memset(ptr, 0, sizeof(HNode)); + ptr->hnext = hnode_unused; + hnode_unused = ptr; +} + + static void hnode_add(uint32_t ip_src, uint8_t protocol, uint16_t port_src, uint32_t bytes) { uint8_t h = hash(ip_src); @@ -65,9 +95,8 @@ static void hnode_add(uint32_t ip_src, uint8_t protocol, uint16_t port_src, uint #ifdef DEBUG printf("malloc %d.%d.%d.%d\n", PRINT_IP(ip_src)); #endif - HNode *hnew = malloc(sizeof(HNode)); - if (!hnew) - errExit("malloc"); + HNode *hnew = hmalloc(); + assert(hnew); hnew->hostname = NULL; hnew->ip_src = ip_src; hnew->port_src = port_src; @@ -117,7 +146,7 @@ static void hnode_free(HNode *elem) { htable[h] = elem->hnext; else prev->hnext = elem->hnext; - free(elem); + hfree(elem); } #ifdef DEBUG @@ -194,14 +223,15 @@ static unsigned adjust_bandwidth(unsigned bw) { static void hnode_print(unsigned bw) { assert(!arg_netfilter); - ansi_clrscr(); - + bw = (bw < 1024 * DISPLAY_INTERVAL)? 1024 * DISPLAY_INTERVAL: bw; #ifdef DEBUG printf("*********************\n"); debug_dlist(); printf("-----------------------------\n"); debug_hnode(); printf("*********************\n"); +#else + ansi_clrscr(); #endif // get terminal size @@ -291,6 +321,17 @@ static void hnode_print(unsigned bw) { ptr = next; } +#ifdef DEBUG + { + int cnt = 0; + HNode *ptr = hnode_unused; + while (ptr) { + cnt++; + ptr = ptr->hnext; + } + printf("hnode unused %d\n", cnt); + } +#endif } static void run_trace(void) { @@ -343,9 +384,22 @@ static void run_trace(void) { unsigned bytes = recvfrom(sock, buf, MAX_BUF_SIZE, 0, NULL, NULL); if (bytes >= 20) { // size of IP header - bw += bytes + 14; // assume a 14 byte Ethernet layer +#ifdef DEBUG + { + uint32_t ip_src; + memcpy(&ip_src, buf + 12, 4); + ip_src = ntohl(ip_src); + + uint32_t ip_dst; + memcpy(&ip_dst, buf + 16, 4); + ip_dst = ntohl(ip_dst); + printf("%d.%d.%d.%d -> %d.%d.%d.%d, %u bytes\n", PRINT_IP(ip_src), PRINT_IP(ip_dst), bytes); + } +#endif // filter out loopback traffic - if (buf[12] != 127) { + if (buf[12] != 127 && buf[16] != 127) { + bw += bytes + 14; // assume a 14 byte Ethernet layer + uint32_t ip_src; memcpy(&ip_src, buf + 12, 4); ip_src = ntohl(ip_src); diff --git a/src/fnettrace/radix.c b/src/fnettrace/radix.c index b9104bd39..c9493717d 100644 --- a/src/fnettrace/radix.c +++ b/src/fnettrace/radix.c @@ -34,18 +34,44 @@ typedef struct rnode_t { RNode *head = 0; int radix_nodes = 0; +// get rid of the malloc overhead +#define RNODE_MAX_MALLOC 128 +static RNode *rnode_unused = NULL; +static int rnode_malloc_cnt = 0; +static RNode *rmalloc(void) { + if (rnode_unused == NULL || rnode_malloc_cnt >= RNODE_MAX_MALLOC) { + rnode_unused = malloc(sizeof(RNode) * RNODE_MAX_MALLOC); + if (!rnode_unused) + errExit("malloc"); + memset(rnode_unused, 0, sizeof(RNode) * RNODE_MAX_MALLOC); + rnode_malloc_cnt = 0; + } + + rnode_malloc_cnt++; + return rnode_unused + rnode_malloc_cnt - 1; +} + + +static inline char *duplicate_name(const char *name) { + assert(name); + + if (strcmp(name, "United States") == 0) + return "United States"; + else if (strcmp(name, "Amazon") == 0) + return "Amazon"; + return strdup(name); +} + static inline RNode *addOne(RNode *ptr, char *name) { assert(ptr); if (ptr->one) return ptr->one; - RNode *node = malloc(sizeof(RNode)); - if (!node) - errExit("malloc"); - memset(node, 0, sizeof(RNode)); + RNode *node = rmalloc(); + assert(node); if (name) { - node->name = strdup(name); + node->name = duplicate_name(name); if (!node->name) - errExit("strdup"); + errExit("duplicate name"); } ptr->one = node; @@ -56,14 +82,12 @@ static inline RNode *addZero(RNode *ptr, char *name) { assert(ptr); if (ptr->zero) return ptr->zero; - RNode *node = malloc(sizeof(RNode)); - if (!node) - errExit("malloc"); - memset(node, 0, sizeof(RNode)); + RNode *node = rmalloc(); + assert(node); if (name) { - node->name = strdup(name); + node->name = duplicate_name(name); if (!node->name) - errExit("strdup"); + errExit("duplicate name"); } ptr->zero = node; @@ -97,9 +121,9 @@ char *radix_add(uint32_t ip, uint32_t mask, char *name) { } assert(ptr); if (!ptr->name) { - ptr->name = strdup(name); + ptr->name = duplicate_name(name); if (!ptr->name) - errExit("strdup"); + errExit("duplicate_name"); } return ptr->name; diff --git a/src/man/firejail.txt b/src/man/firejail.txt index a5704e995..9e3bce643 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -1462,6 +1462,28 @@ $ firejail --name=browser --net=eth0 --netfilter firefox & .br $ firejail --netfilter6.print=browser +.TP +\fB\-\-netlock=name/pid +Several type of programs (email clients, multiplayer games etc.) talk to a very small +number of IP addresses. But the best example is tor browser. It only talks to a guard node, +and there are two or three more on standby in case the main one fails. +During startup, the browser contacts all of them, after that it keeps talking to the main +one... for weeks! + +Use the network locking feature to build and deploy a network firewall in your sandbox. +The firewall allows only the network traffic to the IP addresses detected during the program +startup. Traffic to any other address is quietly dropped. By default the startup monitoring +time is one minute. Example: +.br + +.br +$ firejail --net=eth0 --netlock \\ +.br +--private=~/tor-browser_en-US ./start-tor-browser.desktop +.br + +.br + .TP \fB\-\-netmask=address Use this option when you want to assign an IP address in a new namespace and @@ -1500,25 +1522,35 @@ PID User RX(KB/s) TX(KB/s) Command .br 7383 netblue 9.045 0.112 firejail \-\-net=eth0 transmission .TP -\fB\-\-nettrace=name|pid +\fB\-\-nettrace[=name|pid] Monitor TCP and UDP traffic coming into the sandbox specified by name or pid. Only networked sandboxes created with \-\-net are supported. .br .br -$ firejail --nettrace=browser +Without a name/pid, Firejail will monitor the main system network namespace. +.br + +.br + $ firejail --nettrace=browser +.br + +.br + 95 KB/s geoip 457, IP database 4436 +.br + 52 KB/s *********** 64.222.84.207:443 United States .br - 86 KB/s ********* 64.222.84.207:443 United States + 33 KB/s ******* 89.147.74.105:63930 Hungary .br - 76 KB/s ******** 192.229.210.163:443 MCI + 0 B/s 45.90.28.0:443 NextDNS .br - 111 B/s 9.9.9.9:53 Quad9 DNS + 0 B/s 94.70.122.176:52309(UDP) Greece .br - 32 KB/s *** 142.250.179.182:443 Google + 339 B/s 104.26.7.35:443 Cloudflare .br .br -If /usr/bin/geoiplookup is installed (geoip-bin packet in Debian), +If /usr/bin/geoiplookup is installed (geoip-bin package in Debian), the country the IP address originates from is added to the trace. We also use the static IP map in /etc/firejail/hostnames to print the domain names for some of the more common websites and cloud platforms. -- cgit v1.2.3-54-g00ecf