first commit

This commit is contained in:
Albert S. 2018-01-02 16:38:14 +01:00
commit 11dd36a7d8
4 changed files with 265 additions and 0 deletions

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
all: qsni.c
gcc -std=c11 -Wall -Wextra qsni.c -o qsni

64
README.md Normal file
View 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
View 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
View 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;
}