first commit
This commit is contained in:
commit
11dd36a7d8
2
Makefile
Normal file
2
Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
all: qsni.c
|
||||
gcc -std=c11 -Wall -Wextra qsni.c -o qsni
|
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||
qsni¹
|
||||
====
|
||||
qsni (quite simple network isolation) allows for simple assignment
|
||||
of per cgroup iptables rules to programs.
|
||||
|
||||
While you can also achieve this (and more) using network namespaces,
|
||||
the setup is not as simple/easy.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
You need an iptables version that supports cgroup matching (e. g.
|
||||
version >= 1.6);
|
||||
|
||||
The following kernel config paramaters must be set:
|
||||
CONFIG_NETFILTER_XT_MATCH_CGROUP
|
||||
CONFIG_NET_CLS_CGROUP
|
||||
|
||||
Example
|
||||
=======
|
||||
$ qsni blocked ping google.com
|
||||
ping: unknown host google.com
|
||||
|
||||
$ qsni lan bash
|
||||
$ ping 8.8.8.8
|
||||
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
|
||||
ping: sendmsg: Operation not permitted
|
||||
$ ping 192.168.1.1
|
||||
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
|
||||
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.127 ms
|
||||
$ qsni someprofile bash
|
||||
already assigned to a net class, thus you can't use this binary to change that
|
||||
$
|
||||
|
||||
Setup
|
||||
=====
|
||||
If cgroup_root isn't mounted to /sys/fs/cgroup, do it or change the
|
||||
constant in the source to the correct path.
|
||||
|
||||
make
|
||||
cp qsni /usr/bin/
|
||||
chmod o=rx /usr/bin/qsni
|
||||
chown root:root /usr/bin/qsni
|
||||
setcap 'cap_setuid=ep cap_setgid=ep' /usr/bin/qsni
|
||||
|
||||
mkdir /etc/qsni.d
|
||||
chmod o=rx /etc/qsni.d
|
||||
cp profiles/blocked /etc/qsni.d/blocked
|
||||
chmod o=r /etc/qsni.d/blocked
|
||||
|
||||
Every profile must have its own unique CGROUP_ID value in the profile
|
||||
file.
|
||||
|
||||
|
||||
Security discussion
|
||||
--------------------
|
||||
This alone is not a satisfactory way to prevent misbehaving programs
|
||||
to contact destinations you don't want them to. While the restrictions
|
||||
also apply to the children of the launched progorams, at a minimum, file
|
||||
system isolation is also necessary and perhaps IPC etc.
|
||||
|
||||
qsni however does not aim to be a complete "jailing/isolation" solution.
|
||||
Nevertheless, I have use cases for it, hence its existence.
|
||||
|
||||
¹ name is preliminary,
|
21
profiles/blocked
Executable file
21
profiles/blocked
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
export PATH="/sbin:/usr/sbin:/usr:/bin"
|
||||
CGROUP_ID=2000
|
||||
function addrule()
|
||||
{
|
||||
iptables -C $@ -m cgroup --cgroup $CGROUP_ID &> /dev/null || iptables -A $@ -m cgroup --cgroup $CGROUP_ID
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed adding iptables rule" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
NAME=$(basename $0)
|
||||
[ -d /sys/fs/cgroup/net_cls/$NAME ] || mkdir /sys/fs/cgroup/net_cls/$NAME
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed creating cgroup directory";
|
||||
exit 1
|
||||
fi
|
||||
echo -n "$CGROUP_ID" > /sys/fs/cgroup/net_cls/$NAME/net_cls.classid
|
||||
|
||||
addrule OUTPUT -j DROP
|
||||
|
178
qsni.c
Normal file
178
qsni.c
Normal file
@ -0,0 +1,178 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <linux/limits.h>
|
||||
#define NET_CLS_DIR "/sys/fs/cgroup/net_cls/"
|
||||
#define IPTABLES_PROFILES_DIR "/etc/qsni.d/"
|
||||
|
||||
//exits if we are already inside a profile.
|
||||
void ensure_outside_profile()
|
||||
{
|
||||
FILE *fp = fopen("/proc/self/cgroup", "r");
|
||||
if(fp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to open cgroup file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char *line = NULL;
|
||||
size_t n = 0;
|
||||
while(getline(&line, &n, fp) != -1)
|
||||
{
|
||||
char *id = line;
|
||||
char *tmp = strchr(id, ':');
|
||||
if(tmp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Misformated cgroups file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
*tmp = 0;
|
||||
++tmp;
|
||||
char *controllers = tmp;
|
||||
tmp = strchr(controllers, ':');
|
||||
if(tmp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Misformated cgroups file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
*tmp = 0;
|
||||
++tmp;
|
||||
|
||||
char *assigned = tmp;
|
||||
|
||||
if(strstr(controllers, "net_cls") != NULL)
|
||||
{
|
||||
if(assigned[0] == '/' && assigned[1] != '\n')
|
||||
{
|
||||
fprintf(stderr, "already assigned to a net class, thus you can't use this binary to change that\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
line = NULL;
|
||||
n = 0;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
}
|
||||
|
||||
void init_profile(const char *profilepath)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if(pid == 0)
|
||||
{
|
||||
if(clearenv() != 0)
|
||||
{
|
||||
perror("clearenv");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int ret = execl(profilepath, profilepath, (char *) NULL);
|
||||
if(ret == -1)
|
||||
{
|
||||
perror("execl of child");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
else if(pid > 0)
|
||||
{
|
||||
int status=1;
|
||||
pid_t w = waitpid(pid, &status, 0);
|
||||
if(w == -1)
|
||||
{
|
||||
perror("waitpid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if(! WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||
{
|
||||
|
||||
fprintf(stderr, "profile setup script failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
if(pid == -1)
|
||||
{
|
||||
perror("fork");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
}
|
||||
void drop(uid_t u, gid_t g)
|
||||
{
|
||||
if(setgid(g) != 0)
|
||||
{
|
||||
perror("setgid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if(setuid(u) != 0)
|
||||
{
|
||||
perror("setuid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void assign_to_profile(const char *profilename)
|
||||
{
|
||||
char taskspath[PATH_MAX +1];
|
||||
snprintf(taskspath, sizeof(taskspath), "%s/%s/tasks", NET_CLS_DIR, profilename);
|
||||
|
||||
pid_t mypid = getpid();
|
||||
FILE *fp = fopen(taskspath, "a");
|
||||
if(fp == NULL)
|
||||
{
|
||||
perror("fopen");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
fprintf(fp, "%ld", (long)mypid);
|
||||
fclose(fp);
|
||||
}
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if(argc < 3)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s profile command [arguments...]\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
ensure_outside_profile();
|
||||
|
||||
char *profilename = argv[1];
|
||||
|
||||
char profilepath[PATH_MAX +1];
|
||||
snprintf(profilepath, sizeof(profilepath), "%s/%s", IPTABLES_PROFILES_DIR, profilename);
|
||||
int ret = access(profilepath, R_OK);
|
||||
if(ret != 0)
|
||||
{
|
||||
perror("check for profile path failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
uid_t myuid = getuid();
|
||||
gid_t myguid = getgid();
|
||||
if(setuid(0) != 0)
|
||||
{
|
||||
perror("setuid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
init_profile(profilepath);
|
||||
assign_to_profile(profilename);
|
||||
|
||||
drop(myuid, myguid);
|
||||
|
||||
|
||||
argv += 2;
|
||||
int result = execvp(argv[0], argv);
|
||||
if(result == -1)
|
||||
{
|
||||
perror("execv");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
return 0;
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user