From 11dd36a7d86b846a4399c2e0c6555c1808fd93e4 Mon Sep 17 00:00:00 2001 From: Albert S Date: Tue, 2 Jan 2018 16:38:14 +0100 Subject: [PATCH] first commit --- Makefile | 2 + README.md | 64 +++++++++++++++++ profiles/blocked | 21 ++++++ qsni.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100755 profiles/blocked create mode 100644 qsni.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae3f1fd --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: qsni.c + gcc -std=c11 -Wall -Wextra qsni.c -o qsni diff --git a/README.md b/README.md new file mode 100644 index 0000000..5290d5d --- /dev/null +++ b/README.md @@ -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, diff --git a/profiles/blocked b/profiles/blocked new file mode 100755 index 0000000..1dfd951 --- /dev/null +++ b/profiles/blocked @@ -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 + diff --git a/qsni.c b/qsni.c new file mode 100644 index 0000000..238eb90 --- /dev/null +++ b/qsni.c @@ -0,0 +1,178 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#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; + + + +}