aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlbert S2018-01-02 16:38:14 +0100
committerAlbert S2018-01-02 16:38:14 +0100
commit11dd36a7d86b846a4399c2e0c6555c1808fd93e4 (patch)
treef78b21094ef0bdae3671f29f9793420ce8e3d4f2
downloadqsni-11dd36a7d86b846a4399c2e0c6555c1808fd93e4.tar.gz
qsni-11dd36a7d86b846a4399c2e0c6555c1808fd93e4.tar.bz2
first commit
-rw-r--r--Makefile2
-rw-r--r--README.md64
-rwxr-xr-xprofiles/blocked21
-rw-r--r--qsni.c178
4 files changed, 265 insertions, 0 deletions
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 <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;
+
+
+
+}