netfilter template support

This commit is contained in:
netblue30 2017-11-18 08:39:02 -05:00
parent eb4b505ac2
commit ead4ec3089
20 changed files with 633 additions and 33 deletions

View file

@ -276,10 +276,13 @@ test-fs:
test-fcopy:
cd test/fcopy; ./fcopy.sh | grep TESTING
test: test-profiles test-private-lib test-fcopy test-fs test-utils test-sysutils test-environment test-apps test-apps-x11 test-apps-x11-xorg test-filters test-arguments
test-fnetfilter:
cd test/fnetfilter; ./fnetfilter.sh | grep TESTING
test: test-profiles test-private-lib test-fcopy test-fnetfilter test-fs test-utils test-sysutils test-environment test-apps test-apps-x11 test-apps-x11-xorg test-filters test-arguments
echo "TEST COMPLETE"
test-travis: test-profiles test-fcopy test-fs test-utils test-sysutils test-environment test-filters test-arguments
test-travis: test-profiles test-fcopy test-fnetfilter test-fs test-utils test-sysutils test-environment test-filters test-arguments
echo "TEST COMPLETE"
##########################################

View file

@ -210,18 +210,27 @@ $
--debug-private-lib
Debug messages for --private-lib option.
--netfilter=filename,arg1,arg2,arg3 ...
This is the template version of the previous command. $ARG1,
$ARG2, $ARG3 ... in the firewall script are replaced with arg1,
arg2, arg3 ... passed on the command line. Up to 16 arguments
are supported. Example:
$ firejail --net=eth0 --ip=192.168.1.105 \
--netfilter=/etc/firejail/tcpserver.net,5001 server-program
--netfilter.print=name|pid
Print the firewall installed in the sandbox specified by name
or PID. Example:
$ firejail --net=browser --net=eth0 --netfilter firefox &
$ firejail --name=browser --net=eth0 --netfilter firefox &
$ firejail --netfilter.print=browser
--netfilter6.print=name|pid
Print the IPv6 firewall installed in the sandbox specified by
name or PID. Example:
$ firejail --net=browser --net=eth0 --netfilter firefox &
$ firejail --name=browser --net=eth0 --netfilter firefox &
$ firejail --netfilter6.print=browser
`````

View file

@ -27,6 +27,7 @@ firejail (0.9.51) baseline; urgency=low
* feature: profile build tool (--build)
* feature: --netfilter.print
* feature: --netfilter6.print
* feature: netfilter template support
* new profiles: upstreamed many profiles from the following sources:
https://github.com/chiraag-nataraj/firejail-profiles,
https://github.com/nyancat18/fe,

27
etc/tcpserver.net Normal file
View file

@ -0,0 +1,27 @@
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
###################################################################
# Simple tcp filter template. $ARG1 is the port number.
#
# Usage: $ARG1 in this template is replaced by 5001 from command line below
#
# firejail --net=eth0 --ip=192.168.1.105 --netfilter=/etc/firejail/tcpserver.net,5001 server-program
#
###################################################################
# allow server traffic
-A INPUT -p tcp --dport $ARG1 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -p tcp --sport $ARG1 -m state --state ESTABLISHED -j ACCEPT
# allow incoming ping
-A INPUT -p icmp --icmp-type echo-request -j ACCEPT
-A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
# allow outgoing DNS
-A OUTPUT -p udp --dport 53 -j ACCEPT
-A INPUT -p udp --sport 53 -j ACCEPT
COMMIT

View file

@ -24,7 +24,6 @@
#include <sys/wait.h>
#include <fcntl.h>
void check_netfilter_file(const char *fname) {
EUID_ASSERT();
@ -44,7 +43,6 @@ void check_netfilter_file(const char *fname) {
free(tmp);
}
void netfilter(const char *fname) {
// find iptables command
struct stat s;
@ -150,6 +148,16 @@ void netfilter_print(pid_t pid, int ipv6) {
}
free(comm);
// check privileges for non-root users
uid_t uid = getuid();
if (uid != 0) {
uid_t sandbox_uid = pid_get_uid(pid);
if (uid != sandbox_uid) {
fprintf(stderr, "Error: permission is denied to join a sandbox created by a different user.\n");
exit(1);
}
}
// check network namespace
char *name;
if (asprintf(&name, "/run/firejail/network/%d-netmap", pid) == -1)
@ -196,4 +204,3 @@ void netfilter_print(pid_t pid, int ipv6) {
sbox_run(SBOX_ROOT | SBOX_CAPS_NETWORK | SBOX_SECCOMP, 2, iptables, "-vL");
}

View file

@ -122,7 +122,7 @@ void usage(void) {
printf(" --net=ethernet_interface - enable network namespaces and connect to this\n");
printf("\tEthernet interface.\n");
printf(" --net=none - enable a new, unconnected network namespace.\n");
printf(" --netfilter[=filename] - enable firewall.\n");
printf(" --netfilter[=filename,arg1,arg2,arg3 ...] - enable firewall.\n");
printf(" --netfilter.print=name|pid - print the firewall.\n");
printf(" --netfilter6=filename - enable IPv6 firewall.\n");
printf(" --netfilter6.print=name|pid - print the IPv6 firewall.\n");

View file

@ -20,8 +20,12 @@
#include "../include/common.h"
#define MAXBUF 4098
#define MAXARGS 16
static char *args[MAXARGS] = {0};
static int argcnt = 0;
int arg_quiet = 0;
static char *default_filter =
"*filter\n"
":INPUT DROP [0:0]\n"
@ -29,7 +33,7 @@ static char *default_filter =
":OUTPUT ACCEPT [0:0]\n"
"-A INPUT -i lo -j ACCEPT\n"
"-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
"# echo replay is handled by -m state RELATED/ESTABLISHED below\n"
"# echo replay is handled by -m state RELATED/ESTABLISHED above\n"
"#-A INPUT -p icmp --icmp-type echo-reply -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT\n"
"-A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT\n"
@ -46,6 +50,111 @@ static void usage(void) {
printf("\tfnetfilter netfilter-command destination-file\n");
}
static void copy(const char *src, const char *dest) {
FILE *fp1 = fopen(src, "r");
if (!fp1) {
fprintf(stderr, "Error fnetfilter: cannot open %s\n", src);
exit(1);
}
FILE *fp2 = fopen(dest, "w");
if (!fp2) {
fprintf(stderr, "Error fnetfilter: cannot open %s\n", dest);
exit(1);
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp1))
fprintf(fp2, "%s", buf);
fclose(fp1);
fclose(fp2);
}
static void process_template(char *src, const char *dest) {
char *arg_start = strchr(src, ',');
assert(arg_start);
*arg_start = '\0';
arg_start++;
if (*arg_start == '\0') {
fprintf(stderr, "Error fnetfilter: you need to provide at least on argument\n");
exit(1);
}
// extract the arguments from command line
char *token = strtok(arg_start, ",");
while (token) {
// look for abnormal things
int len = strlen(token);
if (strcspn(token, "\\&!?\"'<>%^(){};,*[]") != (size_t)len) {
fprintf(stderr, "Error fnetfilter: invalid argument in netfilter command\n");
exit(1);
}
args[argcnt] = token;
argcnt++;
token = strtok(NULL, ",");
}
#if 0
{
printf("argcnt %d\n", argcnt);
int i;
for (i = 0; i < argcnt; i++)
printf("%s\n", args[i]);
}
#endif
// open the files
FILE *fp1 = fopen(src, "r");
if (!fp1) {
fprintf(stderr, "Error fnetfilter: cannot open %s\n", src);
exit(1);
}
FILE *fp2 = fopen(dest, "w");
if (!fp2) {
fprintf(stderr, "Error fnetfilter: cannot open %s\n", dest);
exit(1);
}
int line = 0;
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp1)) {
line++;
char *ptr = buf;
while (*ptr != '\0') {
if (*ptr != '$')
fputc(*ptr, fp2);
else {
// parsing
int index = 0;
int rv = sscanf(ptr, "$ARG%u", &index) ;
if (rv != 1) {
fprintf(stderr, "Error fnetfilter: invalid template argument on line %d\n", line);
exit(1);
}
// print argument
if (index < 1 || index > argcnt) {
fprintf(stderr, "Error fnetfilter: $ARG%d on line %d was not defined\n", index, line);
exit(1);
}
fprintf(fp2, "%s", args[index - 1]);
// march to the end of argument
ptr += 4;
while (isdigit(*ptr))
ptr++;
ptr--;
}
ptr++;
}
}
fclose(fp1);
fclose(fp2);
}
int main(int argc, char **argv) {
#if 0
{
@ -61,7 +170,7 @@ printf("\n");
if (quiet && strcmp(quiet, "yes") == 0)
arg_quiet = 1;
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0) {
if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0)) {
usage();
return 0;
}
@ -75,6 +184,12 @@ printf("\n");
char *command = (argc == 3)? argv[1]: NULL;
//printf("command %s\n", command);
//printf("destfile %s\n", destfile);
// destfile is a real filename
int len = strlen(destfile);
if (strcspn(destfile, "\\&!?\"'<>%^(){};,*[]") != (size_t)len) {
fprintf(stderr, "Error fnetfilter: invalid destination file in netfilter command\n");
exit(1);
}
// handle default config (command = NULL, destfile)
if (command == NULL) {
@ -88,28 +203,11 @@ printf("\n");
fclose(fp);
}
else {
// copy the file
FILE *fp1 = fopen(command, "r");
if (!fp1) {
fprintf(stderr, "Error fnetfilter: cannot open %s\n", command);
exit(1);
}
FILE *fp2 = fopen(destfile, "w");
if (!fp2) {
fprintf(stderr, "Error fnetfilter: cannot open %s\n", destfile);
exit(1);
}
char buf[MAXBUF];
while (fgets(buf, MAXBUF, fp1))
fprintf(fp2, "%s", buf);
fclose(fp1);
fclose(fp2);
if (strrchr(command, ','))
process_template(command, destfile);
else
copy(command, destfile);
}
printf("fnetfilter running\n");
return 0;
}

View file

@ -937,13 +937,32 @@ is a desktop client firewall that disable access to local network. Example:
$ firejail --netfilter=/etc/firejail/nolocal.net \\
.br
--net=eth0 firefox
.TP
\fB\-\-netfilter=filename,arg1,arg2,arg3 ...
This is the template version of the previous command. $ARG1, $ARG2, $ARG3 ... in the firewall script
are replaced with arg1, arg2, arg3 ... passed on the command line. Up to 16 arguments are supported.
Example:
.br
.br
$ firejail --net=eth0 --ip=192.168.1.105 \\
.br
--netfilter=/etc/firejail/tcpserver.net,5001 server-program
.br
.TP
\fB\-\-netfilter.print=name|pid
Print the firewall installed in the sandbox specified by name or PID. Example:
.br
.br
$ firejail --net=browser --net=eth0 --netfilter firefox &
$ firejail --name=browser --net=eth0 --netfilter firefox &
.br
$ firejail --netfilter.print=browser
@ -959,7 +978,7 @@ Print the IPv6 firewall installed in the sandbox specified by name or PID. Examp
.br
.br
$ firejail --net=browser --net=eth0 --netfilter firefox &
$ firejail --name=browser --net=eth0 --netfilter firefox &
.br
$ firejail --netfilter6.print=browser

37
test/fnetfilter/cmdline.exp Executable file
View file

@ -0,0 +1,37 @@
#!/usr/bin/expect -f
# This file is part of Firejail project
# Copyright (C) 2014-2017 Firejail Authors
# License GPL v2
set timeout 10
spawn $env(SHELL)
match_max 100000
send -- "fnetfilter\r"
expect {
timeout {puts "TESTING ERROR 1\n";exit}
"Usage:"
}
after 100
send -- "fnetfilter -h\r"
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"Usage:"
}
after 100
send -- "fnetfilter -h a b c d\r"
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"Usage:"
}
after 100
send -- "fnetfilter a b c d\r"
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"Usage:"
}
after 100
puts "\nall done\n"

52
test/fnetfilter/copy.exp Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/expect -f
# This file is part of Firejail project
# Copyright (C) 2014-2017 Firejail Authors
# License GPL v2
set timeout 10
spawn $env(SHELL)
match_max 100000
send -- "rm outfile\r"
after 100
send -- "fnetfilter test1.net outfile\r"
after 100
send -- "cat outfile\r"
expect {
timeout {puts "TESTING ERROR 1\n";exit}
"test1"
}
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"*filter"
}
expect {
timeout {puts "TESTING ERROR 3\n";exit}
"INPUT -m state --state RELATED,ESTABLISHED"
}
expect {
timeout {puts "TESTING ERROR 4\n";exit}
"disable STUN"
}
after 100
send -- "fnetfilter foo outfile\r"
expect {
timeout {puts "TESTING ERROR 5\n";exit}
"cannot open foo"
}
after 100
send -- "fnetfilter test1.net outlocked\r"
expect {
timeout {puts "TESTING ERROR 6\n";exit}
"cannot open outlocked"
}
after 100
send -- "rm outfile\r"
after 100
puts "\nall done\n"

40
test/fnetfilter/default.exp Executable file
View file

@ -0,0 +1,40 @@
#!/usr/bin/expect -f
# This file is part of Firejail project
# Copyright (C) 2014-2017 Firejail Authors
# License GPL v2
set timeout 10
spawn $env(SHELL)
match_max 100000
send -- "rm outfile\r"
after 100
send -- "fnetfilter outfile\r"
after 100
send -- "cat outfile\r"
expect {
timeout {puts "TESTING ERROR 1\n";exit}
"*filter"
}
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"INPUT -m state --state RELATED,ESTABLISHED"
}
expect {
timeout {puts "TESTING ERROR 3\n";exit}
"disable STUN"
}
after 100
send -- "fnetfilter test1.net,33\r"
expect {
timeout {puts "TESTING ERROR 4\n";exit}
"invalid destination file in netfilter command"
}
after 100
send -- "rm outfile\r"
after 100
puts "\nall done\n"

28
test/fnetfilter/fnetfilter.sh Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
# This file is part of Firejail project
# Copyright (C) 2014-2017 Firejail Authors
# License GPL v2
export MALLOC_CHECK_=3
export MALLOC_PERTURB_=$(($RANDOM % 255 + 1))
if [ -f /etc/debian_version ]; then
libdir=$(dirname "$(dpkg -L firejail | grep fcopy)")
export PATH="$PATH:$libdir"
fi
export PATH="$PATH:/usr/lib/firejail"
echo "TESTING: fnetfilter cmdline (test/fnetfilter/cmdline.exp)"
./cmdline.exp
echo "TESTING: fnetfilter default (test/fnetfilter/default.exp)"
./default.exp
echo "TESTING: fnetfilter copy (test/fnetfilter/copy.exp)"
./copy.exp
echo "TESTING: fnetfilter template (test/fnetfilter/template.exp)"
./template.exp
rm -f outfile

View file

82
test/fnetfilter/template.exp Executable file
View file

@ -0,0 +1,82 @@
#!/usr/bin/expect -f
# This file is part of Firejail project
# Copyright (C) 2014-2017 Firejail Authors
# License GPL v2
set timeout 10
spawn $env(SHELL)
match_max 100000
send -- "rm outfile\r"
after 100
send -- "fnetfilter test2.net,icmp-type,destination-unreachable,time-exceeded,echo-request,3478,3479 outfile\r"
after 100
send -- "cat outfile\r"
expect {
timeout {puts "TESTING ERROR 1\n";exit}
"*filter"
}
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"INPUT -m state --state RELATED,ESTABLISHED"
}
expect {
timeout {puts "TESTING ERROR 3\n";exit}
"icmp-type echo-reply"
}
expect {
timeout {puts "TESTING ERROR 4\n";exit}
"icmp-type destination-unreachable"
}
expect {
timeout {puts "TESTING ERROR 5\n";exit}
"icmp-type time-exceeded"
}
expect {
timeout {puts "TESTING ERROR 6\n";exit}
"icmp-type echo-request"
}
expect {
timeout {puts "TESTING ERROR 7\n";exit}
"dport 3478"
}
expect {
timeout {puts "TESTING ERROR 8\n";exit}
"dport 3479"
}
expect {
timeout {puts "TESTING ERROR 8\n";exit}
"dport 3478"
}
expect {
timeout {puts "TESTING ERROR 10\n";exit}
"dport 3479"
}
after 100
send -- "fnetfilter test2.net,icmp-type,destination-unreachable,time-exceeded,echo-request outfile\r"
expect {
timeout {puts "TESTING ERROR 11\n";exit}
"ARG5 on line 14 was not defined"
}
after 100
send -- "fnetfilter test2.net,icmp-type,destination-unreachable,time-exceeded,echo-request\r"
expect {
timeout {puts "TESTING ERROR 12\n";exit}
"invalid destination file in netfilter command"
}
after 100
send -- "fnetfilter test3.net,44 outfile\r"
expect {
timeout {puts "TESTING ERROR 13\n";exit}
"invalid template argument on line 1"
}
after 100
send -- "rm outfile\r"
after 100
puts "\nall done\n"

19
test/fnetfilter/test1.net Normal file
View file

@ -0,0 +1,19 @@
*filter
# test2
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# echo replay is handled by -m state RELATED/ESTABLISHED above
#-A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
-A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
-A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
-A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# disable STUN
-A OUTPUT -p udp --dport 3478 -j DROP
-A OUTPUT -p udp --dport 3479 -j DROP
-A OUTPUT -p tcp --dport 3478 -j DROP
-A OUTPUT -p tcp --dport 3479 -j DROP
COMMIT

19
test/fnetfilter/test2.net Normal file
View file

@ -0,0 +1,19 @@
*filter
# test2
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# echo replay is handled by -m state RELATED/ESTABLISHED above
#-A INPUT -p icmp --$ARG1 echo-reply -j ACCEPT
-A INPUT -p icmp --$ARG1 $ARG2 -j ACCEPT
-A INPUT -p icmp --$ARG1 $ARG3 -j ACCEPT
-A INPUT -p icmp --$ARG1 $ARG4 -j ACCEPT
# disable STUN
-A OUTPUT -p udp --dport $ARG5 -j DROP
-A OUTPUT -p udp --dport $ARG6 -j DROP
-A OUTPUT -p tcp --dport $ARG5 -j DROP
-A OUTPUT -p tcp --dport $ARG6 -j DROP
COMMIT

View file

@ -0,0 +1 @@
asdfasdf $ARG asdfasdfdasf

View file

@ -0,0 +1,44 @@
#!/usr/bin/expect -f
# This file is part of Firejail project
# Copyright (C) 2014-2017 Firejail Authors
# License GPL v2
set timeout 10
spawn $env(SHELL)
match_max 100000
send -- "firejail --net=br1 --ip=10.10.30.10 --name=test1 --netfilter=/etc/firejail/tcpserver.net,5555 ./tcpserver 5555\r"
expect {
timeout {puts "TESTING ERROR 1\n";exit}
"Child process initialized"
}
sleep 1
spawn $env(SHELL)
send -- "telnet 10.10.30.10 5555\r"
expect {
timeout {puts "TESTING ERROR 2\n";exit}
"Connected to 10.10.30.10"
}
sleep 1
send "sdfklsjadfl;ksadjfl;sdkfj\r"
expect {
timeout {puts "TESTING ERROR 3\n";exit}
"response"
}
expect {
timeout {puts "TESTING ERROR 4\n";exit}
"Connection closed"
}
sleep 1
send -- "telnet 10.10.30.10 5556\r"
expect {
timeout {puts "OK\n"}
"Connected to 10.10.30.10" {puts "TESTING ERROR 6\n";exit}
"dikasdfjasdjf"
}
after 100
puts "all done\n"

View file

@ -8,6 +8,12 @@ export MALLOC_PERTURB_=$(($RANDOM % 255 + 1))
sudo ./configure
echo "TESTING: netfilter template (netfilter-template.exp)"
rm -f ./tcpserver
gcc -o tcpserver tcpserver.c
./netfilter-template.exp
rm ./tcpserver
echo "TESTING: firemon interface (firemon-interfaces.exp)"
sudo ./firemon-interfaces.exp

108
test/network/tcpserver.c Normal file
View file

@ -0,0 +1,108 @@
/*
* Copyright (C) 2014-2017 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char **argv) {
int fd, newfd, client_len;
struct sockaddr_in serv_addr, client_addr;
int n, pid;
if (argc < 2) {
printf("Usage: ./server port-number\n");
return 1;
}
int portno = atoi(argv[1]);
// init socket
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("ERROR opening socket");
return 1;
}
// Initialize socket structure
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
// bind
if (bind(fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
return 1;
}
// listen - 5 pending conncections
if (listen(fd, 5) < 0) {
perror("listen");
return 1;
}
client_len = sizeof(client_addr);
while (1) {
newfd = accept(fd, (struct sockaddr *) &client_addr, &client_len);
if (newfd < 0) {
perror("accept");
return 1;
}
/* Create child process */
pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
if (pid == 0) {
// child
close(fd);
#define MAXBUF 4096
char buf[MAXBUF];
memset(buf, 0, MAXBUF);
int rcv = read(newfd, buf, MAXBUF - 1);
if (rcv < 0) {
perror("read");
exit(1);
}
int sent = write(newfd, "response\n", 9);
if (sent < 9) {
perror("write");
return 1;
}
exit(0);
}
else
close(newfd);
}
return 0;
}