diff --git a/Makefile.in b/Makefile.in index 31c2442ed20..4422cf8a980 100644 --- a/Makefile.in +++ b/Makefile.in @@ -27,12 +27,13 @@ COMPLETIONDIRS = src/zsh_completion src/bash_completion all: all_items mydirs $(MAN_TARGET) filters APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats src/jailcheck/jailcheck SBOX_APPS = src/fbuilder/fbuilder src/ftee/ftee src/fids/fids -SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter src/profstats/profstats +SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter +SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp +SBOX_APPS_NON_DUMPABLE += src/fnettrace/fnettrace MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS) MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 jailcheck.1 -SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32 ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS) diff --git a/configure b/configure index da886a541d6..a8c9a1d9602 100755 --- a/configure +++ b/configure @@ -4271,7 +4271,7 @@ fi ac_config_files="$ac_config_files mkdeb.sh" -ac_config_files="$ac_config_files Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile src/ftee/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile src/jailcheck/Makefile src/fids/Makefile" +ac_config_files="$ac_config_files Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile src/ftee/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile src/jailcheck/Makefile src/fids/Makefile src/fnettrace/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -5006,6 +5006,7 @@ do "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; "src/jailcheck/Makefile") CONFIG_FILES="$CONFIG_FILES src/jailcheck/Makefile" ;; "src/fids/Makefile") CONFIG_FILES="$CONFIG_FILES src/fids/Makefile" ;; + "src/fnettrace/Makefile") CONFIG_FILES="$CONFIG_FILES src/fnettrace/Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac diff --git a/configure.ac b/configure.ac index bf501506dca..232d49e1e9a 100644 --- a/configure.ac +++ b/configure.ac @@ -272,7 +272,7 @@ AC_CONFIG_FILES([Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/ src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \ src/ftee/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \ src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile \ -src/jailcheck/Makefile src/fids/Makefile]) +src/jailcheck/Makefile src/fids/Makefile src/fnettrace/Makefile]) AC_OUTPUT cat <next; -printf("data #%s#\n", prf->data); - if (prf->data) - free(prf->data); -printf("link #%s#\n", prf->link); - if (prf->link) - free(prf->link); - free(prf); - prf = next; - } - } -#endif if (WIFEXITED(status)){ diff --git a/src/firejail/netfilter.c b/src/firejail/netfilter.c index fc79dddec9d..f412950f217 100644 --- a/src/firejail/netfilter.c +++ b/src/firejail/netfilter.c @@ -24,6 +24,91 @@ #include #include +void netfilter_netlock(pid_t pid) { + EUID_ASSERT(); + + // give the sandbox a chance to start up before entering the network namespace + sleep(1); + enter_network_namespace(pid); + + char *flog; + if (asprintf(&flog, "/run/firejail/network/%d-netlock", getpid()) == -1) + errExit("asprintf"); + FILE *fp = fopen(flog, "w"); + if (!fp) + errExit("fopen"); + fclose(fp); + + // try to find a X terminal + char *terminal = NULL; + if (access("/usr/bin/lxterminal", X_OK) == 0) + terminal = "/usr/bin/lxterminal"; + else if (access("/usr/bin/xterm", X_OK) == 0) + terminal = "/usr/bin/xterm"; + else if (access("/usr/bin/xfce4-terminal", X_OK) == 0) + terminal = "/usr/bin/xfce4-terminal"; + else if (access("/usr/bin/konsole", X_OK) == 0) + terminal = "/usr/bin/konsole"; +// problem: newer gnome-terminal versions don't support -e command line option??? +// else if (access("/usr/bin/gnome-terminal", X_OK) == 0) +// terminal = "/usr/bin/gnome-terminal"; + + if (terminal) { + pid_t p = fork(); + if (p == -1) + ; // run without terminal logger + else if (p == 0) { // child + drop_privs(0); + + char *cmd; + if (asprintf(&cmd, "%s -e \"tail -f %s\"", terminal, flog) == -1) + errExit("asprintf"); + int rv = system(cmd); + (void) rv; + exit(0); + } + } + + char *cmd; + if (asprintf(&cmd, "%s/firejail/fnettrace --netfilter --log=%s", LIBDIR, flog) == -1) + errExit("asprintf"); + free(flog); + + //************************ + // build command + //************************ + char *arg[4]; + arg[0] = "/bin/sh"; + arg[1] = "-c"; + arg[2] = cmd; + arg[3] = NULL; + clearenv(); + sbox_exec_v(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_SECCOMP, arg); + // it will never get here!! +} + +void netfilter_trace(pid_t pid) { + EUID_ASSERT(); + + enter_network_namespace(pid); + char *cmd; + if (asprintf(&cmd, "%s/firejail/fnettrace", LIBDIR) == -1) + errExit("asprintf"); + + //************************ + // build command + //************************ + char *arg[4]; + arg[0] = "/bin/sh"; + arg[1] = "-c"; + arg[2] = cmd; + arg[3] = NULL; + + clearenv(); + sbox_exec_v(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_SECCOMP, arg); + // it will never get here!! +} + void check_netfilter_file(const char *fname) { EUID_ASSERT(); diff --git a/src/fnettrace/Makefile.in b/src/fnettrace/Makefile.in new file mode 100644 index 00000000000..755ddcc3a19 --- /dev/null +++ b/src/fnettrace/Makefile.in @@ -0,0 +1,17 @@ +.PHONY: all +all: fnettrace + +include ../common.mk + +%.o : %.c $(H_FILE_LIST) + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ + +fnettrace: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(EXTRA_LDFLAGS) + +.PHONY: clean +clean:; rm -fr *.o fnettrace *.gcov *.gcda *.gcno *.plist + +.PHONY: distclean +distclean: clean + rm -fr Makefile diff --git a/src/fnettrace/fnettrace.h b/src/fnettrace/fnettrace.h new file mode 100644 index 00000000000..9c34e17ca44 --- /dev/null +++ b/src/fnettrace/fnettrace.h @@ -0,0 +1,64 @@ +#ifndef FNETTRACE_H +#define FNETTRACE_H + +#include "../include/common.h" +#include +#include +#include +#include +#include +#include + +//#define NETLOCK_INTERVAL 60 +#define NETLOCK_INTERVAL 60 +#define DISPLAY_INTERVAL 3 + +void logprintf(char* fmt, ...); + +static inline void ansi_topleft(int tolog) { + char str[] = {0x1b, '[', '1', ';', '1', 'H', '\0'}; + if (tolog) + logprintf("%s", str); + else + printf("%s", str); + fflush(0); +} + +static inline void ansi_clrscr(int tolog) { + ansi_topleft(tolog); + char str[] = {0x1b, '[', '0', 'J', '\0'}; + if (tolog) + logprintf("%s", str); + else + printf("%s", str); + fflush(0); +} + +static inline void ansi_linestart(int tolog) { + char str[] = {0x1b, '[', '0', 'G', '\0'}; + if (tolog) + logprintf("%s", str); + else + printf("%s", str); + fflush(0); +} + +static inline void ansi_clrline(int tolog) { + ansi_linestart(tolog); + char str[] = {0x1b, '[', '0', 'K', '\0'}; + if (tolog) + logprintf("%s", str); + else + printf("%s", str); + fflush(0); +} + +static inline uint8_t hash(uint32_t ip) { + uint8_t *ptr = (uint8_t *) &ip; + // simple byte xor + return *ptr ^ *(ptr + 1) ^ *(ptr + 2) ^ *(ptr + 3); +} + + + +#endif \ No newline at end of file diff --git a/src/fnettrace/main.c b/src/fnettrace/main.c new file mode 100644 index 00000000000..f036d0c9ed4 --- /dev/null +++ b/src/fnettrace/main.c @@ -0,0 +1,433 @@ +#include "fnettrace.h" +#define MAX_BUF_SIZE (64 * 1024) + +static int arg_netfilter = 0; +static char *arg_log = NULL; + +typedef struct hlist_t { + struct hlist_t *next; + uint32_t ip_src; + uint32_t ip_dst; + uint16_t port_src; + uint64_t bytes; + int instance; +#define MAX_TTL 20 // 20 * DISPLAY_INTERVAL = 1 minute + short ttl; + uint8_t protocol; +} HList; + +#define HMAX 256 +HList *htable[HMAX] = {NULL}; +static int htable_empty = 1; + +static void hlist_add(uint32_t ip_src, uint32_t ip_dst, uint8_t protocol, uint16_t port_src, uint64_t bytes) { + uint8_t h = hash(ip_src); + htable_empty = 0; + + // find + int instance = 0; + HList *ptr = htable[h]; + while (ptr) { + if (ptr->ip_src == ip_src) { + instance++; + if (ptr->ip_dst == ip_dst && ptr->port_src == port_src && ptr->protocol == protocol) { + ptr->bytes += bytes; + ptr->ttl = MAX_TTL; + return; + } + } + ptr = ptr->next; + } + + HList *hnew = malloc(sizeof(HList)); + hnew->ip_src = ip_src; + hnew->ip_dst = ip_dst; + hnew->port_src = port_src; + hnew->protocol = protocol; + hnew->next = NULL; + hnew->bytes = bytes; + hnew->ttl = MAX_TTL; + hnew->instance = instance + 1; + if (htable[h] == NULL) + htable[h] = hnew; + else { + hnew->next = htable[h]; + htable[h] = hnew; + } + + ansi_clrline(1); + logprintf(" %u.%u.%u.%u\n", PRINT_IP(hnew->ip_src)); +} + +// remove entries with a ttl <= 0 +static void hlist_clean_ttl() { + if (htable_empty) + return; + + int i; + for (i = 0; i < HMAX; i++) { + HList *ptr = htable[i]; + HList *parent = NULL; + while (ptr) { + if (--ptr->ttl <= 0) { + HList *tmp = ptr; + ptr = ptr->next; + if (parent) + parent->next = ptr; + else + htable[i] = ptr; + free(tmp); + } + else { + parent = ptr; + ptr = ptr->next; + } + } + } +} + +static void hlist_print() { + ansi_clrscr(0); + if (htable_empty) + return; + if (arg_netfilter) + printf("\n\n"); + static int clear_cnt = 0; + + int i; + int cnt = 0; + int cnt_printed = 0; + for (i = 0; i < HMAX; i++) { + HList *ptr = htable[i]; + while (ptr) { + if (ptr->bytes) { + cnt_printed++; + char ip_src[30]; + sprintf(ip_src, "%u.%u.%u.%u:%u", PRINT_IP(ptr->ip_src), ptr->port_src); + char ip_dst[30]; + sprintf(ip_dst, "%u.%u.%u.%u", PRINT_IP(ptr->ip_dst)); + printf("%-25s => %-25s\t%s:", + ip_src, + ip_dst, + (ptr->protocol == 6)? "TCP": "UDP"); + + if (ptr->bytes > (DISPLAY_INTERVAL * 1024 * 2)) // > 2 KB/second + printf(" %lu KB/sec\n", + ptr->bytes / (DISPLAY_INTERVAL * 1024)); + else + printf(" %lu B/sec\n", + ptr->bytes / DISPLAY_INTERVAL); + ptr->bytes = 0; + } + + ptr = ptr->next; + cnt++; + } + } + + if (cnt_printed < 7) { + for (i = 0; i < 7 - cnt_printed; i++) + printf("\n"); + } + + if (!arg_netfilter) { + printf("(%d %s in the last one minute)\n", cnt, (cnt == 1)? "stream": "streams"); + hlist_clean_ttl(); + } +} + +static void run_trace(void) { + logprintf("accumulating traffic for %d seconds...\n", NETLOCK_INTERVAL); + + // trace only rx ipv4 tcp and upd + int s1 = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); + int s2 = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + if (s1 < 0 || s2 < 0) + errExit("socket"); + + unsigned start = time(NULL); + unsigned last_print_traces = 0; + unsigned last_print_remaining = 0; + unsigned char buf[MAX_BUF_SIZE]; + int progress_cnt = 0; + while (1) { + unsigned end = time(NULL); + if (arg_netfilter && end - start >= NETLOCK_INTERVAL) { + ansi_clrline(1); + break; + } + if (end % DISPLAY_INTERVAL == 1 && last_print_traces != end) { // first print after 1 second + hlist_print(); + last_print_traces = end; + } + if (arg_netfilter && last_print_remaining != end) { + ansi_clrline(1); + int secs = NETLOCK_INTERVAL - (end - start); + logprintf("%d %s remaining ", secs, (secs == 1)? "second": "seconds"); + last_print_remaining = end; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(s1, &rfds); + FD_SET(s2, &rfds); + int maxfd = (s1 > s2) ? s1 : s2; + maxfd++; + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + int rv = select(maxfd, &rfds, NULL, NULL, &tv); + if (rv < 0) + errExit("select"); + else if (rv == 0) + continue; + + + + int sock = (FD_ISSET(s1, &rfds)) ? s1 : s2; + + unsigned char buf[MAX_BUF_SIZE]; + unsigned bytes = recvfrom(sock, buf, MAX_BUF_SIZE, 0, NULL, NULL); + if (bytes >= 20) { // size of IP header + // filter out loopback traffic + if (buf[12] != 127) { + 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); + + uint8_t hlen = (buf[0] & 0x0f) * 4; + uint16_t port_src; + memcpy(&port_src, buf + hlen, 2); + port_src = ntohs(port_src); + + hlist_add(ip_src, ip_dst, buf[9], port_src, (uint64_t) bytes); + } + } + } + + close(s1); + close(s2); +} + +static char *filter_start = +"*filter\n" +":INPUT DROP [0:0]\n" +":FORWARD DROP [0:0]\n" +":OUTPUT DROP [0:0]\n"; + +// return 1 if error +static int print_filter(FILE *fp) { + if (htable_empty) + return 1; + fprintf(fp, "%s\n", filter_start); + fprintf(fp, "-A INPUT -s 127.0.0.0/8 -j ACCEPT\n"); + fprintf(fp, "-A OUTPUT -d 127.0.0.0/8 -j ACCEPT\n"); + fprintf(fp, "\n"); + + int i; + for (i = 0; i < HMAX; i++) { + HList *ptr = htable[i]; + while (ptr) { + if (ptr->instance == 1) { + char *protocol = (ptr->protocol == 6)? "tcp": "udp"; + fprintf(fp, "-A INPUT -s %u.%u.%u.%u -sport %u -p %s -j ACCEPT\n", + PRINT_IP(ptr->ip_src), + ptr->port_src, + protocol); + fprintf(fp, "-A OUTPUT -d %u.%u.%u.%u -dport %u -p %s -j ACCEPT\n", + PRINT_IP(ptr->ip_src), + ptr->port_src, + protocol); + fprintf(fp, "\n"); + } + ptr = ptr->next; + } + } + fprintf(fp, "COMMIT\n"); + + return 0; +} + +static char *flush_rules[] = { + "-P INPUT ACCEPT", + "-P FORWARD ACCEPT", + "-P OUTPUT ACCEPT", + "-F", + "-X", + "-t nat -F", + "-t nat -X", + "-t mangle -F", + "-t mangle -X", + "iptables -t raw -F", + "-t raw -X", + NULL +}; + +static void flush_netfilter(void) { + // find iptables command + struct stat s; + char *iptables = NULL; + if (stat("/sbin/iptables", &s) == 0) + iptables = "/sbin/iptables"; + else if (stat("/usr/sbin/iptables", &s) == 0) + iptables = "/usr/sbin/iptables"; + if (iptables == NULL) { + fprintf(stderr, "Error: iptables command not found, netfilter not configured\n"); + exit(1); + } + + int i = 0; + while (flush_rules[i]) { + char *cmd; + if (asprintf(&cmd, "%s %s", iptables, flush_rules[i]) == -1) + errExit("asprintf"); + int rv = system(cmd); + (void) rv; + free(cmd); + i++; + } +} + +static void deploy_netfilter(void) { + int rv; + char *cmd; + + // create temporary file + char fname[] = "/tmp/firejail-XXXXXX"; + int fd = mkstemp(fname); + if (fd == -1) { + fprintf(stderr, "Error: cannot create temporary configuration file\n"); + exit(1); + } + + FILE* fp = fdopen(fd, "w"); + if (!fp) { + rv = unlink(fname); + (void) rv; + fprintf(stderr, "Error: cannot create temporary configuration file\n"); + exit(1); + } + print_filter(fp); + fclose(fp); + + if (arg_log) { + logprintf("\n"); + logprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + if (asprintf(&cmd, "cat %s >> %s", fname, arg_log) == -1) + errExit("asprintf"); + rv = system(cmd); + (void) rv; + free(cmd); + logprintf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); + } + + // find iptables command + struct stat s; + char *iptables = NULL; + char *iptables_restore = NULL; + if (stat("/sbin/iptables", &s) == 0) { + iptables = "/sbin/iptables"; + iptables_restore = "/sbin/iptables-restore"; + } + else if (stat("/usr/sbin/iptables", &s) == 0) { + iptables = "/usr/sbin/iptables"; + iptables_restore = "/usr/sbin/iptables-restore"; + } + if (iptables == NULL || iptables_restore == NULL) { + fprintf(stderr, "Error: iptables command not found, netfilter not configured\n"); + rv = unlink(fname); + (void) rv; + exit(1); + } + + // configuring + if (asprintf(&cmd, "%s %s", iptables_restore, fname) == -1) + errExit("asprintf"); + rv = system(cmd); + if (rv) + fprintf(stdout, "Warning: possible netfilter problem!"); + free(cmd); + + sleep(1); + if (asprintf(&cmd, "%s %s", iptables_restore, fname) == -1) + errExit("asprintf"); + rv = system(cmd); + free(cmd); + + printf("Current firewall configuration:\n\n"); + if (asprintf(&cmd, "%s -vL -n", iptables) == -1) + errExit("asprintf"); + rv = system(cmd); + + rv = unlink(fname); + (void) rv; + logprintf("\nfirewall deployed\n"); +} + +void logprintf(char* fmt, ...) { + if (!arg_log) + return; + + FILE *fp = fopen(arg_log, "a"); + if (fp) { // disregard if error + va_list args; + va_start(args,fmt); + vfprintf(fp, fmt, args); + va_end(args); + fclose(fp); + } +} + +static void usage(void) { + printf("Usage: fnetlock [OPTIONS]\n"); + printf("Options:\n"); + printf(" --help, -? - this help screen\n"); + printf(" --netfilter - build the firewall rules and commit them.\n"); + printf(" --log=filename - logfile\n"); + printf("\n"); +} + +int main(int argc, char **argv) { + int i; + printf("\n\n"); + + if (getuid() != 0) { + fprintf(stderr, "Error: you need to be root to run this program\n"); + return 1; + } + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0) { + usage(); + return 0; + } + else if (strcmp(argv[i], "--netfilter") == 0) + arg_netfilter = 1; + else if (strncmp(argv[i], "--log=", 6) == 0) + arg_log = argv[i] + 6; + else { + fprintf(stderr, "Error: invalid argument\n"); + return 1; + } + } + + if (arg_netfilter) { + logprintf("starting network lockdown\n"); + flush_netfilter(); + } + + ansi_clrscr(0); + run_trace(); + if (arg_netfilter) { + deploy_netfilter(); + sleep(3); + if (arg_log) + unlink(arg_log); + } + + return 0; +}