Initial commit
This commit is contained in:
		
							
								
								
									
										42
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
qssb (quite simple sandbox)
 | 
			
		||||
===========================
 | 
			
		||||
qssb.h is a simple header only library for easy sandboxing of
 | 
			
		||||
applications.
 | 
			
		||||
 | 
			
		||||
It aims to provide an interface to avoid the annoying details that
 | 
			
		||||
using Seccomp and Linux Namespaces requires.
 | 
			
		||||
 | 
			
		||||
Features
 | 
			
		||||
========
 | 
			
		||||
Systemcall filtering, restricting file system access, dropping
 | 
			
		||||
privileges, isolating the application from the network, etc.
 | 
			
		||||
 | 
			
		||||
Requirements
 | 
			
		||||
============
 | 
			
		||||
Kernel x.y.z.
 | 
			
		||||
 | 
			
		||||
Status
 | 
			
		||||
======
 | 
			
		||||
No release yet, API is unstable.
 | 
			
		||||
 | 
			
		||||
Documentation
 | 
			
		||||
=============
 | 
			
		||||
To be written
 | 
			
		||||
 | 
			
		||||
Examples
 | 
			
		||||
========
 | 
			
		||||
Real world project: cgit sandboxed: https://git.quitesimple.org/cgitsb
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Contributing
 | 
			
		||||
============
 | 
			
		||||
Contributations are very welcome. Options: 
 | 
			
		||||
1) Pull-Request: github.com/quitesimpleorg/qssb 
 | 
			
		||||
2) Mail to qssb at quitesimple.org with instructions
 | 
			
		||||
on where to pull the changes.
 | 
			
		||||
3) Mailing a classic patch.
 | 
			
		||||
 | 
			
		||||
License
 | 
			
		||||
=======
 | 
			
		||||
ISC
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										401
									
								
								qssb.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										401
									
								
								qssb.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,401 @@
 | 
			
		||||
#ifndef QSSB_H
 | 
			
		||||
#define QSSB_H
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
#include <sched.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <sys/mount.h>
 | 
			
		||||
#include <sys/prctl.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <linux/limits.h>
 | 
			
		||||
#include <linux/filter.h>
 | 
			
		||||
#include <linux/seccomp.h>
 | 
			
		||||
#include <sys/capability.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
//TODO: stolen from kernel samples/seccomp, GPLv2...?
 | 
			
		||||
#define ALLOW \
 | 
			
		||||
	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
 | 
			
		||||
#define DENY \
 | 
			
		||||
	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
 | 
			
		||||
#define SYSCALL(nr, jt) \
 | 
			
		||||
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (nr), 0, 1), jt
 | 
			
		||||
 | 
			
		||||
#define LOAD_SYSCALL_NR \
 | 
			
		||||
	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
 | 
			
		||||
		 offsetof(struct seccomp_data, nr))
 | 
			
		||||
 | 
			
		||||
#define QSSB_ISOLATE_NETWORK 1<<1
 | 
			
		||||
 | 
			
		||||
#define QSSB_LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
 | 
			
		||||
 | 
			
		||||
/* Policy tells qssb what to do */
 | 
			
		||||
