Comparar commits
51 Commits
9df2e9ee90
...
0d7c5bd6d4
Autor | SHA1 | Data | |
---|---|---|---|
0d7c5bd6d4 | |||
55e1f42ca8 | |||
11d64c6fcf | |||
ebe043c08d | |||
8bc0d1e73a | |||
215032f32c | |||
411e00715d | |||
8a9b1730de | |||
b2b501d97e | |||
26f391f736 | |||
68fd1a0a87 | |||
b0d0beab22 | |||
c44ce85628 | |||
25d8ed9bca | |||
e389140436 | |||
f6af1bb78f | |||
9192ec3aa4 | |||
51844ea3ab | |||
66c6d28dcd | |||
5cd45c09b7 | |||
fa06287b13 | |||
68694723fe | |||
4a4d551e75 | |||
57238b535c | |||
b4e8116c20 | |||
75f607bc35 | |||
a585db7778 | |||
55ec51ba21 | |||
ade022ba62 | |||
c57c79fa36 | |||
5138d88b12 | |||
b8d6c78780 | |||
a7c04537f7 | |||
85c01899a9 | |||
0b13f551f4 | |||
bb07b95993 | |||
d070268fca | |||
d6f4a37de8 | |||
afb429e124 | |||
946492c28e | |||
ad9c391e3f | |||
fcebed557c | |||
bb02e40101 | |||
7e2d4139cb | |||
6e6812e13d | |||
edf144bbc7 | |||
67e1afc904 | |||
2c94fe8225 | |||
4674638e9a | |||
8697fd8b84 | |||
ed6a2a1067 |
17
Makefile
Arquivo normal
17
Makefile
Arquivo normal
@ -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
|
72
README.md
72
README.md
@ -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
904
qssb.h
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
316
test.c
Arquivo normal
316
test.c
Arquivo normal
@ -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
Arquivo executável
77
test.sh
Arquivo executável
@ -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
|
Referência em uma nova issue
Block a user