Porównaj commity

...

51 Commity

Autor SHA1 Wiadomość Data
0d7c5bd6d4 append_syscall_to_bpf(): Explicit type cast to fix (C++) warnings 2021-10-25 18:18:31 +02:00
55e1f42ca8 check_policy_sanity(): Initialize last_policy 2021-10-03 21:25:37 +02:00
11d64c6fcf enter_namespaces(): Check fopen/fprintf errors 2021-09-12 20:00:03 +02:00
ebe043c08d Fix missing \n in some error outputs 2021-09-12 19:50:05 +02:00
8bc0d1e73a Use overflow-safe operator builtins
As a precaution as it does not hurt
2021-09-12 19:47:45 +02:00
215032f32c enable_no_fs(): Fix corresponding test by adding missing default policy 2021-09-06 21:43:50 +02:00
411e00715d Rename qssb_append_default_syscall_policy() to better distinguish it from qssb_append_syscall_default_policy() 2021-09-05 17:24:42 +02:00
8a9b1730de test: Remove argc,argv from tests as there was no use for them 2021-09-05 17:12:25 +02:00
b2b501d97e test: Refactor: Put seccomp tests into child processes ; Simplfy .sh
Refactor the test logic. Seccomp tests that can be
killed run in their own subprocess now.

All test functions now return 0 on success. Therefore,
the shell script can be simplified.
2021-09-05 17:12:25 +02:00
26f391f736 test: implement test_seccomp_errno() 2021-09-05 17:12:25 +02:00
68fd1a0a87 test: test_seccomp_blacklisted_call_permitted(): Add missing default policy 2021-09-05 17:12:25 +02:00
b0d0beab22 README.md: Update 2021-09-05 17:12:25 +02:00
c44ce85628 test: Add test ensuring seccomp ends with default rule, minor fixes 2021-09-05 17:12:25 +02:00
25d8ed9bca check_policy_sanity(): Add syscall policy checks 2021-09-05 17:12:25 +02:00
e389140436 test.sh: Log exit code, print yes/no instead of 1/0 2021-09-05 17:12:25 +02:00
f6af1bb78f policy: Add disable_syscall_filter policy. Add defaults only on enable.
Only add default syscall policy when disable_syscall_filter is 0 (default)
and no user-custom policy has been added.
2021-09-05 17:12:25 +02:00
9192ec3aa4 Rewrite syscall policy logic
Instead of having a blacklist and whitelist, we now allow
setting a policy that runs as a chain.

This adds qssb_append_syscalls_policy()

Furthermore, add a feature to decide per syscall which action to take.
This allows now to return an error instead of just killing the process.

In the future, it may allow us to set optimize/shrink the BPF filter.
2021-09-05 17:12:03 +02:00
51844ea3ab bpf: Deny x32 system calls for now
The arch field is the same for x86_64 and x32, thus checking it
is not enough.

Simply using x32 system calls would allow a bypass. Thus,
we must check whether the system call number is in __X32_SYSCALL_BIT.

This is of course a lazy solution, we could also add the
same system call number + _X32_SYSCALL_BIT to our black/whitelists.

For now however, this however will do.
2021-08-12 12:25:12 +02:00
66c6d28dcd bpf: Check arch value
The filter was missing this check for arch, allowing bypasses
by using different calling conventions of other architectures.

A trivial example is execve() of x86 from and x86_64 process.
2021-08-12 11:57:13 +02:00
5cd45c09b7 bpf: Use SECCOMP_RET_KILL_PROCESS instead SECCOMP_RET_KILL
We generally want to kill the process not the thread.
2021-08-12 11:40:29 +02:00
fa06287b13 Use new qssb_append_*_syscall functions, remove old fields 2021-08-12 11:37:19 +02:00
68694723fe Begin qssb_append_*_syscall family of functions
The purpose of these new functions is to make it simpler for users
to add new syscalls to the whitelist and blacklist.

The current approach uses a user-supplied pointer which however
was difficult to manage with "no_fs", which may add systemcalls
to the blacklist. Then we must resize arrays, and suddenly
it's our job to free them.

As a bonus, implementing them here allows easier data structure
changes and decreases the chances tgat users of this API
do something wrong, like forgetting -1 at then end, etc.
2021-08-12 11:37:19 +02:00
4a4d551e75 Introduce "no_fs" and "no_new_fd" options.
no_fs is a simple way to take away all
FS access, without constructing path_policies etc.