struct qssb_policy
 | 
			
		||||
{
 | 
			
		||||
	int drop_caps;
 | 
			
		||||
	int preserve_cwd;
 | 
			
		||||
	int not_dumpable;
 | 
			
		||||
	int no_new_privs;
 | 
			
		||||
	int namespace_options;
 | 
			
		||||
	int syscall_default_policy;
 | 
			
		||||
	int *blacklisted_syscalls;
 | 
			
		||||
	int *allowed_syscalls;
 | 
			
		||||
	char *chroot_target_path;
 | 
			
		||||
	char *chdir_path;
 | 
			
		||||
	char **readonly_paths;
 | 
			
		||||
	char **writable_paths;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Creates the default policy
 | 
			
		||||
 * Must be freed using qssb_free_policy
 | 
			
		||||
 * @returns: default policy */
 | 
			
		||||
struct qssb_policy *qssb_init_policy()
 | 
			
		||||
{
 | 
			
		||||
	struct qssb_policy *result = calloc(1, sizeof(struct qssb_policy));
 | 
			
		||||
	result->drop_caps = 1;
 | 
			
		||||
	result->not_dumpable = 1;
 | 
			
		||||
	result->no_new_privs = 1;
 | 
			
		||||
	result->namespace_options = 0;
 | 
			
		||||
	result->chdir_path = "/";
 | 
			
		||||
	result->chroot_target_path = NULL;
 | 
			
		||||
	result->readonly_paths = NULL;
 | 
			
		||||
	result->writable_paths = NULL;
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Creates a directory and all necessary parent directories 
 | 
			
		||||
 * 
 | 
			
		||||
 * @returns: 0 on success, -ERRNO on failure
 | 
			
		||||
 * */
 | 
			
		||||
static int mkdir_structure(const char *p, mode_t mode)
 | 
			
		||||
{
 | 
			
		||||
	char path[PATH_MAX + 2] = { 0 };
 | 
			
		||||
	snprintf(path, sizeof(path), "%s/", p);
 | 
			
		||||
 | 
			
		||||
	char *begin = path;
 | 
			
		||||
	char *end = begin+1;
 | 
			
		||||
	
 | 
			
		||||
	while(*end)
 | 
			
		||||
	{
 | 
			
		||||
		if(*end == '/')
 | 
			
		||||
		{
 | 
			
		||||
			*end = 0;
 | 
			
		||||
			if(mkdir(begin, mode) < 0)
 | 
			
		||||
			{
 | 
			
		||||
				if(errno == EEXIST)
 | 
			
		||||
				{
 | 
			
		||||
					//TODO: stat, test if it is a directory, if not, err
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					QSSB_LOG_ERROR("Failed to create directory for chroot: %s\n", begin);
 | 
			
		||||
					return -1;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			*end = '/';
 | 
			
		||||
			++end;
 | 
			
		||||
			while(*end == '/')
 | 
			
		||||
			{
 | 
			
		||||
				++end;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			++end;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Helper to mount directories into the chroot path "chroot_target_path"
 | 
			
		||||
 * Paths will be created if necessary
 | 
			
		||||
 
 | 
			
		||||
 * @returns: 0 on sucess, -ERRNO on failure */
 | 
			
		||||
static int mount_to_chroot(const char *chroot_target_path, char **paths, unsigned long flags)
 | 
			
		||||
{
 | 
			
		||||
	if(paths == NULL)
 | 
			
		||||
	{
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	char *path = *paths;
 | 
			
		||||
	while(path != NULL)
 | 
			
		||||
	{
 | 
			
		||||
		char path_inside_chroot[PATH_MAX + 1];
 | 
			
		||||
		snprintf(path_inside_chroot, sizeof(path_inside_chroot), "%s/%s", chroot_target_path, path);
 | 
			
		||||
		int ret = mkdir_structure(path_inside_chroot, 0700);
 | 
			
		||||
		if(ret < 0)
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("Error creating directory structure while mounting paths to chroot. %s\n", strerror(errno));
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ret = mount(path, path_inside_chroot,  NULL, flags, NULL);
 | 
			
		||||
		if(ret < 0 )
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("Error: Failed to mount %s to %s: %s\n", path, path_inside_chroot, strerror(errno));
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path = *(++paths);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Ends the policy as best as possible. */
 | 
			
		||||
/* TODO: can this function do actually anything useful?*/
 | 
			
		||||
static int qssb_end_policy(struct qssb_policy *ctxt)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Frees the memory taken by a qssb_policy object
 | 
			
		||||
 */
 | 
			
		||||
static void qssb_free_policy(struct qssb_policy *ctxt)
 | 
			
		||||
{
 | 
			
		||||
	free(ctxt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Enters the user and mount namespaces */
 | 
			
		||||
static int enter_namespaces()
 | 
			
		||||
{
 | 
			
		||||
	int ret = unshare(CLONE_NEWUSER);
 | 
			
		||||
	if(ret == -1)
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("Error: Failed to unshare user namespaces: %s\n", strerror(errno));
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid_t current_uid = getuid();
 | 
			
		||||
	gid_t current_gid = getgid();
 | 
			
		||||
 | 
			
		||||
	//TODO: check errors
 | 
			
		||||
	FILE *fp = fopen("/proc/self/setgroups", "w");
 | 
			
		||||
	fprintf(fp, "deny");
 | 
			
		||||
	fclose(fp);
 | 
			
		||||
 | 
			
		||||
	fp = fopen("/proc/self/uid_map", "w");
 | 
			
		||||
	fprintf(fp, "0 %i", current_uid);
 | 
			
		||||
	fclose(fp); 
 | 
			
		||||
 | 
			
		||||
	fp = fopen("/proc/self/gid_map", "w");
 | 
			
		||||
	fprintf(fp, "0 %i", current_gid);
 | 
			
		||||
	fclose(fp);
 | 
			
		||||
 | 
			
		||||
	ret = unshare(CLONE_NEWNS);
 | 
			
		||||
	if(ret == -1)
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("Error: Failed to unshare mount namespaces: %s\n", strerror(errno));
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Drops all capabiltiies held by the process 
 | 
			
		||||
 * 
 | 
			
		||||
 * @returns: 0 on sucess, -1 on error
 | 
			
		||||
*/
 | 
			
		||||
static int drop_caps()
 | 
			
		||||
{
 | 
			
		||||
	int cap = 0;
 | 
			
		||||
	int res = 0;
 | 
			
		||||
	while((res = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) == 0)
 | 
			
		||||
	{
 | 
			
		||||
		++cap;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(res == -1 && errno != EINVAL)
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("Failed to drop the capability bounding set!");
 | 
			
		||||
		return -errno;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO: systems that are not 64 bit
 | 
			
		||||
	struct __user_cap_header_struct h = { 0 };
 | 
			
		||||
	h.pid = 0;
 | 
			
		||||
	h.version = _LINUX_CAPABILITY_VERSION_3;
 | 
			
		||||
	struct __user_cap_data_struct drop[2];
 | 
			
		||||
	drop[0].effective = 0;
 | 
			
		||||
	drop[0].permitted = 0;
 | 
			
		||||
	drop[0].inheritable = 0;
 | 
			
		||||
	drop[1].effective = 0;
 | 
			
		||||
	drop[1].permitted = 0;
 | 
			
		||||
	drop[1].inheritable = 0;
 | 
			
		||||
	if(capset(&h, drop) == -1)
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("Failed to drop capabilities: %s\n", strerror(errno));;
 | 
			
		||||
		return -errno;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Enables the per_syscall seccomp action for system calls
 | 
			
		||||
 * 
 | 
			
		||||
 * syscalls: array of system calls numbers. -1 must be the last entry.
 | 
			
		||||
 * per_syscall: action to apply for each system call
 | 
			
		||||
 * default_action: the default action at the end
 | 
			
		||||
 * 
 | 
			
		||||
 * @returns: 0 on success, -1 on error
 | 
			
		||||
 */
 | 
			
		||||
static int seccomp_enable(int *syscalls, int per_syscall, int default_action)
 | 
			
		||||
{
 | 
			
		||||
	struct sock_filter filter[1024] =
 | 
			
		||||
	{
 | 
			
		||||
		LOAD_SYSCALL_NR,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	int current_filter_index = 1;
 | 
			
		||||
	while(*syscalls != -1)
 | 
			
		||||
	{
 | 
			
		||||
		struct sock_filter syscall = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, *syscalls, 0, 1);
 | 
			
		||||
		struct sock_filter action = BPF_STMT(BPF_RET+BPF_K, per_syscall);
 | 
			
		||||
		filter[current_filter_index++] = syscall;
 | 
			
		||||
		filter[current_filter_index++] = action;
 | 
			
		||||
		
 | 
			
		||||
		++syscalls;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct sock_filter da = BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL);
 | 
			
		||||
	filter[current_filter_index] = da;
 | 
			
		||||
	
 | 
			
		||||
	struct sock_fprog prog = {
 | 
			
		||||
		.len = current_filter_index + 1,
 | 
			
		||||
		.filter = filter,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) 
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("prctl SET_SECCOMP %s\n", strerror(errno));
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Blacklists the specified systemcalls.
 | 
			
		||||
 * 
 | 
			
		||||
 * syscalls: array of system calls numbers. -1 must be the last entry.
 | 
			
		||||
 */
 | 
			
		||||
static int seccomp_enable_blacklist(int *syscalls)
 | 
			
		||||
{
 | 
			
		||||
	return seccomp_enable(syscalls, SECCOMP_RET_KILL, SECCOMP_RET_ALLOW);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Blacklists the specified systemcalls.
 | 
			
		||||
 * 
 | 
			
		||||
 * syscalls: array of system calls numbers. -1 must be the last entry.
 | 
			
		||||
 */
 | 
			
		||||
static int seccomp_enable_whitelist(int *syscalls)
 | 
			
		||||
{
 | 
			
		||||
	return seccomp_enable(syscalls, SECCOMP_RET_ALLOW, SECCOMP_RET_KILL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Enables the specified qssb_policy.
 | 
			
		||||
 * 
 | 
			
		||||
 * The calling process is supposed *TO BE WRITTEN* if 
 | 
			
		||||
 * this function fails.
 | 
			
		||||
 * @returns: 0 on sucess, <0 on error
 | 
			
		||||
 */
 | 
			
		||||
int qssb_enable_policy(struct qssb_policy *policy)
 | 
			
		||||
{
 | 
			
		||||
	if(policy->blacklisted_syscalls != NULL && policy->allowed_syscalls != NULL)
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("Error: Cannot mix blacklisted and whitelisted systemcalls\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	if(policy->chroot_target_path == NULL)
 | 
			
		||||
	{
 | 
			
		||||
		policy->chroot_target_path = "/tmp/.TODOIMPLEMENT"; //TODO: implement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(enter_namespaces() < 0)
 | 
			
		||||
	{
 | 
			
		||||
		QSSB_LOG_ERROR("Error while trying to enter namespaces\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(policy->readonly_paths != NULL || policy->writable_paths != NULL)
 | 
			
		||||
	{
 | 
			
		||||
		if(mount_to_chroot(policy->chroot_target_path, policy->readonly_paths,  MS_BIND | MS_RDONLY) < 0)
 | 
			
		||||
		{
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(mount_to_chroot(policy->chroot_target_path, policy->writable_paths,  MS_BIND) < 0)
 | 
			
		||||
		{
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(chroot(policy->chroot_target_path) < 0)
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("chroot: failed to enter %s\n", policy->chroot_target_path);
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(chdir(policy->chdir_path) < 0)
 | 
			
		||||
	{
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(policy->drop_caps)
 | 
			
		||||
	{
 | 
			
		||||
		drop_caps();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(policy->not_dumpable)
 | 
			
		||||
	{
 | 
			
		||||
		if(prctl(PR_SET_DUMPABLE, 0) == -1)
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("prctl: PR_SET_DUMPABLE failed\n");
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(policy->no_new_privs)
 | 
			
		||||
	{
 | 
			
		||||
		if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("prctl: PR_SET_NO_NEW_PRIVS failed: %s\n", strerror(errno));
 | 
			
		||||
			return -1;
 | 
			
		||||
		}	
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(policy->allowed_syscalls != NULL)
 | 
			
		||||
	{
 | 
			
		||||
		if(seccomp_enable_whitelist(policy->allowed_syscalls) <0)
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("seccomp_enable_whitelist failed\n");
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(policy->blacklisted_syscalls != NULL)
 | 
			
		||||
	{
 | 
			
		||||
		if(seccomp_enable_blacklist(policy->blacklisted_syscalls) <0)
 | 
			
		||||
		{
 | 
			
		||||
			QSSB_LOG_ERROR("seccomp_enable_blacklist failed\n");
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
		Reference in New Issue
	
	Block a user