diff options
author | 2023-07-25 10:22:59 -0400 | |
---|---|---|
committer | 2023-07-25 10:22:59 -0400 | |
commit | 8e4b847cdd9203227c003152d1ea4a2dd0f91664 (patch) | |
tree | aec337dd5faa357606ac06874f2168db6afe520e /src/fnetlock/main.c | |
parent | cleanup (diff) | |
download | firejail-8e4b847cd.tar.gz firejail-8e4b847cd.tar.zst firejail-8e4b847cd.zip |
split nettrace executable ^Cto netrace and netlock
Diffstat (limited to 'src/fnetlock/main.c')
-rw-r--r-- | src/fnetlock/main.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/src/fnetlock/main.c b/src/fnetlock/main.c new file mode 100644 index 000000000..b76be029d --- /dev/null +++ b/src/fnetlock/main.c | |||
@@ -0,0 +1,392 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2023 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 "fnetlock.h" | ||
21 | #include <limits.h> | ||
22 | #include <sys/ioctl.h> | ||
23 | #include <sys/prctl.h> | ||
24 | #include <signal.h> | ||
25 | #define MAX_BUF_SIZE (64 * 1024) | ||
26 | |||
27 | static int arg_tail = 0; | ||
28 | static char *arg_log = NULL; | ||
29 | |||
30 | //***************************************************************** | ||
31 | // traffic trace storage - hash table for fast access + linked list for display purposes | ||
32 | //***************************************************************** | ||
33 | typedef struct hnode_t { | ||
34 | struct hnode_t *hnext; // used for hash table and unused linked list | ||
35 | struct hnode_t *dnext; // used to display streams on the screen | ||
36 | uint32_t ip_src; | ||
37 | uint16_t port_src; | ||
38 | uint8_t protocol; | ||
39 | |||
40 | // the firewall is build based on source address, and in the linked list | ||
41 | // we could have elements with the same address but different ports | ||
42 | uint8_t ip_instance; | ||
43 | } HNode; | ||
44 | |||
45 | // hash table | ||
46 | #define HMAX 256 | ||
47 | HNode *htable[HMAX] = {NULL}; | ||
48 | static int have_traffic = 0; | ||
49 | |||
50 | // using protocol 0 and port 0 for ICMP | ||
51 | static void hnode_add(uint32_t ip_src, uint8_t protocol, uint16_t port_src) { | ||
52 | uint8_t h = hash(ip_src); | ||
53 | int ip_instance = 0; | ||
54 | HNode *ptr = htable[h]; | ||
55 | while (ptr) { | ||
56 | if (ptr->ip_src == ip_src) { | ||
57 | ip_instance++; | ||
58 | if (ptr->port_src == port_src && ptr->protocol == protocol) | ||
59 | return; | ||
60 | } | ||
61 | ptr = ptr->hnext; | ||
62 | } | ||
63 | |||
64 | logprintf("netlock: adding %d.%d.%d.%d\n", PRINT_IP(ip_src)); | ||
65 | have_traffic = 1; | ||
66 | HNode *hnew = malloc(sizeof(HNode)); | ||
67 | assert(hnew); | ||
68 | hnew->ip_src = ip_src; | ||
69 | hnew->port_src = port_src; | ||
70 | hnew->protocol = protocol; | ||
71 | hnew->hnext = NULL; | ||
72 | hnew->ip_instance = ip_instance + 1; | ||
73 | if (htable[h] == NULL) | ||
74 | htable[h] = hnew; | ||
75 | else { | ||
76 | hnew->hnext = htable[h]; | ||
77 | htable[h] = hnew; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | |||
82 | |||
83 | |||
84 | // trace rx traffic coming in | ||
85 | static void run_trace(void) { | ||
86 | logprintf("netlock: accumulating traffic for %d seconds\n", NETLOCK_INTERVAL); | ||
87 | |||
88 | // trace only rx ipv4 tcp and upd | ||
89 | int s1 = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); | ||
90 | int s2 = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); | ||
91 | if (s1 < 0 || s2 < 0) | ||
92 | errExit("socket"); | ||
93 | |||
94 | |||
95 | unsigned start = time(NULL); | ||
96 | unsigned char buf[MAX_BUF_SIZE]; | ||
97 | unsigned bw = 0; // bandwidth calculations | ||
98 | |||
99 | int printed = 0; | ||
100 | while (1) { | ||
101 | unsigned runtime = time(NULL) - start; | ||
102 | if ( runtime >= NETLOCK_INTERVAL) | ||
103 | break; | ||
104 | if (runtime % 10 == 0) { | ||
105 | if (!printed) | ||
106 | logprintf("netlock: %u seconds remaining\n", NETLOCK_INTERVAL - runtime); | ||
107 | printed = 1; | ||
108 | } | ||
109 | else | ||
110 | printed = 0; | ||
111 | |||
112 | fd_set rfds; | ||
113 | FD_ZERO(&rfds); | ||
114 | FD_SET(s1, &rfds); | ||
115 | FD_SET(s2, &rfds); | ||
116 | int maxfd = (s1 > s2) ? s1 : s2; | ||
117 | maxfd++; | ||
118 | |||
119 | struct timeval tv; | ||
120 | tv.tv_sec = 1; | ||
121 | tv.tv_usec = 0; | ||
122 | |||
123 | int rv = select(maxfd, &rfds, NULL, NULL, &tv); | ||
124 | if (rv < 0) | ||
125 | errExit("select"); | ||
126 | else if (rv == 0) | ||
127 | continue; | ||
128 | |||
129 | |||
130 | // rx tcp traffic by default | ||
131 | int sock = s1; | ||
132 | |||
133 | if (FD_ISSET(s2, &rfds)) | ||
134 | sock = s2; | ||
135 | |||
136 | unsigned bytes = recvfrom(sock, buf, MAX_BUF_SIZE, 0, NULL, NULL); | ||
137 | if (bytes >= 20) { // size of IP header | ||
138 | #ifdef DEBUG | ||
139 | { | ||
140 | uint32_t ip_src; | ||
141 | memcpy(&ip_src, buf + 12, 4); | ||
142 | ip_src = ntohl(ip_src); | ||
143 | |||
144 | uint32_t ip_dst; | ||
145 | memcpy(&ip_dst, buf + 16, 4); | ||
146 | ip_dst = ntohl(ip_dst); | ||
147 | printf("%d.%d.%d.%d -> %d.%d.%d.%d, %u bytes\n", PRINT_IP(ip_src), PRINT_IP(ip_dst), bytes); | ||
148 | } | ||
149 | #endif | ||
150 | // filter out loopback traffic | ||
151 | if (buf[12] != 127 && buf[16] != 127) { | ||
152 | bw += bytes + 14; // assume a 14 byte Ethernet layer | ||
153 | |||
154 | uint32_t ip_src; | ||
155 | memcpy(&ip_src, buf + 12, 4); | ||
156 | ip_src = ntohl(ip_src); | ||
157 | |||
158 | uint8_t hlen = (buf[0] & 0x0f) * 4; | ||
159 | uint16_t port_src = 0; | ||
160 | memcpy(&port_src, buf + hlen, 2); | ||
161 | port_src = ntohs(port_src); | ||
162 | |||
163 | uint8_t protocol = buf[9]; | ||
164 | hnode_add(ip_src, protocol, port_src); | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | close(s1); | ||
170 | close(s2); | ||
171 | } | ||
172 | |||
173 | static char *filter_start = | ||
174 | "*filter\n" | ||
175 | ":INPUT DROP [0:0]\n" | ||
176 | ":FORWARD DROP [0:0]\n" | ||
177 | ":OUTPUT DROP [0:0]\n"; | ||
178 | |||
179 | // return 1 if error | ||
180 | static int print_filter(FILE *fp) { | ||
181 | fprintf(fp, "%s\n", filter_start); | ||
182 | fprintf(fp, "-A INPUT -s 127.0.0.0/8 -j ACCEPT\n"); | ||
183 | fprintf(fp, "-A OUTPUT -d 127.0.0.0/8 -j ACCEPT\n"); | ||
184 | fprintf(fp, "\n"); | ||
185 | |||
186 | int i; | ||
187 | for (i = 0; i < HMAX; i++) { | ||
188 | HNode *ptr = htable[i]; | ||
189 | while (ptr) { | ||
190 | // filter rules are targeting ip address, the port number is disregarded, | ||
191 | // so we look only at the first instance of an address | ||
192 | if (ptr->ip_instance == 1) { | ||
193 | char *protocol = (ptr->protocol == 6) ? "tcp" : "udp"; | ||
194 | fprintf(fp, "-A INPUT -s %d.%d.%d.%d -p %s -j ACCEPT\n", | ||
195 | PRINT_IP(ptr->ip_src), | ||
196 | protocol); | ||
197 | fprintf(fp, "-A OUTPUT -d %d.%d.%d.%d -p %s -j ACCEPT\n", | ||
198 | PRINT_IP(ptr->ip_src), | ||
199 | protocol); | ||
200 | fprintf(fp, "\n"); | ||
201 | } | ||
202 | ptr = ptr->hnext; | ||
203 | } | ||
204 | } | ||
205 | fprintf(fp, "COMMIT\n"); | ||
206 | |||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static char *flush_rules[] = { | ||
211 | "-P INPUT ACCEPT", | ||
212 | // "-P FORWARD DENY", | ||
213 | "-P OUTPUT ACCEPT", | ||
214 | "-F", | ||
215 | "-X", | ||
216 | // "-t nat -F", | ||
217 | // "-t nat -X", | ||
218 | // "-t mangle -F", | ||
219 | // "-t mangle -X", | ||
220 | // "iptables -t raw -F", | ||
221 | // "-t raw -X", | ||
222 | NULL | ||
223 | }; | ||
224 | |||
225 | static void deploy_netfilter(void) { | ||
226 | int rv; | ||
227 | char *cmd; | ||
228 | int i; | ||
229 | |||
230 | if (have_traffic == 0) { | ||
231 | logprintf("Sorry, no network traffic was detected. The firewall was not configured.\n"); | ||
232 | return; | ||
233 | } | ||
234 | // find iptables command | ||
235 | char *iptables = NULL; | ||
236 | char *iptables_restore = NULL; | ||
237 | if (access("/sbin/iptables", X_OK) == 0) { | ||
238 | iptables = "/sbin/iptables"; | ||
239 | iptables_restore = "/sbin/iptables-restore"; | ||
240 | } | ||
241 | else if (access("/usr/sbin/iptables", X_OK) == 0) { | ||
242 | iptables = "/usr/sbin/iptables"; | ||
243 | iptables_restore = "/usr/sbin/iptables-restore"; | ||
244 | } | ||
245 | if (iptables == NULL || iptables_restore == NULL) { | ||
246 | fprintf(stderr, "Error: iptables command not found, netfilter not configured\n"); | ||
247 | exit(1); | ||
248 | } | ||
249 | |||
250 | // flush all netfilter rules | ||
251 | i = 0; | ||
252 | while (flush_rules[i]) { | ||
253 | char *cmd; | ||
254 | if (asprintf(&cmd, "%s %s", iptables, flush_rules[i]) == -1) | ||
255 | errExit("asprintf"); | ||
256 | int rv = system(cmd); | ||
257 | (void) rv; | ||
258 | free(cmd); | ||
259 | i++; | ||
260 | } | ||
261 | |||
262 | // create temporary file | ||
263 | char fname[] = "/tmp/firejail-XXXXXX"; | ||
264 | int fd = mkstemp(fname); | ||
265 | if (fd == -1) { | ||
266 | fprintf(stderr, "Error: cannot create temporary configuration file\n"); | ||
267 | exit(1); | ||
268 | } | ||
269 | |||
270 | FILE *fp = fdopen(fd, "w"); | ||
271 | if (!fp) { | ||
272 | rv = unlink(fname); | ||
273 | (void) rv; | ||
274 | fprintf(stderr, "Error: cannot create temporary configuration file\n"); | ||
275 | exit(1); | ||
276 | } | ||
277 | print_filter(fp); | ||
278 | fclose(fp); | ||
279 | |||
280 | logprintf("\n\n"); | ||
281 | logprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); | ||
282 | if (asprintf(&cmd, "cat %s >> %s", fname, arg_log) == -1) | ||
283 | errExit("asprintf"); | ||
284 | rv = system(cmd); | ||
285 | (void) rv; | ||
286 | free(cmd); | ||
287 | |||
288 | if (asprintf(&cmd, "cat %s", fname) == -1) | ||
289 | errExit("asprintf"); | ||
290 | rv = system(cmd); | ||
291 | (void) rv; | ||
292 | free(cmd); | ||
293 | logprintf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | ||
294 | |||
295 | |||
296 | // configuring | ||
297 | if (asprintf(&cmd, "%s %s", iptables_restore, fname) == -1) | ||
298 | errExit("asprintf"); | ||
299 | rv = system(cmd); | ||
300 | if (rv) | ||
301 | fprintf(stdout, "Warning: possible netfilter problem!"); | ||
302 | free(cmd); | ||
303 | |||
304 | rv = unlink(fname); | ||
305 | (void) rv; | ||
306 | logprintf("\nnetlock: firewall deployed\n"); | ||
307 | } | ||
308 | |||
309 | void logprintf(char *fmt, ...) { | ||
310 | if (!arg_log) | ||
311 | return; | ||
312 | |||
313 | FILE *fp = fopen(arg_log, "a"); | ||
314 | if (fp) { // disregard if error | ||
315 | va_list args; | ||
316 | va_start(args, fmt); | ||
317 | vfprintf(fp, fmt, args); | ||
318 | va_end(args); | ||
319 | fclose(fp); | ||
320 | } | ||
321 | |||
322 | va_list args; | ||
323 | va_start(args, fmt); | ||
324 | vfprintf(stdout, fmt, args); | ||
325 | va_end(args); | ||
326 | } | ||
327 | |||
328 | static const char *const usage_str = | ||
329 | "Usage: fnettrace [OPTIONS]\n" | ||
330 | "Options:\n" | ||
331 | " --help, -? - this help screen\n" | ||
332 | " --log=filename - netlocker logfile\n" | ||
333 | " --tail - \"tail -f\" functionality\n"; | ||
334 | |||
335 | static void usage(void) { | ||
336 | puts(usage_str); | ||
337 | } | ||
338 | |||
339 | int main(int argc, char **argv) { | ||
340 | int i; | ||
341 | |||
342 | for (i = 1; i < argc; i++) { | ||
343 | if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0) { | ||
344 | usage(); | ||
345 | return 0; | ||
346 | } | ||
347 | else if (strcmp(argv[i], "--tail") == 0) | ||
348 | arg_tail = 1; | ||
349 | else if (strncmp(argv[i], "--log=", 6) == 0) | ||
350 | arg_log = argv[i] + 6; | ||
351 | else { | ||
352 | fprintf(stderr, "Error: invalid argument\n"); | ||
353 | return 1; | ||
354 | } | ||
355 | } | ||
356 | |||
357 | // tail | ||
358 | if (arg_tail) { | ||
359 | if (!arg_log) { | ||
360 | fprintf(stderr, "Error: no log file\n"); | ||
361 | usage(); | ||
362 | exit(1); | ||
363 | } | ||
364 | |||
365 | tail(arg_log); | ||
366 | sleep(5); | ||
367 | exit(0); | ||
368 | } | ||
369 | |||
370 | if (getuid() != 0) { | ||
371 | fprintf(stderr, "Error: you need to be root to run this program\n"); | ||
372 | return 1; | ||
373 | } | ||
374 | |||
375 | // kill the process if the parent died | ||
376 | prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); | ||
377 | |||
378 | logprintf("netlock: starting network lockdown\n"); | ||
379 | run_trace(); | ||
380 | |||
381 | // TCP path MTU discovery will not work properly since the firewall drops all ICMP packets | ||
382 | // Instead, we use iPacketization Layer PMTUD (RFC 4821) support in Linux kernel | ||
383 | int rv = system("echo 1 > /proc/sys/net/ipv4/tcp_mtu_probing"); | ||
384 | (void) rv; | ||
385 | |||
386 | deploy_netfilter(); | ||
387 | sleep(3); | ||
388 | if (arg_log) | ||
389 | unlink(arg_log); | ||
390 | |||
391 | return 0; | ||
392 | } | ||