no_new_fd disallows opening any new
file descriptors
2021-08-10 16:58:43 +02:00
57238b535c Expand disallowed system calls
Relevant: #8
2021-08-10 16:57:44 +02:00
b4e8116c20 seccomp_enable_whitelist(): Fix comment 2021-08-10 16:55:58 +02:00
75f607bc35 qssb_append_path_policies(): Add explicit type cast for c++ 2021-08-07 12:05:58 +02:00
a585db7778 qssb_free_policy(): Allow passing NULL 2021-06-08 22:04:46 +02:00
55ec51ba21 Improve and add functions comments 2021-06-08 22:04:46 +02:00
ade022ba62 update README 2021-06-08 22:04:26 +02:00
c57c79fa36 test: Log output of individual tests 2021-06-06 09:27:45 +02:00
5138d88b12 test: Count succeeded/failed tests 2021-06-06 09:02:30 +02:00
b8d6c78780 test: Rename fail(), echogreen() 2021-06-06 08:57:24 +02:00
a7c04537f7 Rename allowed_syscalls to whitelisted_syscalls for consistency 2021-06-05 20:15:09 +02:00
85c01899a9 Start implementing tests 2021-06-05 20:11:07 +02:00
0b13f551f4 Fix stray = in #define 2021-06-05 14:03:42 +02:00
bb07b95993 Fix stray semicolon 2021-06-05 11:55:59 +02:00
d070268fca Add more system calls to blacklist 2021-05-29 23:15:04 +02:00
d6f4a37de8 Remove unused qssb_end_policy() 2021-05-22 22:36:01 +02:00
afb429e124 qssb_policy: Remove unused syscall_default_policy member 2021-05-22 22:35:26 +02:00
946492c28e qssb_free_policy(): free path policies 2021-05-22 20:05:31 +02:00
ad9c391e3f QSSB_FS_ALLOW_WRITE does not imply ALLOW_READ anymore
Landlock can handle write access without it implying read access,
in contrast to the existing bind mounts solution. Hence, remove
ALLOW_READ from ALLOW_WRITE bitmask.
2021-05-22 20:05:31 +02:00
fcebed557c Add qssb_append_path_polic{ies,y}: Convenience function to add path policies 2021-05-22 20:05:25 +02:00
bb02e40101 Begin landlock support 2021-05-15 23:30:05 +02:00
7e2d4139cb Begin check_policy_sanity(): Checks whether policy is reasonable
Issue: #3
2021-05-09 12:59:58 +02:00
6e6812e13d Introduce mount_path_policies_to_chroot option, changing path_policy enforcement logic
Previously, we needed chroot and bind mounts to enforce path_policies. Therefore,
in the presence of path policies, we had to explicitly create a chroot
dir.

With the coming landlock support, this is not required anymore.

However, one might still want to chroot and bind mount flags. But
path policies don't dictate that anymore.
2021-05-09 12:59:58 +02:00
edf144bbc7 Allow overriding HAVE_LANDLOCK irrespectible of kernel verison 2021-05-09 12:59:58 +02:00
67e1afc904 Remove unused policy flag QSSB_FS_ALLOW_NOTHING 2021-05-09 12:59:58 +02:00
2c94fe8225 qssb_path_policy: rename 'mountpoint' to 'path', make 'policy' unsigned 2021-05-09 12:59:58 +02:00
4674638e9a Add landlock policy flags if landlock is supported 2021-05-09 12:59:58 +02:00
8697fd8b84 qssb.h: Add copyright header 2021-05-09 10:02:31 +02:00
ed6a2a1067 Rename general QSSB_MOUNT* flags to QSSB_FS* 2021-05-09 09:35:17 +02:00
5 zmienionych plików z 1229 dodań i 157 usunięć

17
Makefile Normal file
Wyświetl plik

@ -0,0 +1,17 @@
prefix = /usr/local
bindir = $(prefix)/bin
CFLAGS = -std=c99 -Wall -Wextra -pedantic
.DEFAULT_GOAL := test
clean:
rm -f test
test: test.c
$(CC) test.c -g $(CFLAGS) -o test
check: test
./test.sh
.PHONY: check

Wyświetl plik

