split nettrace executable ^Cto netrace and netlock

This commit is contained in:
netblue30 2023-07-25 10:22:59 -04:00
parent 97d6993f35
commit 8e4b847cdd
6 changed files with 518 additions and 3 deletions

View file

@ -17,7 +17,7 @@ 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/fzenity/fzenity
SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp
SBOX_APPS_NON_DUMPABLE += src/fnettrace/fnettrace src/fnettrace-dns/fnettrace-dns src/fnettrace-sni/fnettrace-sni
SBOX_APPS_NON_DUMPABLE += src/fnettrace-icmp/fnettrace-icmp
SBOX_APPS_NON_DUMPABLE += src/fnettrace-icmp/fnettrace-icmp src/fnetlock/fnetlock
MYDIRS = src/lib $(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

View file

@ -65,7 +65,7 @@ void netfilter_netlock(pid_t pid) {
umask(orig_umask);
char *cmd;
if (asprintf(&cmd, "%s -e \"%s/firejail/fnettrace --tail --log=%s\"", terminal, LIBDIR, flog) == -1)
if (asprintf(&cmd, "%s -e \"%s/firejail/fnetlock --tail --log=%s\"", terminal, LIBDIR, flog) == -1)
errExit("asprintf");
int rv = system(cmd);
(void) rv;
@ -74,7 +74,7 @@ void netfilter_netlock(pid_t pid) {
}
char *cmd;
if (asprintf(&cmd, "%s/firejail/fnettrace --netfilter --log=%s", LIBDIR, flog) == -1)
if (asprintf(&cmd, "%s/firejail/fnetlock --log=%s", LIBDIR, flog) == -1)
errExit("asprintf");
free(flog);

9
src/fnetlock/Makefile Normal file
View file

@ -0,0 +1,9 @@
.SUFFIXES:
ROOT = ../..
-include $(ROOT)/config.mk
MOD_DIR = src/fnetlock
PROG = fnetlock
TARGET = $(PROG)
include $(ROOT)/src/prog.mk

51
src/fnetlock/fnetlock.h Normal file
View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014-2023 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.
*/
#ifndef FNETLOCK_H
#define FNETLOCK_H
#include "../include/common.h"
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/mman.h>
//#define DEBUG 1
#define NETLOCK_INTERVAL 60 // seconds
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);
}
// main.c
void logprintf(char* fmt, ...);
// tail.c
void tail(const char *logfile);
#endif

392
src/fnetlock/main.c Normal file
View file