@ -1,61 +1,57 @@
qssb.h (quite simple sandbox)
=============================
qssb.h is a simple header only library that provides an interface
to sandbox applications on Linux. Using Seccomp and Linux Namespaces for that
purpose requires some knowledge of annoying details which this library
aims to abstract away as much as possible.
# qssb.h (quite simple sandbox)
`qssb.h` is a simple header-only library that provides an interface to sandbox processes on Linux. Using Seccomp and Linux Namespaces for that purpose requires some knowledge of annoying details which this library aims to abstract away as much as possible, when reasonable. Hence, the goal is to provide a convenient way for processes to restrict themselves in order to mitigate the effect of exploits. Currently, it utilizes technologies like Seccomp, Namespaces and Landlock to this end.
Status
======
No release yet, API is unstable.
## Status
No release yet, expiremental, API is unstable, builds will break on updates of this library.
Features
========
- Systemcall filtering
- restricting file system access
Currently, it's mainly evolving according to the needs of my other projects.
## Features
- Systemcall filtering (using seccomp-bpf)
- restricting file system access (using Landlock and/or Namespaces)
- dropping privileges
- isolating the application from the network, etc.
Requirements
============
## Requirements
Kernel >=3.17
sys/capabilities.h header. Depending on your system, libcap
``sys/capabilities.h`` header. Depending on your distribution, libcap
might be needed for this.
While mostly transparent to users of this API, kernel >= 5.13 is required to take advantage of Landlock.
FAQ
===
Does the process need to be priviliged to utilize the library?
----------------------------------------------------------------
No.
## FAQ
It doesn't work on Debian!
--------------------------
You can thank a Debian-specific patch for that. In the future,
### Does the process need to be priviliged to utilize the library?
No.
### It doesn't work on Debian!
You can thank a Debian-specific kernel patch for that. In the future,
the library may check against that. Execute
echo 1 > /proc/sys/kernel/unprivileged_userns_clone to disable that
patch for now.
`echo 1 > /proc/sys/kernel/unprivileged_userns_clone` to disable that patch for now.
Documentation
=============
To be written
Examples
========
### Examples
- looqs: https://gitea.quitesimple.org/crtxcr/looqs
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
- cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb
- qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb
Contributing
============
### Contributing
Contributions 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.
1. Pull-Request on [github](https://github.com/quitesimpleorg/qssb.h)
2. Mail to `qssb at quitesimple.org` with instructions on where to pull the changes from.
3. Mailing a classic patch/diff to the same address.
License
=======

904
qssb.h

Plik diff jest za duży Load Diff

316
test.c Normal file
Wyświetl plik

@ -0,0 +1,316 @@
#include "qssb.h"
#include <stdbool.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
int xqssb_enable_policy(struct qssb_policy *policy)
{
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
fprintf(stderr, "qssb_enable_policy() failed: %i\n", ret);
exit(EXIT_FAILURE);
}
return 0;
}
int test_default_main()
{
struct qssb_policy *policy = qssb_init_policy();
int ret = qssb_enable_policy(policy);
return ret;
}
static int test_expected_kill(int (*f)())
{
pid_t pid = fork();
if(pid == 0)
{
return f();
}
int status = 0;
waitpid(pid, &status, 0);
if(WIFSIGNALED(status))
{
int c = WTERMSIG(status);
if(c == SIGSYS)
{
printf("Got expected signal\n");
return 0;
}
printf("Unexpected status code: %i\n", c);
return 1;
}
else
{
int c = WEXITSTATUS(status);
printf("Process was not killed, test fails. Status code of exit: %i\n", c);
return 1;
}
return 0;
}
static int test_successful_exit(int (*f)())
{
pid_t pid = fork();
if(pid == 0)
{
return f();
}
int status = 0;
waitpid(pid, &status, 0);
if(WIFSIGNALED(status))
{
int c = WTERMSIG(status);
printf("Received signal, which was not expected. Signal was: %i\n", c);
return 1;
}
else
{
int c = WEXITSTATUS(status);
if(c != 0)
{
printf("Process failed to exit properly. Status code is: %i\n", c);
}
return c;
}
printf("Process exited sucessfully as expected");
return 0;
}
static int do_test_seccomp_blacklisted()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
xqssb_enable_policy(policy);
uid_t pid = geteuid();
pid = getuid();
return 0;
}
int test_seccomp_blacklisted()
{
return test_expected_kill(&do_test_seccomp_blacklisted);
}
static int do_test_seccomp_blacklisted_call_permitted()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
int ret = qssb_enable_policy(policy);
//geteuid is not blacklisted, so must succeed
uid_t pid = geteuid();
return 0;
}
int test_seccomp_blacklisted_call_permitted()
{
return test_successful_exit(&do_test_seccomp_blacklisted_call_permitted);
}
static int do_test_seccomp_x32_kill()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
xqssb_enable_policy(policy);
/* Attempt to bypass by falling back to x32 should be blocked */
syscall(QSSB_SYS(getuid)+__X32_SYSCALL_BIT);
return 0;
}
int test_seccomp_x32_kill()
{
return test_expected_kill(&do_test_seccomp_x32_kill);
}
/* Tests whether seccomp rules end with a policy matching all syscalls */
int test_seccomp_require_last_matchall()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid));
int status = qssb_enable_policy(policy);
if(status == 0)
{
printf("Failed. Should not have been enabled!");
return 1;
}
return 0;
}
static int do_test_seccomp_errno()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, QSSB_SYS(close));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
xqssb_enable_policy(policy);
uid_t id = getuid();
int fd = close(0);
printf("close() return code: %i, errno: %s\n", fd, strerror(errno));
return fd == -1 ? 0 : 1;
}
int test_seccomp_errno()
{
return test_successful_exit(&do_test_seccomp_errno);
}
int test_landlock()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/proc/self/fd");
int ret = qssb_enable_policy(policy);
int fd = open("/", O_RDONLY | O_CLOEXEC);
if(fd < 0)
{
return 0;
}
return 1;
}
int test_landlock_deny_write()
{
struct qssb_policy *policy = qssb_init_policy();
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/tmp/");
int ret = qssb_enable_policy(policy);
int fd = open("/tmp/a", O_WRONLY | O_CLOEXEC);
if(fd < 0)
{
return 0;
}
return 1;
}
int test_nofs()
{
struct qssb_policy *policy = qssb_init_policy();
policy->no_fs = 1;
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
fprintf(stderr, "Failed to activate nofs sandbox\n");
return -1;
}
int s = socket(AF_INET,SOCK_STREAM,0);
if(s == -1)
{
fprintf(stderr, "Failed to open socket but this was not requested by policy\n");
return 1;
}
/* Expect seccomp to take care of this */
if(open("/test", O_CREAT | O_WRONLY) >= 0)
{
fprintf(stderr, "Failed: We do not expect write access\n");
return 1;
}
return 0;
}
int test_no_new_fds()
{
struct qssb_policy *policy = qssb_init_policy();
policy->no_new_fds = 1;
int ret = qssb_enable_policy(policy);
if(ret != 0)
{
fprintf(stderr, "Failed to activate no_new_fd sandbox\n");
return -1;
}
if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0)
{
fprintf(stderr, "Failed: Could open new file descriptor\n");
return -1;
}
int s = socket(AF_INET,SOCK_STREAM,0);
if(s >= 0)
{
fprintf(stderr, "Failed: socket got opened but policy denied\n");
return -1;
}
return 0;
}
struct dispatcher
{
char *name;
int (*f)();
};
struct dispatcher dispatchers[] = {
{ "default", &test_default_main },
{ "seccomp-blacklisted", &test_seccomp_blacklisted},
{ "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted},
{ "seccomp-x32-kill", &test_seccomp_x32_kill},
{ "seccomp-require-last-matchall", &test_seccomp_require_last_matchall},
{ "seccomp-errno", &test_seccomp_errno},
{ "landlock", &test_landlock},
{ "landlock-deny-write", &test_landlock_deny_write },
{ "no_fs", &test_nofs},
{ "no_new_fds", &test_no_new_fds}
};
int main(int argc, char *argv[])
{
if(argc < 2)
{
fprintf(stderr, "Usage: %s [testname]\n", argv[0]);
return EXIT_FAILURE;
}
char *test = argv[1];
if(strcmp(test, "--dumptests") == 0)
{
for(unsigned int i = 0; i < sizeof(dispatchers)/sizeof(dispatchers[0]); i++)
{
printf("%s\n", dispatchers[i].name);
}
return EXIT_SUCCESS;
}
for(unsigned int i = 0; i < sizeof(dispatchers)/sizeof(dispatchers[0]); i++)
{
struct dispatcher *current = &dispatchers[i];
if(strcmp(current->name, test) == 0)
{
return current->f();
}
}
fprintf(stderr, "Unknown test\n");
return EXIT_FAILURE;
}

77
test.sh Executable file
Wyświetl plik

@ -0,0 +1,77 @@
#!/bin/sh
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
COUNT_SUCCEEDED=0
COUNT_FAILED=0
function print_fail()
{
echo -e "${RED}$@${NC}" 1>&2
}
function print_success()
{
echo -e "${GREEN}$@${NC}"
}
function runtest_fail()
{
print_fail "failed"
COUNT_FAILED=$(($COUNT_FAILED+1))
}
function runtest_success()
{
print_success "ok"
COUNT_SUCCEEDED=$((COUNT_SUCCEEDED+1))
}
function runtest()
{
testname="$1"
test_log_file="$2"
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
echo -n "Running $1... "
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
(./test $1 || exit $?) &>> "${test_log_file}"
ret=$?
SUCCESS="no"
if [ $ret -eq 0 ] ; then
runtest_success
SUCCESS="yes"
else
runtest_fail
fi
echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
}
GIT_ID=$( git log --pretty="format:%h" -n1 )
TIMESTAMP=$(date +%s)
LOG_OUTPUT_DIR=$1
if [ -z "$LOG_OUTPUT_DIR" ] ; then
LOG_OUTPUT_DIR="./logs/"
fi
LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/qssb_test_${GIT_ID}_${TIMESTAMP}"
[ -d "$LOG_OUTPUT_DIR_PATH" ] || mkdir -p "$LOG_OUTPUT_DIR_PATH"
for test in $( ./test --dumptests ) ; do
testname=$( echo $test )
runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
done
echo
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"
echo "Succeeded: $COUNT_SUCCEEDED"
echo "Failed: $COUNT_FAILED"
if [ $COUNT_FAILED -gt 0 ] ; then
exit 1
fi
exit 0