@ -0,0 +1,392 @@
/*
* Copyright (C) 2014-2023 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 "fnetlock.h"
#include <limits.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <signal.h>
#define MAX_BUF_SIZE (64 * 1024)
static int arg_tail = 0;
static char *arg_log = NULL;
//*****************************************************************
// traffic trace storage - hash table for fast access + linked list for display purposes
//*****************************************************************
typedef struct hnode_t {
struct hnode_t *hnext; // used for hash table and unused linked list
struct hnode_t *dnext; // used to display streams on the screen
uint32_t ip_src;
uint16_t port_src;
uint8_t protocol;
// the firewall is build based on source address, and in the linked list
// we could have elements with the same address but different ports
uint8_t ip_instance;
} HNode;
// hash table
#define HMAX 256
HNode *htable[HMAX] = {NULL};
static int have_traffic = 0;
// using protocol 0 and port 0 for ICMP
static void hnode_add(uint32_t ip_src, uint8_t protocol, uint16_t port_src) {
uint8_t h = hash(ip_src);
int ip_instance = 0;
HNode *ptr = htable[h];
while (ptr) {
if (ptr->ip_src == ip_src) {
ip_instance++;
if (ptr->port_src == port_src && ptr->protocol == protocol)
return;
}
ptr = ptr->hnext;
}
logprintf("netlock: adding %d.%d.%d.%d\n", PRINT_IP(ip_src));
have_traffic = 1;
HNode *hnew = malloc(sizeof(HNode));
assert(hnew);
hnew->ip_src = ip_src;
hnew->port_src = port_src;
hnew->protocol = protocol;
hnew->hnext = NULL;
hnew->ip_instance = ip_instance + 1;
if (htable[h] == NULL)
htable[h] = hnew;
else {
hnew->hnext = htable[h];
htable[h] = hnew;
}
}
// trace rx traffic coming in
static void run_trace(void) {
logprintf("netlock: 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 char buf[MAX_BUF_SIZE];
unsigned bw = 0; // bandwidth calculations
int printed = 0;
while (1) {
unsigned runtime = time(NULL) - start;
if ( runtime >= NETLOCK_INTERVAL)
break;
if (runtime % 10 == 0) {
if (!printed)
logprintf("netlock: %u seconds remaining\n", NETLOCK_INTERVAL - runtime);
printed = 1;
}
else
printed = 0;
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;
// rx tcp traffic by default
int sock = s1;
if (FD_ISSET(s2, &rfds))
sock = s2;
unsigned bytes = recvfrom(sock, buf, MAX_BUF_SIZE, 0, NULL, NULL);
if (bytes >= 20) { // size of IP header
#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 && 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);
uint8_t hlen = (buf[0] & 0x0f) * 4;
uint16_t port_src = 0;
memcpy(&port_src, buf + hlen, 2);
port_src = ntohs(port_src);
uint8_t protocol = buf[9];
hnode_add(ip_src, protocol, port_src);
}
}
}
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) {
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++) {
HNode *ptr = htable[i];
while (ptr) {
// filter rules are targeting ip address, the port number is disregarded,
// so we look only at the first instance of an address
if (ptr->ip_instance == 1) {
char *protocol = (ptr->protocol == 6) ? "tcp" : "udp";
fprintf(fp, "-A INPUT -s %d.%d.%d.%d -p %s -j ACCEPT\n",
PRINT_IP(ptr->ip_src),
protocol);
fprintf(fp, "-A OUTPUT -d %d.%d.%d.%d -p %s -j ACCEPT\n",
PRINT_IP(ptr->ip_src),
protocol);
fprintf(fp, "\n");
}
ptr = ptr->hnext;
}
}
fprintf(fp, "COMMIT\n");
return 0;
}
static char *flush_rules[] = {
"-P INPUT ACCEPT",
// "-P FORWARD DENY",
"-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 deploy_netfilter(void) {
int rv;
char *cmd;
int i;
if (have_traffic == 0) {
logprintf("Sorry, no network traffic was detected. The firewall was not configured.\n");
return;
}
// find iptables command
char *iptables = NULL;
char *iptables_restore = NULL;
if (access("/sbin/iptables", X_OK) == 0) {
iptables = "/sbin/iptables";
iptables_restore = "/sbin/iptables-restore";
}
else if (access("/usr/sbin/iptables", X_OK) == 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");
exit(1);
}
// flush all netfilter rules
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++;
}
// 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);
logprintf("\n\n");
logprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
if (asprintf(&cmd, "cat %s >> %s", fname, arg_log) == -1)
errExit("asprintf");
rv = system(cmd);
(void) rv;
free(cmd);
if (asprintf(&cmd, "cat %s", fname) == -1)
errExit("asprintf");
rv = system(cmd);
(void) rv;
free(cmd);
logprintf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
// 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);
rv = unlink(fname);
(void) rv;
logprintf("\nnetlock: firewall 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);
}
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
}
static const char *const usage_str =
"Usage: fnettrace [OPTIONS]\n"
"Options:\n"
" --help, -? - this help screen\n"
" --log=filename - netlocker logfile\n"
" --tail - \"tail -f\" functionality\n";
static void usage(void) {
puts(usage_str);
}
int main(int argc, char **argv) {
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0) {
usage();
return 0;
}
else if (strcmp(argv[i], "--tail") == 0)
arg_tail = 1;
else if (strncmp(argv[i], "--log=", 6) == 0)
arg_log = argv[i] + 6;
else {
fprintf(stderr, "Error: invalid argument\n");
return 1;
}
}
// tail
if (arg_tail) {
if (!arg_log) {
fprintf(stderr, "Error: no log file\n");
usage();
exit(1);
}
tail(arg_log);
sleep(5);
exit(0);
}
if (getuid() != 0) {
fprintf(stderr, "Error: you need to be root to run this program\n");
return 1;
}
// kill the process if the parent died
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
logprintf("netlock: starting network lockdown\n");
run_trace();
// TCP path MTU discovery will not work properly since the firewall drops all ICMP packets
// Instead, we use iPacketization Layer PMTUD (RFC 4821) support in Linux kernel
int rv = system("echo 1 > /proc/sys/net/ipv4/tcp_mtu_probing");
(void) rv;
deploy_netfilter();
sleep(3);
if (arg_log)
unlink(arg_log);
return 0;
}

63
src/fnetlock/tail.c Normal file
View file

@ -0,0 +1,63 @@
/*
* Copyright (C) 2014-2023 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 "fnetlock.h"
void tail(const char *logfile) {
assert(logfile);
// wait for no more than 5 seconds for the logfile to appear in the filesystem
int cnt = 5;
while (access(logfile, R_OK) && cnt > 0)
cnt--;
if (cnt == 0)
exit(1);
off_t last_size = 0;
while (1) {
int fd = open(logfile, O_RDONLY);
if (fd == -1)
return;
off_t size = lseek(fd, 0, SEEK_END);
if (size < 0) {
close(fd);
return;
}
char *content = NULL;
int mmapped = 0;
if (size && size != last_size) {
content = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (content != MAP_FAILED)
mmapped = 1;
}
if (mmapped) {
printf("%.*s", (int) (size - last_size), content + last_size);
fflush(0);
munmap(content, size);
last_size = size;
}
sleep(1);
}
}