Comparar commits
115 Commits
9df2e9ee90
...
WIP/cpp
Autor | SHA1 | Fecha | |
---|---|---|---|
73dae3a102 | |||
f2ca26010a | |||
0f39ee7061 | |||
41bd6e8f10 | |||
7f083909e6 | |||
732623fc6f | |||
dcfbe641f9 | |||
72a3b041d9 | |||
c57ba807d7 | |||
6f19c53acf | |||
99d26480d7 | |||
f13cff754c | |||
278ae31e2e | |||
5ef54a08b4 | |||
29b5864dd3 | |||
0a4e4850f9 | |||
4a3ac8e0bc | |||
ed54575b89 | |||
0caff45600 | |||
080c0e53c2 | |||
4adc13215b | |||
bf29edf213 | |||
68bfd7e66c | |||
58bc50db61 | |||
1e63fa75ef | |||
6c44c88397 | |||
3780509078 | |||
fd4dfb12f0 | |||
a9e6b3ee67 | |||
3b61e90761 | |||
0e27b19999 | |||
ff70142e04 | |||
4824c6eaa9 | |||
9048a3b4fe | |||
0b54e73ff4 | |||
b2306299d5 | |||
55b43fdaac | |||
6420ca1b40 | |||
98c76089de | |||
631980b775 | |||
0be081c55d | |||
ca0f82790c | |||
77adf09d34 | |||
bcab0377f1 | |||
b469a82eec | |||
6711b394d9 | |||
9abbc7510c | |||
029762e894 | |||
6b513f8339 | |||
d2357ac676 | |||
0b0dda0de1 | |||
7115ef8b4d | |||
15a6850023 | |||
48deab0dde | |||
ce7eb57998 | |||
3407fded04 | |||
1b4c5477a5 | |||
756b0fb421 | |||
d150c2ecd9 | |||
435bcefa48 | |||
2a4cee2ece | |||
d847d0f996 | |||
1a2443db18 | |||
db17e58deb | |||
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 |
27
Makefile
Archivo normal
27
Makefile
Archivo normal
@ -0,0 +1,27 @@
|
||||
prefix = /usr/local
|
||||
bindir = $(prefix)/bin
|
||||
CFLAGS = -std=c99 -Wall -Wextra -pedantic
|
||||
CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic
|
||||
|
||||
.DEFAULT_GOAL := tests
|
||||
|
||||
|
||||
clean:
|
||||
rm -f test exile.o testcpp
|
||||
|
||||
|
||||
exile.o: exile.c exile.h
|
||||
$(CC) -c exile.c -g $(CFLAGS) -o exile.o
|
||||
|
||||
test: test.c exile.h exile.o
|
||||
$(CC) test.c exile.o -g $(CFLAGS) -o test
|
||||
|
||||
testcpp: test.cpp exile.h exile.hpp exile.o
|
||||
$(CXX) test.cpp exile.o -g $(CXXFLAGS) -o testcpp
|
||||
|
||||
tests: test testcpp
|
||||
|
||||
check: tests
|
||||
./test.sh
|
||||
|
||||
.PHONY: check
|
136
README.md
136
README.md
@ -1,61 +1,115 @@
|
||||
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.
|
||||
# exile.h
|
||||
`exile.h` is a header-only library, enabling processes to easily isolate themselves on Linux for exploit mitigation. exile.h wants to make existing technologies, such as Seccomp and Linux Namespaces, easier to use. Those generally
|
||||
require knowledge of details and are not trivial for developers to employ, which prevents a more widespread adoption.
|
||||
|
||||
Status
|
||||
======
|
||||
No release yet, API is unstable.
|
||||
The following section gives small quick examples. Then the motivation is explained in more detail.
|
||||
Proper API documentation will be maintained in other files.
|
||||
|
||||
Features
|
||||
========
|
||||
- Systemcall filtering
|
||||
- restricting file system access
|
||||
- dropping privileges
|
||||
- isolating the application from the network, etc.
|
||||
## Quick demo
|
||||
TODO This section will demonstrate the simplicity of the API, but only serves as an overview.
|
||||
|
||||
Requirements
|
||||
============
|
||||
### Filesystem isolation
|
||||
|
||||
|
||||
|
||||
### System call policies / vows
|
||||
|
||||
|
||||
### Isolation of single functions
|
||||
exile_launch() demo
|
||||
|
||||
## Status
|
||||
No release yet, experimental, API is unstable, builds will break on updates of this library.
|
||||
|
||||
Currently, it's mainly evolving from the needs of my other projects.
|
||||
|
||||
## Motivation and Background
|
||||
exile.h unlocks existing Linux mechanisms to facilite isolation of processes from resources. Limiting the scope of what programs can do helps defending the rest of the system when a process gets under attacker's control (when classic mitigations such as ASLR etc. failed). To this end, OpenBSD has the pledge() and unveil() functions available. Those functions are helpful mitigation mechanisms, but such accessible ways are unfortunately not readily available on Linux. This is where exile.h steps in.
|
||||
|
||||
Seccomp allows to restrict system calls available to a process and thus decrease the systems attack surface, but it generally is not easy to use. Requiring BPF filter instructions, you generally just can't make use of it right away. exile.h provides an API inspired by pledge(), building on top of seccomp. It also provides an interface to manually restrict the system calls that can be issued.
|
||||
|
||||
Traditional methods employed to restrict file system access, like different uids/gids, chroot, bind-mounts, namespaces etc. may require administrator intervention, are perhaps only suitable
|
||||
for daemons and not desktop applications, or are generally rather involved. As a positive example, Landlock since 5.13 is a vast improvement to limit file system access of processes. It also greatly simplifies exile.h' implementation of fs isolation.
|
||||
|
||||
Abstracting those details may help developers bring sandboxing into their applications.
|
||||
|
||||
## Example: Archive extraction
|
||||
A programming uncompressing archives does not need network access, but should a bug allow code execution, obviously the payload may also access the network. Once the target path is known, it doesn't need access to the whole file system, only write-permissions to the target directory and read on the archive file(s).
|
||||
|
||||
TODO example with exile.h applied on "tar" or "unzip". Link to repo.
|
||||
|
||||
## Example: Web apps
|
||||
Those generally don't need access to the whole filesystem hierarchy, nor do they necessarily require the ability to execute other processes.
|
||||
|
||||
Way more examples can be given, but we can put it in simple words: A general purpose OS allow a process to do more things than it actually needs to do.
|
||||
|
||||
## Features
|
||||
- Restricting file system access (using Landlock or Namespaces/chroot as fallback)
|
||||
- Systemcall filtering (using seccomp-bpf). An interface inspired by OpenBSD's pledge() is available, removing the need to specifc rules for syscalls.
|
||||
- Dropping privileges in general, such as capabilities
|
||||
- Isolating the application from the network, etc. through Namespaces
|
||||
- Helpers to isolate single functions
|
||||
|
||||
|
||||
## What it's not
|
||||
A way for end users/administrators to restrict processes. In the future, a wrapper binary may be available to achieve this, but it generally aims for developers to bring sandboxing/isolation into their software, like web browsers do. This allows a more fine-grained approach, as the developers
|
||||
is more familiar with the software. Applying restrictions with solutions like AppArmor requires
|
||||
them to be present on the system and it's easy to break things this way.
|
||||
|
||||
Therefore, software should ideally be written with sandboxing in mind from the beginning.
|
||||
|
||||
|
||||
## Documentation
|
||||
Will be available once the interface stabilizes.
|
||||
|
||||
It's recommended to start with [README.usage.md] to get a feeling for exile.h.
|
||||
API-Documentation: [README.api.md]
|
||||
|
||||
## Limitations
|
||||
TODO:
|
||||
- seccomp must be kept up to date syscalls kernel
|
||||
- ioctl does not know the fd, so checking values is kind of strange
|
||||
- redundancies: some things are handled by capabilties, other by seccomp or both
|
||||
- seccomp no deep argument inspection
|
||||
- landlock: stat() does not apply
|
||||
- no magic, be reasonable, devs should not get sloppy, restrict IPC.
|
||||
|
||||
## Requirements
|
||||
Kernel >=3.17
|
||||
sys/capabilities.h header. Depending on your system, libcap
|
||||
might be needed for this.
|
||||
|
||||
While mostly transparent to users of this API, kernel >= 5.13 is required to take advantage of Landlock and furthermore it depends on distro-provided kernels being reasonable and enabling it by default. In practise, this means that Landlock probably won't be used for now, and exile.h will use a combination of namespaces, bind mounts and chroot as fallbacks.
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
FAQ
|
||||
===
|
||||
|
||||
Does the process need to be priviliged to utilize the library?
|
||||
----------------------------------------------------------------
|
||||
### Does the process need to be priviliged to utilize the library?
|
||||
|
||||
No.
|
||||
|
||||
It doesn't work on Debian!
|
||||
--------------------------
|
||||
You can thank a Debian-specific patch for that. In the future,
|
||||
### 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
|
||||
|
||||
Outdated:
|
||||
- cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb
|
||||
- qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb
|
||||
|
||||
|
||||
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.
|
||||
### Contributing
|
||||
|
||||
Contributions are very welcome. Options:
|
||||
|
||||
1. Pull-Request on [github](https://github.com/quitesimpleorg/exile.h)
|
||||
2. Mail to `exile at quitesimple.org` with instructions on where to pull the changes from.
|
||||
3. Mailing a classic patch/diff to the same address.
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
|
1854
exile.c
Archivo normal
1854
exile.c
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar Diff
540
exile.h
Archivo normal
540
exile.h
Archivo normal
@ -0,0 +1,540 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 Albert Schwarzkopf <mail at quitesimple dot org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef EXILE_H
|
||||
#define EXILE_H
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#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 <sys/random.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/mman.h>
|
||||
#include <stdarg.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 <linux/version.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/capability.h>
|
||||
#include <stddef.h>
|
||||
#include <inttypes.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#define capget(hdrp,datap) syscall(__NR_capget,hdrp,datap)
|
||||
#define capset(hdrp,datap) syscall(__NR_capset,hdrp,datap)
|
||||
|
||||
|
||||
#ifndef HAVE_LANDLOCK
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,13,0)
|
||||
/* TODO: Hopefully a fair assumption. But we need to runtime checks */
|
||||
#define HAVE_LANDLOCK 1
|
||||
#endif
|
||||
#endif
|
||||
#if HAVE_LANDLOCK == 1
|
||||
#include <linux/landlock.h>
|
||||
#if LANDLOCK_CREATE_RULESET_VERSION != (1U << 0)
|
||||
#error "This landlock ABI version is not supported by exile.h (yet)"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64
|
||||
#else
|
||||
#error Seccomp support has not been tested for exile.h for this platform yet
|
||||
#endif
|
||||
|
||||
#define EXILE_UNSHARE_NETWORK 1<<1
|
||||
#define EXILE_UNSHARE_USER 1<<2
|
||||
#define EXILE_UNSHARE_MOUNT 1<<3
|
||||
|
||||
#ifndef EXILE_LOG_ERROR
|
||||
#define EXILE_LOG_ERROR(...) do { fprintf(stderr, "exile.h: %s(): Error: ", __func__); fprintf(stderr, __VA_ARGS__); } while(0)
|
||||
#endif
|
||||
|
||||
#ifndef EXILE_TEMP_DIR
|
||||
#define EXILE_TEMP_DIR "/tmp"
|
||||
#endif
|
||||
|
||||
#define EXILE_SYS(x) __NR_##x
|
||||
|
||||
/* Allow all read-effect operations on the path */
|
||||
#define EXILE_FS_ALLOW_ALL_READ 1<<0
|
||||
/* Allow all write-effect operations on the path, such as normal writes, creation/deletion of files */
|
||||
#define EXILE_FS_ALLOW_ALL_WRITE (1<<1)
|
||||
#define EXILE_FS_ALLOW_EXEC 1<<2
|
||||
#define EXILE_FS_ALLOW_DEV 1<<3
|
||||
#define EXILE_FS_ALLOW_SETUID 1<<4
|
||||
|
||||
//don't mount recursive
|
||||
#define EXILE_MOUNT_NOT_REC 1<<5
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/* Fine-granular approach available with landlock */
|
||||
#if HAVE_LANDLOCK == 1
|
||||
#define EXILE_FS_ALLOW_REMOVE_DIR (1 << 7)
|
||||
#define EXILE_FS_ALLOW_REMOVE_FILE (1 << 8)
|
||||
#define EXILE_FS_ALLOW_MAKE_CHAR (1 << 9)
|
||||
#define EXILE_FS_ALLOW_MAKE_DIR (1 << 10)
|
||||
#define EXILE_FS_ALLOW_MAKE_REG (1 << 11)
|
||||
#define EXILE_FS_ALLOW_MAKE_SOCK (1 << 12)
|
||||
#define EXILE_FS_ALLOW_MAKE_FIFO (1 << 13)
|
||||
#define EXILE_FS_ALLOW_MAKE_BLOCK (1 << 14)
|
||||
#define EXILE_FS_ALLOW_MAKE_SYM (1 << 15)
|
||||
#define EXILE_FS_ALLOW_WRITE_FILE (1 << 16)
|
||||
#define EXILE_FS_ALLOW_READ_DIR (1 << 17)
|
||||
#define EXILE_FS_ALLOW_REMOVE (1 << 18)
|
||||
|
||||
#ifndef landlock_create_ruleset
|
||||
static inline int landlock_create_ruleset(
|
||||
const struct landlock_ruleset_attr *const attr,
|
||||
const size_t size, const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef landlock_add_rule
|
||||
static inline int landlock_add_rule(const int ruleset_fd,
|
||||
const enum landlock_rule_type rule_type,
|
||||
const void *const rule_attr, const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type,
|
||||
rule_attr, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef landlock_restrict_self
|
||||
static inline int landlock_restrict_self(const int ruleset_fd,
|
||||
const __u32 flags)
|
||||
{
|
||||
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#ifndef __NR_pkey_mprotect
|
||||
#define __NR_pkey_mprotect 329
|
||||
#endif
|
||||
#ifndef __NR_pkey_alloc
|
||||
#define __NR_pkey_alloc 330
|
||||
#endif
|
||||
#ifndef __NR_pkey_free
|
||||
#define __NR_pkey_free 331
|
||||
#endif
|
||||
#ifndef __NR_statx
|
||||
#define __NR_statx 332
|
||||
#endif
|
||||
#ifndef __NR_io_pgetevents
|
||||
#define __NR_io_pgetevents 333
|
||||
#endif
|
||||
#ifndef __NR_rseq
|
||||
#define __NR_rseq 334
|
||||
#endif
|
||||
#ifndef __NR_pidfd_send_signal
|
||||
#define __NR_pidfd_send_signal 424
|
||||
#endif
|
||||
#ifndef __NR_io_uring_setup
|
||||
#define __NR_io_uring_setup 425
|
||||
#endif
|
||||
#ifndef __NR_io_uring_enter
|
||||
#define __NR_io_uring_enter 426
|
||||
#endif
|
||||
#ifndef __NR_io_uring_register
|
||||
#define __NR_io_uring_register 427
|
||||
#endif
|
||||
#ifndef __NR_open_tree
|
||||
#define __NR_open_tree 428
|
||||
#endif
|
||||
#ifndef __NR_move_mount
|
||||
#define __NR_move_mount 429
|
||||
#endif
|
||||
#ifndef __NR_fsopen
|
||||
#define __NR_fsopen 430
|
||||
#endif
|
||||
#ifndef __NR_fsconfig
|
||||
#define __NR_fsconfig 431
|
||||
#endif
|
||||
#ifndef __NR_fsmount
|
||||
#define __NR_fsmount 432
|
||||
#endif
|
||||
#ifndef __NR_fspick
|
||||
#define __NR_fspick 433
|
||||
#endif
|
||||
#ifndef __NR_pidfd_open
|
||||
#define __NR_pidfd_open 434
|
||||
#endif
|
||||
#ifndef __NR_clone3
|
||||
#define __NR_clone3 435
|
||||
#endif
|
||||
#ifndef __NR_futex_waitv
|
||||
#define __NR_futex_waitv 449
|
||||
#endif
|
||||
#ifndef __NR_close_range
|
||||
#define __NR_close_range 436
|
||||
#endif
|
||||
#ifndef __NR_openat2
|
||||
#define __NR_openat2 437
|
||||
#endif
|
||||
#ifndef __NR_pidfd_getfd
|
||||
#define __NR_pidfd_getfd 438
|
||||
#endif
|
||||
#ifndef __NR_faccessat2
|
||||
#define __NR_faccessat2 439
|
||||
#endif
|
||||
#ifndef __NR_process_madvise
|
||||
#define __NR_process_madvise 440
|
||||
#endif
|
||||
#ifndef __NR_epoll_pwait2
|
||||
#define __NR_epoll_pwait2 441
|
||||
#endif
|
||||
#ifndef __NR_mount_setattr
|
||||
#define __NR_mount_setattr 442
|
||||
#endif
|
||||
#ifndef __NR_quotactl_fd
|
||||
#define __NR_quotactl_fd 443
|
||||
#endif
|
||||
#ifndef __NR_landlock_create_ruleset
|
||||
#define __NR_landlock_create_ruleset 444
|
||||
#endif
|
||||
#ifndef __NR_landlock_add_rule
|
||||
#define __NR_landlock_add_rule 445
|
||||
#endif
|
||||
#ifndef __NR_landlock_restrict_self
|
||||
#define __NR_landlock_restrict_self 446
|
||||
#endif
|
||||
#ifndef __NR_memfd_secret
|
||||
#define __NR_memfd_secret 447
|
||||
#endif
|
||||
#ifndef __NR_process_mrelease
|
||||
#define __NR_process_mrelease 448
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct syscall_vow_map
|
||||
{
|
||||
long syscall;
|
||||
uint64_t vowmask;
|
||||
};
|
||||
|
||||
struct str_to_vow_map
|
||||
{
|
||||
const char *str;
|
||||
uint64_t value;
|
||||
};
|
||||
|
||||
struct exile_path_policy
|
||||
{
|
||||
const char *path;
|
||||
unsigned int policy;
|
||||
struct exile_path_policy *next;
|
||||
};
|
||||
|
||||
/* Special values */
|
||||
#define EXILE_SYSCALL_MATCH_ALL -1
|
||||
/* exit the bpf filter, not matching policy. Go to the next syscall (or the default action, if none left to check) */
|
||||
#define EXILE_SYSCALL_EXIT_BPF_NO_MATCH 255
|
||||
/* exit the bpf filter, go directly to the action for the syscall (skip all other args checks) */
|
||||
#define EXILE_SYSCALL_EXIT_BPF_RETURN 254
|
||||
|
||||
#define EXILE_SYSCALL_ALLOW 1
|
||||
#define EXILE_SYSCALL_DENY_KILL_PROCESS 2
|
||||
#define EXILE_SYSCALL_DENY_RET_ERROR 3
|
||||
|
||||
#define EXILE_BPF_NOP \
|
||||
BPF_STMT(BPF_JMP+BPF_JA,0)
|
||||
|
||||
/* A few more dirty markers to simplify array block initializers. We replace those
|
||||
in append_syscall_to_bpf(). The k value is meaningless here and we don't expect
|
||||
to ever have filter code actually wanting to jump that many steps forward. So
|
||||
they serve as an special value we will replace with actual ones. */
|
||||
#define EXILE_BPF_RETURN_MATCHING \
|
||||
BPF_STMT(BPF_JMP+BPF_JA,1234)
|
||||
|
||||
#define EXILE_BPF_RETURN_NOT_MATCHING \
|
||||
BPF_STMT(BPF_JMP+BPF_JA,5678)
|
||||
|
||||
#define EXILE_BPF_LOAD_SECCOMP_ARG(nr) \
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[nr])))
|
||||
|
||||
#define EXILE_BPF_CMP_EQ(val,t,f) \
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, val, t, f)
|
||||
|
||||
#define EXILE_BPF_CMP_SET(val,t,f) \
|
||||
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, val, t, f)
|
||||
|
||||
/* Immediatly go to the syscall action, do not process any other arg filters */
|
||||
#define EXILE_BPF_MATCH(argval) \
|
||||
EXILE_BPF_CMP_EQ(argval, EXILE_SYSCALL_EXIT_BPF_RETURN, 0)
|
||||
|
||||
#define EXILE_BPF_MATCH_SET(argval) \
|
||||
EXILE_BPF_CMP_SET(argval, EXILE_SYSCALL_EXIT_BPF_RETURN, 0)
|
||||
|
||||
/* Immediatly go beyond the syscall action, do not process any other arg filters. What to do with this syscall
|
||||
is thus up to the default policy */
|
||||
#define EXILE_BPF_NO_MATCH(argval) \
|
||||
EXILE_BPF_CMP_EQ(argval, EXILE_SYSCALL_EXIT_BPF_NO_MATCH, 0)
|
||||
|
||||
#define EXILE_BPF_NO_MATCH_SET(argval) \
|
||||
EXILE_BPF_CMP_SET(argval, EXILE_SYSCALL_EXIT_BPF_NO_MATCH, 0)
|
||||
|
||||
|
||||
/* Pledge definitions */
|
||||
#define EXILE_SYSCALL_VOW_CHOWN ((uint64_t)1<<1)
|
||||
#define EXILE_SYSCALL_VOW_CLONE ((uint64_t)1<<2)
|
||||
#define EXILE_SYSCALL_VOW_CPATH ((uint64_t)1<<3)
|
||||
#define EXILE_SYSCALL_VOW_DPATH ((uint64_t)1<<4)
|
||||
#define EXILE_SYSCALL_VOW_EXEC ((uint64_t)1<<5)
|
||||
#define EXILE_SYSCALL_VOW_FATTR ((uint64_t)1<<6)
|
||||
#define EXILE_SYSCALL_VOW_FSNOTIFY ((uint64_t)1<<7)
|
||||
#define EXILE_SYSCALL_VOW_ID ((uint64_t)1<<8)
|
||||
#define EXILE_SYSCALL_VOW_INET ((uint64_t)1<<9)
|
||||
#define EXILE_SYSCALL_VOW_IOCTL ((uint64_t)1<<10)
|
||||
#define EXILE_SYSCALL_VOW_PRCTL ((uint64_t)1<<11)
|
||||
#define EXILE_SYSCALL_VOW_PROC ((uint64_t)1<<12)
|
||||
#define EXILE_SYSCALL_VOW_PROT_EXEC ((uint64_t)1<<13)
|
||||
#define EXILE_SYSCALL_VOW_RPATH ((uint64_t)1<<14)
|
||||
#define EXILE_SYSCALL_VOW_SCHED ((uint64_t)1<<15)
|
||||
#define EXILE_SYSCALL_VOW_SECCOMP_INSTALL ((uint64_t)1<<16)
|
||||
#define EXILE_SYSCALL_VOW_SHM ((uint64_t)1<<17)
|
||||
#define EXILE_SYSCALL_VOW_STDIO ((uint64_t)1<<18)
|
||||
#define EXILE_SYSCALL_VOW_THREAD ((uint64_t)1<<19)
|
||||
#define EXILE_SYSCALL_VOW_UNIX ((uint64_t)1<<20)
|
||||
#define EXILE_SYSCALL_VOW_WPATH ((uint64_t)1<<21)
|
||||
|
||||
#define EXILE_SYSCALL_VOW_DENY_ERROR ((uint64_t)1<<63)
|
||||
|
||||
|
||||
#define EXILE_ARGFILTERS_COUNT 60
|
||||
|
||||
|
||||
#define EXILE_FLAG_ADD_PATH_POLICY_FAIL (1u<<1)
|
||||
#define EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL (1u<<2)
|
||||
|
||||
struct exile_syscall_policy
|
||||
{
|
||||
struct sock_filter argfilters[EXILE_ARGFILTERS_COUNT];
|
||||
size_t argfilterscount;
|
||||
long syscall;
|
||||
unsigned int policy;
|
||||
struct exile_syscall_policy *next;
|
||||
};
|
||||
|
||||
/* Policy tells exile what to do */
|
||||
struct exile_policy
|
||||
{
|
||||
int drop_caps;
|
||||
int preserve_cwd;
|
||||
int not_dumpable;
|
||||
int no_new_privs;
|
||||
int no_fs;
|
||||
int no_new_fds;
|
||||
int namespace_options;
|
||||
int disable_syscall_filter;
|
||||
/* Bind mounts all paths in path_policies into the chroot and applies
|
||||
non-landlock policies */
|
||||
int mount_path_policies_to_chroot;
|
||||
char chroot_target_path[PATH_MAX];
|
||||
const char *chdir_path;
|
||||
|
||||
uint64_t vow_promises;
|
||||
|
||||
/* Do not manually add policies here, use exile_append_path_policies() */
|
||||
struct exile_path_policy *path_policies;
|
||||
struct exile_path_policy **path_policies_tail;
|
||||
|
||||
/* Do not manually add policies here, use exile_append_syscall_policy() */
|
||||
struct exile_syscall_policy *syscall_policies;
|
||||
struct exile_syscall_policy **syscall_policies_tail;
|
||||
|
||||
uint32_t exile_flags;
|
||||
};
|
||||
|
||||
/* Converts the whitespace separated vows strings to vows flags
|
||||
*
|
||||
* This mainly helps readability, as lots of flags ORed together is not
|
||||
* very readable.
|
||||
*
|
||||
* If an unkown string is found, abort() is called.
|
||||
*/
|
||||
uint64_t exile_vows_from_str(const char *str);
|
||||
|
||||
/*
|
||||
* If we can use landlock, return 1, otherwise 0
|
||||
*/
|
||||
int exile_landlock_is_available();
|
||||
|
||||
int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n);
|
||||
|
||||
int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy);
|
||||
|
||||
struct exile_syscall_filter
|
||||
{
|
||||
uint64_t vowmask; /* Apply filter if this mask is set. 0 = ignore mask, apply always */
|
||||
struct sock_filter filter;
|
||||
int whenset; /* 1 = Filter should be added if vowmask is contained in pledge mask, otherwise won't be added. */
|
||||
};
|
||||
|
||||
#define COUNT_EXILE_SYSCALL_FILTER(f) \
|
||||
sizeof(f)/sizeof(f[0])
|
||||
|
||||
#define EXILE_SYSCALL_FILTER_LOAD_ARG(val) \
|
||||
{ 0, EXILE_BPF_LOAD_SECCOMP_ARG(val), 0}
|
||||
|
||||
/* Returns, for the specific syscall, the correct sock_filter struct for the provided vow_promises
|
||||
*
|
||||
* Returns: 0 if none copied, otherwise the number of entries in "filter".
|
||||
*/
|
||||
int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy);
|
||||
|
||||
|
||||
int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises);
|
||||
|
||||
|
||||
/* Creates an empty policy struct without opinionated defaults.
|
||||
*
|
||||
* Must be freed using exile_free_policy()
|
||||
* @returns: empty policy
|
||||
*/
|
||||
struct exile_policy *exile_create_policy();
|
||||
|
||||
|
||||
/* Creates the default policy
|
||||
* Must be freed using exile_free_policy()
|
||||
*
|
||||
* @returns: default policy
|
||||
*/
|
||||
struct exile_policy *exile_init_policy();
|
||||
|
||||
|
||||
/* Appends path policies to the exile_policy object
|
||||
* The last paramater must be NULL
|
||||
*
|
||||
* This function does not copy parameters. All passed paths
|
||||
* MUST NOT be freed until exile_enable_policy() is called!
|
||||
*
|
||||
* @returns: 0 on success, -1 on failure */
|
||||
int exile_append_path_policies(struct exile_policy *exile_policy, unsigned int path_policy, ...);
|
||||
#define exile_append_path_policies(e, p, ...) exile_append_path_policies(e, p, __VA_ARGS__, NULL)
|
||||
|
||||
int path_policy_needs_landlock(struct exile_path_policy *path_policy);
|
||||
|
||||
/*
|
||||
* Frees the memory taken by a exile_policy object
|
||||
*/
|
||||
void exile_free_policy(struct exile_policy *ctxt);
|
||||
|
||||
|
||||
/*
|
||||
* Enables the seccomp policy
|
||||
*
|
||||
* policy: exile policy object
|
||||
*
|
||||
* @returns: 0 on success, -1 on error
|
||||
*/
|
||||
int exile_enable_syscall_policy(struct exile_policy *policy);
|
||||
|
||||
|
||||
int exile_enable_policy(struct exile_policy *policy);
|
||||
|
||||
|
||||
/* Convenience wrapper for the vow-related subset of exile.h
|
||||
*
|
||||
* Only installs seccomp filters for the specified vow promises.
|
||||
*
|
||||
* Useful if only vow is required from exile.h, but nothing else
|
||||
*
|
||||
* Comparable with OpenBSD's pledge(), subsequent calls can only reduce allowed syscalls.
|
||||
*
|
||||
* Here, adding more promises than a previous call set may return success, but
|
||||
* won't be allowed during execution.
|
||||
*
|
||||
* Due to the nature of seccomp, it's furthermore required the EXILE_SYSCALL_VOW_SECCOMP_INSTALL promise
|
||||
* is set if further calls are expected. Generally, it's reasonable for the last call to
|
||||
* exile_vow() a program makes to not set EXILE_SYSCALL_VOW_SECCOMP_INSTALL.
|
||||
*
|
||||
* There are no seperate exec_promises. All children of the process inherit the filter.
|
||||
* .
|
||||
* Return value: 0 on success, any other value on failure.
|
||||
*/
|
||||
int exile_vow(uint64_t promises);
|
||||
|
||||
struct exile_launch_params
|
||||
{
|
||||
struct exile_policy *policy; /* Policy to activate before jumping to func */
|
||||
int (*func)(void *); /* Function to be sandboxed */
|
||||
void *funcarg; /* Arg to be passed */
|
||||
int child_read_pipe[2];
|
||||
int child_write_pipe[2];
|
||||
};
|
||||
|
||||
struct exile_launch_result
|
||||
{
|
||||
int tid;
|
||||
int read_fd;
|
||||
int write_fd;
|
||||
};
|
||||
|
||||
int exile_clone_handle(void *arg);
|
||||
/* Helper to easily execute a single function sandboxed.
|
||||
*
|
||||
* Creates a child-process, then activates the policy contained in launch_params,
|
||||
* and jumps to the specified function, passing the specified argument to it.
|
||||
* Returns a fd connected to stdout in the child process, as well as a fd allowing to write
|
||||
* to the child.
|
||||
*
|
||||
* if cloneflags is 0, the default ones are passed to clone(), otherwise the value of cloneflags
|
||||
*
|
||||
* Return value: Negative on error, otherwise the file descriptor to read from*/
|
||||
int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result);
|
||||
|
||||
|
||||
/* Helper for exile_launch, to easily read all output from a function
|
||||
* This function will read all output from a sandboxed function. It's up to the caller to ensure
|
||||
* that enough memory will be available.
|
||||
*
|
||||
* The result is \0 terminated. The "n" parameter contains the size of the result, not including the \0.
|
||||
*
|
||||
* Return value: All data written by the function. The result should be passed to free() once not needed. NULL will
|
||||
* be returned on error.
|
||||
*/
|
||||
char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
199
exile.hpp
Archivo normal
199
exile.hpp
Archivo normal
@ -0,0 +1,199 @@
|
||||
#include "exile.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#ifndef EXILE_MMAP_SIZE
|
||||
#define EXILE_MMAP_SIZE 128 * 1024 * 1024 //128MB
|
||||
#endif
|
||||
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
class launch_arg
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
static_assert(!std::is_pointer_v<T>);
|
||||
|
||||
public:
|
||||
struct exile_policy *policy;
|
||||
T *result_shm;
|
||||
U fn;
|
||||
std::tuple<Args...> args;
|
||||
|
||||
launch_arg(struct exile_policy *policy, T *result_shm, U fn, Args && ... args) : policy(policy),
|
||||
result_shm(result_shm), fn(fn), args(std::forward<Args>(args)...) {}
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
class launch_arg_serializer
|
||||
{
|
||||
static_assert(std::is_copy_constructible_v<T>);
|
||||
|
||||
public:
|
||||
struct exile_policy *policy;
|
||||
char *serialize_buffer;
|
||||
size_t n;
|
||||
U fn;
|
||||
std::tuple<Args...> args;
|
||||
|
||||
const std::function<size_t (const T &, char *, size_t n)> &serializer;
|
||||
const std::function<T(const char * buf, size_t n)> &deserializer;
|
||||
|
||||
launch_arg_serializer(struct exile_policy *policy, char *serialize_buffer, size_t n, const std::function<size_t (const T &, char *, size_t)> &serializer, const std::function<T(const char *, size_t)> &deserializer, U fn, Args && ... args) : policy(policy), serialize_buffer(serialize_buffer), n(n), fn(fn), args(std::forward<Args>(args)...), serializer(serializer), deserializer(deserializer) {}
|
||||
};
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
int exile_clone_handle_trivial(void * arg)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
static_assert(!std::is_pointer_v<T>);
|
||||
|
||||
launch_arg<T, U, Args...> *launchargs = (launch_arg<T, U, Args...> *) arg;
|
||||
int ret = exile_enable_policy(launchargs->policy);
|
||||
if(ret != 0)
|
||||
{
|
||||
EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
T result = std::apply(launchargs->fn, launchargs->args);
|
||||
std::cout << result;
|
||||
memcpy(launchargs->result_shm, &result, sizeof(T));
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
int exile_clone_handle_serializer(void * arg)
|
||||
{
|
||||
static_assert(std::is_copy_constructible_v<T>);
|
||||
|
||||
launch_arg_serializer<T, U, Args...> *launchargs = (launch_arg_serializer<T, U, Args...> *) arg;
|
||||
int ret = exile_enable_policy(launchargs->policy);
|
||||
if(ret != 0)
|
||||
{
|
||||
EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
T result = std::apply(launchargs->fn, launchargs->args);
|
||||
/* TODO: exception handling */
|
||||
/* TODO: ugly :S */
|
||||
char *target = launchargs->serialize_buffer + sizeof(size_t);
|
||||
size_t n = launchargs->n - sizeof(size_t);
|
||||
|
||||
size_t size = launchargs->serializer(result, target, n);
|
||||
memcpy(launchargs->serialize_buffer, &size, sizeof(size_t));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int do_clone(int (*clonefn)(void *), void *launcharg)
|
||||
{
|
||||
struct rlimit rlimit;
|
||||
int ret = getrlimit(RLIMIT_STACK, &rlimit);
|
||||
if(ret != 0)
|
||||
{
|
||||
EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
size_t size = rlimit.rlim_cur;
|
||||
char *stack = (char *) calloc(1, size);
|
||||
if(stack == NULL)
|
||||
{
|
||||
EXILE_LOG_ERROR("Failed to allocate stack memory for child\n");
|
||||
return 1;
|
||||
}
|
||||
stack += size;
|
||||
|
||||
ret = clone(clonefn, stack, 17 /* SIGCHLD */, launcharg);
|
||||
int status = 0;
|
||||
waitpid(ret, &status, __WALL);
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
/* TODO: exception or what? */
|
||||
return 23;
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
typename std::enable_if_t<std::is_trivially_copyable_v<T>, T> exile_launch(struct exile_policy *policy, U fn, Args && ... args)
|
||||
{
|
||||
size_t mapsize = sizeof(T);
|
||||
T * sharedbuf = (T *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
|
||||
if(sharedbuf == NULL)
|
||||
{
|
||||
throw std::runtime_error(std::string("mmap failed: ") + strerror(errno));
|
||||
}
|
||||
|
||||
std::shared_ptr<void> deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); });
|
||||
launch_arg<T, U, Args...> launcharg(policy, sharedbuf, fn, std::forward<Args>(args)...);
|
||||
|
||||
int (*clonefn)(void *) = &exile_clone_handle_trivial<T, U, Args...>;
|
||||
/* TODO: exception or what? */
|
||||
int ret = do_clone(clonefn, &launcharg);
|
||||
if(ret == 0)
|
||||
{
|
||||
return *sharedbuf;
|
||||
}
|
||||
throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret)));
|
||||
return T();
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
typename std::enable_if_t<!std::is_trivially_copyable_v<T> && std::is_copy_constructible_v<T>, T>
|
||||
exile_launch(struct exile_policy *policy, const std::function<size_t (const T &, char *, size_t)> &serializer, const std::function<T(const char *, size_t)> &deserializer, U fn, Args && ... args)
|
||||
{
|
||||
size_t mapsize = EXILE_MMAP_SIZE;
|
||||
char *sharedbuf = (char *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
|
||||
if(sharedbuf == NULL)
|
||||
{
|
||||
throw std::runtime_error(std::string("mmap failed: ") + strerror(errno));
|
||||
}
|
||||
std::shared_ptr<void> deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); });
|
||||
|
||||
|
||||
launch_arg_serializer<T, U, Args...> launcharg(policy, sharedbuf, mapsize, serializer, deserializer, fn, std::forward<Args>(args)...);
|
||||
|
||||
int (*clonefn)(void *) = &exile_clone_handle_serializer<T, U, Args...>;
|
||||
/* TODO: exception or what? */
|
||||
int ret = do_clone(clonefn, &launcharg);
|
||||
if(ret == 0)
|
||||
{
|
||||
size_t size = 0;
|
||||
memcpy(&size, sharedbuf, sizeof(size));
|
||||
|
||||
return deserializer(sharedbuf + sizeof(size_t), size);
|
||||
}
|
||||
throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret)));
|
||||
return T();
|
||||
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::basic_string<typename T::value_type> deserialize_stdstring(const char *buf, size_t n)
|
||||
{
|
||||
return std::basic_string<typename T::value_type> { buf, n };
|
||||
}
|
||||
|
||||
template<class T>
|
||||
size_t serialize_stdstring(const std::basic_string<typename T::value_type> &t, char *buf, size_t n)
|
||||
{
|
||||
if(n < t.size())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
memcpy(buf, t.data(), t.size());
|
||||
return t.size();
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename U, typename ... Args>
|
||||
std::basic_string<typename T::value_type> exile_launch(struct exile_policy *policy, U fn, Args && ... args)
|
||||
{
|
||||
return exile_launch<T, U, Args...>(policy, &serialize_stdstring<T>, &deserialize_stdstring<T>, fn, std::forward<Args>(args) ...);
|
||||
}
|
587
qssb.h
587
qssb.h
@ -1,587 +0,0 @@
|
||||
#ifndef QSSB_H
|
||||
#define QSSB_H
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#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 <sys/random.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>
|
||||
#include <inttypes.h>
|
||||
#include <asm/unistd.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_UNSHARE_NETWORK 1<<1
|
||||
#define QSSB_UNSHARE_USER 1<<2
|
||||
#define QSSB_UNSHARE_MOUNT 1<<3
|
||||
|
||||
#ifndef QSSB_LOG_ERROR
|
||||
#define QSSB_LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#ifndef QSSB_TEMP_DIR
|
||||
#define QSSB_TEMP_DIR "/tmp"
|
||||
#endif
|
||||
|
||||
#define QSSB_SYS(x) (__NR_##x)
|
||||
|
||||
//TODO: implement
|
||||
#define QSSB_MOUNT_ALLOW_NOTHING 0 //explicit rule
|
||||
|
||||
#define QSSB_MOUNT_ALLOW_READ 1<<0
|
||||
#define QSSB_MOUNT_ALLOW_WRITE (1<<1) | QSSB_MOUNT_ALLOW_READ
|
||||
#define QSSB_MOUNT_ALLOW_EXEC 1<<2
|
||||
#define QSSB_MOUNT_ALLOW_DEV 1<<3
|
||||
#define QSSB_MOUNT_ALLOW_SETUID 1<<4
|
||||
//don't mount recursive
|
||||
#define QSSB_MOUNT_NOT_REC 1<<5
|
||||
|
||||
|
||||
/* Most exploits have more need for those syscalls than the
|
||||
* exploited programs. In cases they are needed, this list should be
|
||||
* filtered or simply not used.
|
||||
*/
|
||||
/* TODO: more execv* in some architectures */
|
||||
/* TODO: add more */
|
||||
static int default_blacklisted_syscals[] = {
|
||||
QSSB_SYS(setuid),
|
||||
QSSB_SYS(setgid),
|
||||
QSSB_SYS(chroot),
|
||||
QSSB_SYS(pivot_root),
|
||||
QSSB_SYS(mount),
|
||||
QSSB_SYS(setns),
|
||||
QSSB_SYS(unshare),
|
||||
QSSB_SYS(ptrace),
|
||||
QSSB_SYS(personality),
|
||||
QSSB_SYS(execve),
|
||||
-1
|
||||
};
|
||||
|
||||
struct qssb_path_policy
|
||||
{
|
||||
const char *mountpoint;
|
||||
int policy;
|
||||
struct qssb_path_policy *next;
|
||||
};
|
||||
|
||||
|
||||
/* 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[PATH_MAX];
|
||||
const char *chdir_path;
|
||||
struct qssb_path_policy *path_policies;
|
||||
};
|
||||
|
||||
|
||||
/* Creates the default policy
|
||||
* Must be freed using qssb_free_policy
|
||||
* @returns: default policy */
|
||||
struct qssb_policy *qssb_init_policy()
|
||||
{
|
||||
struct qssb_policy *result = (struct qssb_policy *) calloc(1, sizeof(struct qssb_policy));
|
||||
result->blacklisted_syscalls = default_blacklisted_syscals;
|
||||
result->drop_caps = 1;
|
||||
result->not_dumpable = 1;
|
||||
result->no_new_privs = 1;
|
||||
result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER;
|
||||
result->chdir_path = NULL;
|
||||
result->chroot_target_path[0] = '\0';
|
||||
result->path_policies = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fills buffer with random characters a-z.
|
||||
* The string will be null terminated.
|
||||
*
|
||||
* @returns: number of written chars (excluding terminating null byte) on success
|
||||
*/
|
||||
int random_string(char *buffer, size_t buffer_length)
|
||||
{
|
||||
int r = getrandom(buffer, buffer_length-1, GRND_NONBLOCK);
|
||||
if(r != -1 && (size_t) r == buffer_length-1)
|
||||
{
|
||||
int i = 0;
|
||||
while(i < r)
|
||||
{
|
||||
buffer[i] = 'a' + ((unsigned int)buffer[i] % 26);
|
||||
++i;
|
||||
}
|
||||
buffer[buffer_length-1] = '\0';
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* 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] = { 0 };
|
||||
int res = snprintf(path, sizeof(path), "%s/", p);
|
||||
if(res < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("qssb: mkdir_strucutre: error during path concatination\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if(res >= PATH_MAX)
|
||||
{
|
||||
QSSB_LOG_ERROR("qssb: mkdir_structure: path concatination truncated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* @returns: argument for mount(2) flags */
|
||||
static int get_policy_mount_flags(struct qssb_path_policy *policy)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if( (policy->policy & QSSB_MOUNT_ALLOW_DEV) == 0)
|
||||
{
|
||||
result |= MS_NODEV;
|
||||
}
|
||||
|
||||
if( (policy->policy & QSSB_MOUNT_ALLOW_EXEC) == 0)
|
||||
{
|
||||
result |= MS_NOEXEC;
|
||||
}
|
||||
|
||||
if( (policy->policy & QSSB_MOUNT_ALLOW_SETUID) == 0)
|
||||
{
|
||||
result |= MS_NOSUID;
|
||||
}
|
||||
|
||||
if( ((policy->policy) & (QSSB_MOUNT_ALLOW_WRITE)) == QSSB_MOUNT_ALLOW_READ)
|
||||
{
|
||||
result |= MS_RDONLY;
|
||||
}
|
||||
|
||||
if( !(policy->policy & QSSB_MOUNT_NOT_REC))
|
||||
{
|
||||
result |= MS_REC;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 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, struct qssb_path_policy *path_policy)
|
||||
{
|
||||
while(path_policy != NULL)
|
||||
{
|
||||
|
||||
char path_inside_chroot[PATH_MAX];
|
||||
int written = snprintf(path_inside_chroot, sizeof(path_inside_chroot), "%s/%s", chroot_target_path, path_policy->mountpoint);
|
||||
if(written < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("qssb: mount_to_chroot: Error during path concatination\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if(written >= PATH_MAX)
|
||||
{
|
||||
QSSB_LOG_ERROR("qssb: mount_to_chroot: path concatination truncated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
int mount_flags = get_policy_mount_flags(path_policy);
|
||||
|
||||
//all we do is bind mounts
|
||||
mount_flags |= MS_BIND;
|
||||
|
||||
|
||||
if(path_policy->policy & QSSB_MOUNT_ALLOW_READ)
|
||||
{
|
||||
ret = mount(path_policy->mountpoint, path_inside_chroot, NULL, mount_flags, NULL);
|
||||
if(ret < 0 )
|
||||
{
|
||||
QSSB_LOG_ERROR("Error: Failed to mount %s to %s: %s\n", path_policy->mountpoint, path_inside_chroot, strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
//remount so noexec, readonly etc. take effect
|
||||
ret = mount(NULL, path_inside_chroot, NULL, mount_flags | MS_REMOUNT, NULL);
|
||||
if(ret < 0 )
|
||||
{
|
||||
QSSB_LOG_ERROR("Error: Failed to remount %s: %s", path_inside_chroot, strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
path_policy = path_policy->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ends the policy as best as possible. */
|
||||
/* TODO: can this function do actually anything useful?*/
|
||||
int qssb_end_policy(struct qssb_policy *ctxt)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees the memory taken by a qssb_policy object
|
||||
*/
|
||||
void qssb_free_policy(struct qssb_policy *ctxt)
|
||||
{
|
||||
free(ctxt);
|
||||
}
|
||||
|
||||
/* Enters the specified namespaces */
|
||||
static int enter_namespaces(int namespace_options)
|
||||
{
|
||||
if(namespace_options & QSSB_UNSHARE_USER)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if(namespace_options & QSSB_UNSHARE_MOUNT)
|
||||
{
|
||||
int ret = unshare(CLONE_NEWNS);
|
||||
if(ret == -1)
|
||||
{
|
||||
QSSB_LOG_ERROR("Error: Failed to unshare mount namespaces: %s\n", strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if(namespace_options & QSSB_UNSHARE_NETWORK)
|
||||
{
|
||||
int ret = unshare(CLONE_NEWNET);
|
||||
if(ret == -1)
|
||||
{
|
||||
QSSB_LOG_ERROR("Error: Failed to unshare network namespace: %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, unsigned int per_syscall, unsigned int default_action)
|
||||
{
|
||||
struct sock_filter filter[1024] =
|
||||
{
|
||||
LOAD_SYSCALL_NR,
|
||||
};
|
||||
|
||||
unsigned short int current_filter_index = 1;
|
||||
while(*syscalls >= 0)
|
||||
{
|
||||
unsigned int sysc = (unsigned int) *syscalls;
|
||||
struct sock_filter syscall = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, sysc, 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, default_action);
|
||||
filter[current_filter_index] = da;
|
||||
|
||||
++current_filter_index;
|
||||
struct sock_fprog prog = {
|
||||
.len = current_filter_index ,
|
||||
.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(enter_namespaces(policy->namespace_options) < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("Error while trying to enter namespaces\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(policy->path_policies != NULL)
|
||||
{
|
||||
if(*policy->chroot_target_path == '\0')
|
||||
{
|
||||
char random_str[17];
|
||||
if(random_string(random_str, sizeof(random_str)) == 16)
|
||||
{
|
||||
int res = snprintf(policy->chroot_target_path, sizeof(policy->chroot_target_path), "%s/.sandbox_%" PRIdMAX "_%s", QSSB_TEMP_DIR, (intmax_t)getpid(), random_str);
|
||||
if(res < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("qssb: qssb_enable_policy: error during path concatination\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if(res >= PATH_MAX)
|
||||
{
|
||||
QSSB_LOG_ERROR("qssb: qssb_enable_policy: path concatination truncated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QSSB_LOG_ERROR("Error creating random sandbox directory name\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(mount_to_chroot(policy->chroot_target_path, policy->path_policies) < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("mount_to_chroot: setup of path policies failed\n");
|
||||
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(policy->chdir_path == NULL)
|
||||
{
|
||||
policy->chdir_path = "/";
|
||||
}
|
||||
}
|
||||
|
||||
if(policy->chdir_path != NULL && chdir(policy->chdir_path) < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("chdir to %s failed\n", policy->chdir_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(policy->drop_caps)
|
||||
{
|
||||
if(drop_caps() < 0)
|
||||
{
|
||||
QSSB_LOG_ERROR("failed to drop capabilities\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
702
test.c
Archivo normal
702
test.c
Archivo normal
@ -0,0 +1,702 @@
|
||||
#include "exile.h"
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define LOG(...) do { fprintf(stdout, "%s(): ", __func__); fprintf(stdout, __VA_ARGS__); } while(0)
|
||||
|
||||
int xexile_enable_policy(struct exile_policy *policy)
|
||||
{
|
||||
int ret = exile_enable_policy(policy);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("failed: %i\n", ret);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_default_main()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
return xexile_enable_policy(policy);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LOG("Got expected signal\n");
|
||||
return 0;
|
||||
}
|
||||
LOG("Unexpected status code: %i\n", c);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = WEXITSTATUS(status);
|
||||
LOG("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);
|
||||
LOG("Received signal, which was not expected. Signal was: %i\n", c);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = WEXITSTATUS(status);
|
||||
if(c != 0)
|
||||
{
|
||||
LOG("Process failed to exit properly. Status code is: %i\n", c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
LOG("Process exited sucessfully as expected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int do_test_seccomp_blacklisted()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
exile_append_syscall_policy(policy,EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
uid_t pid = syscall(EXILE_SYS(geteuid));
|
||||
pid = syscall(EXILE_SYS(getuid));
|
||||
return 0;
|
||||
|
||||
|
||||
}
|
||||
int test_seccomp_blacklisted()
|
||||
{
|
||||
return test_expected_kill(&do_test_seccomp_blacklisted);
|
||||
}
|
||||
|
||||
|
||||
static int do_test_seccomp_blacklisted_call_permitted()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
|
||||
xexile_enable_policy(policy);
|
||||
//geteuid is not blacklisted, so must succeed
|
||||
uid_t pid = syscall(EXILE_SYS(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 exile_policy *policy = exile_init_policy();
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
/* Attempt to bypass by falling back to x32 should be blocked */
|
||||
syscall(EXILE_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 exile_policy *policy = exile_init_policy();
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(getuid), EXILE_SYSCALL_DENY_KILL_PROCESS, NULL, 0);
|
||||
|
||||
int status = exile_enable_policy(policy);
|
||||
if(status == 0)
|
||||
{
|
||||
LOG("Failed. Should not have been enabled!");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_test_seccomp_errno()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(close),EXILE_SYSCALL_DENY_RET_ERROR, NULL, 0);
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
|
||||
xexile_enable_policy(policy);
|
||||
uid_t id = syscall(EXILE_SYS(getuid));
|
||||
|
||||
int fd = syscall(EXILE_SYS(close), 0);
|
||||
LOG("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_seccomp_argfilter_allowed()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
|
||||
struct sock_filter argfilter[2] =
|
||||
{
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
|
||||
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
|
||||
};
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(open),EXILE_SYSCALL_DENY_RET_ERROR, argfilter, 2);
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
|
||||
char *t = "/dev/random";
|
||||
int ret = (int) syscall(EXILE_SYS(open),t, O_RDONLY);
|
||||
|
||||
if(ret == -1)
|
||||
{
|
||||
printf("Failed: open was expected to succeed, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_seccomp_argfilter_filtered()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
|
||||
struct sock_filter argfilter[2] =
|
||||
{
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
|
||||
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
|
||||
};
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(open),EXILE_SYSCALL_DENY_RET_ERROR, argfilter, 2);
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
char *t = "/dev/random";
|
||||
int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY);
|
||||
|
||||
if(ret != -1)
|
||||
{
|
||||
printf("Failed: open was expected to fail, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int test_seccomp_argfilter_mixed()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
|
||||
struct sock_filter argfilter[2] =
|
||||
{
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
|
||||
BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, O_WRONLY, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH)
|
||||
};
|
||||
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(stat),EXILE_SYSCALL_DENY_RET_ERROR, NULL,0);
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(open),EXILE_SYSCALL_DENY_RET_ERROR, argfilter, 2);
|
||||
exile_append_syscall_policy(policy, EXILE_SYS(getpid),EXILE_SYSCALL_DENY_RET_ERROR, NULL, 0);
|
||||
|
||||
exile_append_syscall_default_policy(policy, EXILE_SYSCALL_ALLOW);
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
struct stat statbuf;
|
||||
int s = (int) syscall(EXILE_SYS(stat), "/dev/urandom", &statbuf);
|
||||
if(s != -1)
|
||||
{
|
||||
LOG("Failed: stat was expected to fail, but returned %i\n", s);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pid_t p = (pid_t) syscall(EXILE_SYS(getpid));
|
||||
if(p != -1)
|
||||
{
|
||||
LOG("Failed: getpid was expected to fail, but returned %i\n", p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *t = "/dev/random";
|
||||
int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY);
|
||||
if(ret != -1)
|
||||
{
|
||||
LOG("Failed: open was expected to fail, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
ret = (int) syscall(EXILE_SYS(open), t, O_RDONLY);
|
||||
if(ret == -1)
|
||||
{
|
||||
LOG("Failed: open with O_RDONLY was expected to succeed, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int do_test_seccomp_vow_socket()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
policy->vow_promises = EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_DENY_ERROR;
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(s == -1)
|
||||
{
|
||||
LOG("Failed: socket was expected to succeed, but returned %i\n", s);
|
||||
return 1;
|
||||
}
|
||||
s = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||
if(s != -1)
|
||||
{
|
||||
LOG("Failed: socket was expected to fail, but returned %i\n", s);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_test_seccomp_vow_open()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
policy->vow_promises = EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_DENY_ERROR;
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
int ret = open("/dev/urandom", O_WRONLY | O_APPEND);
|
||||
if(ret != -1)
|
||||
{
|
||||
LOG("Failed: open was expected to fail, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
ret = open("/dev/urandom", O_RDWR);
|
||||
if(ret != -1)
|
||||
{
|
||||
LOG("Failed: open O_RDWR was expected to fail, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
ret = open("/dev/urandom", O_RDONLY);
|
||||
if(ret == -1)
|
||||
{
|
||||
LOG("Failed: open was expected to succceed, but returned %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_seccomp_vow()
|
||||
{
|
||||
int ret = test_successful_exit(&do_test_seccomp_vow_open);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: do_test_seccomp_vow_open()\n");
|
||||
return 1;
|
||||
}
|
||||
ret = test_successful_exit(&do_test_seccomp_vow_socket);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: do_test_seccomp_vow_socket()\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_seccomp_exile_vow_multiple()
|
||||
{
|
||||
|
||||
int ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: exile_vow() call 1 failed\n");
|
||||
return 1;
|
||||
}
|
||||
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if(s == -1)
|
||||
{
|
||||
LOG("Failed: socket was expected to succeed, but returned %i\n", s);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Let's take away unix sockets, so it should not be possible anymore */
|
||||
ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: exile_vow() call 2 failed\n");
|
||||
return 1;
|
||||
}
|
||||
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if(s != -1)
|
||||
{
|
||||
LOG("Failed: socket was expected to fail, but returned %i\n", s);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Let's try to regain unix sockets again */
|
||||
ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: exile_vow() call 3 failed\n");
|
||||
return 1;
|
||||
}
|
||||
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if(s != -1)
|
||||
{
|
||||
LOG("Failed: socket was still expected to fail, but returned %i\n", s);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if HAVE_LANDLOCK == 1
|
||||
int test_landlock()
|
||||
{
|
||||
if(!exile_landlock_is_available())
|
||||
{
|
||||
LOG("landlock not available, so cannot test\n");
|
||||
return 1;
|
||||
}
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/proc/self/fd");
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
int fd = open("/", O_RDONLY | O_CLOEXEC);
|
||||
if(fd < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int test_landlock_deny_write()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/tmp/");
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
int fd = open("/tmp/a", O_WRONLY | O_CLOEXEC);
|
||||
if(fd < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
int test_landlock()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
int test_landlock_deny_write()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
int test_nofs()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
policy->no_fs = 1;
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
int s = socket(AF_INET,SOCK_STREAM,0);
|
||||
if(s == -1)
|
||||
{
|
||||
LOG("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)
|
||||
{
|
||||
LOG("Failed: We do not expect write access\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int test_no_new_fds()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
policy->no_new_fds = 1;
|
||||
xexile_enable_policy(policy);
|
||||
|
||||
if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0)
|
||||
{
|
||||
LOG("Failed: Could open new file descriptor\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s = socket(AF_INET,SOCK_STREAM,0);
|
||||
if(s >= 0)
|
||||
{
|
||||
LOG("Failed: socket got opened but policy denied\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
extern int mkpath(const char *p, mode_t mode, int baseisfile);
|
||||
int test_mkpath()
|
||||
{
|
||||
system("rm -rf /tmp/.exile.h/");
|
||||
const char *filepath = "/tmp/.exile.h/test_mkpath/some/sub/dir/file";
|
||||
const char *dirpath = "/tmp/.exile.h/test_mkpath/some/other/sub/dir";
|
||||
int ret = mkpath(filepath, 0700, 1);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: mkpath(file) returned: %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
ret = mkpath(dirpath, 0700, 0);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: mkpath(dirpath) returned: %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct stat statbuf;
|
||||
ret = stat(filepath, &statbuf);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: stat on filepath returned: %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
if(!S_ISREG(statbuf.st_mode))
|
||||
{
|
||||
LOG("Failed: mkpath did not create a file: %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
ret = stat(dirpath, &statbuf);
|
||||
if(ret != 0)
|
||||
{
|
||||
LOG("Failed: stat on dirpath returned: %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
if(!S_ISDIR(statbuf.st_mode))
|
||||
{
|
||||
LOG("Failed: mkpath did not create a directory: %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
system("rm -rf /tmp/.exile.h/");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_fail_flags()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/nosuchpathexists");
|
||||
int ret = exile_enable_policy(policy);
|
||||
if(ret == 0)
|
||||
{
|
||||
fprintf(stderr, "Failed: A path that does not exist should have set the error flag %i\n", ret);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int *read_pipe = NULL;
|
||||
int do_launch_test(void *arg)
|
||||
{
|
||||
int num = *(int *)(arg);
|
||||
num += 1;
|
||||
char buffer[512] = { 0 };
|
||||
read(*read_pipe, buffer, sizeof(buffer)-1);
|
||||
printf("Sandboxed +1: %i\n", num);
|
||||
printf("Echoing: %s\n", buffer);
|
||||
fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_launch()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
struct exile_launch_params params = { 0 };
|
||||
struct exile_launch_result res = {0};
|
||||
int num = 22;
|
||||
params.func = &do_launch_test;
|
||||
params.funcarg = #
|
||||
params.policy = policy;
|
||||
read_pipe = ¶ms.child_write_pipe[0];
|
||||
int launchfd = exile_launch(¶ms, &res);
|
||||
if(launchfd < 0)
|
||||
{
|
||||
LOG("Failed to launch\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char buffer[4096] = { 0 };
|
||||
write(res.write_fd, "1234", 4);
|
||||
int s = read(res.read_fd, buffer, sizeof(buffer)-1);
|
||||
write(1, buffer, s);
|
||||
LOG("Before wait, got: %i\n", s);
|
||||
fflush(stdout);
|
||||
if(strstr(buffer, "Echoing: 1234") == NULL)
|
||||
{
|
||||
LOG("Failed: Did not get back what we wrote\n");
|
||||
}
|
||||
int status = 0;
|
||||
waitpid(res.tid, &status, __WALL);
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
status = WEXITSTATUS(status);
|
||||
return status;
|
||||
}
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
#define LAUNCH_GET_TEST_STR "Control yourself. Take only what you need from it.\n"
|
||||
int do_launch_get_test(void *a)
|
||||
{
|
||||
fprintf(stdout, LAUNCH_GET_TEST_STR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_launch_get()
|
||||
{
|
||||
struct exile_policy *policy = exile_init_policy();
|
||||
struct exile_launch_params params = { 0 };
|
||||
params.func = &do_launch_get_test;
|
||||
params.funcarg = NULL;
|
||||
params.policy = policy;
|
||||
|
||||
size_t n = 0;
|
||||
char *content = exile_launch_get(¶ms, &n);
|
||||
unsigned int len = strlen(LAUNCH_GET_TEST_STR);
|
||||
if(n != strlen(LAUNCH_GET_TEST_STR))
|
||||
{
|
||||
LOG("Lenght does does not match: %lu vs %u\n", n, len);
|
||||
return 1;
|
||||
}
|
||||
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
|
||||
{
|
||||
LOG("Received content differs\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_vows_from_str()
|
||||
{
|
||||
uint64_t expected = EXILE_SYSCALL_VOW_CHOWN | EXILE_SYSCALL_VOW_WPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_DENY_ERROR;
|
||||
uint64_t actual = exile_vows_from_str("chown wpath inet error");
|
||||
if(expected != actual)
|
||||
{
|
||||
LOG("Masks don't match: %lu vs %lu\n", expected, actual);
|
||||
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},
|
||||
{ "seccomp-argfilter-allowed", &test_seccomp_argfilter_allowed},
|
||||
{ "seccomp-argfilter-filtered", &test_seccomp_argfilter_filtered},
|
||||
{ "seccomp-argfilter-mixed", &test_seccomp_argfilter_mixed},
|
||||
{ "seccomp-vow", &test_seccomp_vow},
|
||||
{ "seccomp-vow-exile_vow-multi", &test_seccomp_exile_vow_multiple},
|
||||
{ "landlock", &test_landlock},
|
||||
{ "landlock-deny-write", &test_landlock_deny_write },
|
||||
{ "no_fs", &test_nofs},
|
||||
{ "no_new_fds", &test_no_new_fds},
|
||||
{ "mkpath", &test_mkpath},
|
||||
{ "failflags", &test_fail_flags},
|
||||
{ "launch", &test_launch},
|
||||
{ "launch-get", &test_launch_get},
|
||||
{ "vow_from_str", &test_vows_from_str},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
92
test.cpp
Archivo normal
92
test.cpp
Archivo normal
@ -0,0 +1,92 @@
|
||||
#include "exile.hpp"
|
||||
#include "assert.h"
|
||||
#include <map>
|
||||
|
||||
std::string sandboxed_reverse(std::string str)
|
||||
{
|
||||
std::reverse(str.begin(), str.end());
|
||||
return str;
|
||||
}
|
||||
|
||||
size_t stdstrlen(const std::string &str)
|
||||
{
|
||||
return str.size();
|
||||
}
|
||||
|
||||
int incrementer(int arg)
|
||||
{
|
||||
return ++arg;
|
||||
}
|
||||
int test_exile_launch_trivial()
|
||||
{
|
||||
int u = 22;
|
||||
int result = exile_launch<int>(exile_init_policy(), &incrementer, u);
|
||||
assert(result == 23);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_exile_launch_stdstring()
|
||||
{
|
||||
std::string str = "abc123";
|
||||
std::string reversed = exile_launch<std::string>(exile_init_policy(), &sandboxed_reverse, str);
|
||||
assert(reversed == "321cba");
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct not_trivially_copyable
|
||||
{
|
||||
public:
|
||||
std::string somecontent;
|
||||
};
|
||||
|
||||
int test_exile_launch_serializer()
|
||||
{
|
||||
static_assert(! std::is_trivially_copyable_v<not_trivially_copyable>);
|
||||
|
||||
auto serializer = [](const not_trivially_copyable &obj, char *buf, size_t n){
|
||||
serialize_stdstring<std::string>(obj.somecontent, buf, n);
|
||||
return obj.somecontent.size();
|
||||
};
|
||||
|
||||
auto deserializer = [](const char *buffer, size_t n) {
|
||||
not_trivially_copyable obj;
|
||||
obj.somecontent = deserialize_stdstring<std::string>(buffer, n);
|
||||
return obj;
|
||||
};
|
||||
|
||||
not_trivially_copyable result = exile_launch<not_trivially_copyable>(exile_init_policy(), serializer, deserializer, []() {not_trivially_copyable obj; obj.somecontent = "Just something"; return obj;});
|
||||
|
||||
assert(result.somecontent == "Just something");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if(argc < 2)
|
||||
{
|
||||
std::cerr << "Missing test" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::map<std::string, int (*)()> map = {
|
||||
{ "launch-trivial-cpp", &test_exile_launch_trivial} ,
|
||||
{ "launch-stdstring-cpp", &test_exile_launch_stdstring },
|
||||
{ "launch-serializer-cpp", &test_exile_launch_serializer },
|
||||
};
|
||||
|
||||
std::string test = argv[1];
|
||||
if(test == "--dumptests")
|
||||
{
|
||||
for(auto &entry : map)
|
||||
{
|
||||
std::cout << entry.first << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int (*fn)() = map[test];
|
||||
if(fn != nullptr)
|
||||
{
|
||||
return fn();
|
||||
}
|
||||
std::cerr << "Unknown test" << std::endl;
|
||||
return 1;
|
||||
}
|
99
test.sh
Archivo ejecutable
99
test.sh
Archivo ejecutable
@ -0,0 +1,99 @@
|
||||
#!/bin/sh
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
COUNT_SUCCEEDED=0
|
||||
COUNT_FAILED=0
|
||||
COUNT_SKIPPED=0
|
||||
|
||||
function print_fail()
|
||||
{
|
||||
echo -e "${RED}$@${NC}" 1>&2
|
||||
}
|
||||
|
||||
function print_success()
|
||||
{
|
||||
echo -e "${GREEN}$@${NC}"
|
||||
}
|
||||
|
||||
function print_skipped()
|
||||
{
|
||||
echo -e "${YELLOW}$@${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_skipped()
|
||||
{
|
||||
print_skipped "skipped"
|
||||
COUNT_SKIPPED=$((COUNT_SKIPPED+1))
|
||||
}
|
||||
|
||||
|
||||
function runtest()
|
||||
{
|
||||
testbin="$1"
|
||||
testname="$2"
|
||||
test_log_file="$3"
|
||||
|
||||
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
|
||||
|
||||
echo -n "Running $testname... "
|
||||
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
|
||||
(./$testbin "$testname" || exit $?) &>> "${test_log_file}"
|
||||
ret=$?
|
||||
SUCCESS="no"
|
||||
if [ $ret -eq 0 ] ; then
|
||||
runtest_success
|
||||
SUCCESS="yes"
|
||||
elif [ $ret -eq 2 ] ; then
|
||||
runtest_skipped
|
||||
SUCCESS="skipped"
|
||||
else
|
||||
runtest_fail
|
||||
fi
|
||||
|
||||
echo "Finished: ${testname} (${testbin}). 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}/exile_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 test "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
|
||||
done
|
||||
|
||||
for test in $( ./testcpp --dumptests ) ; do
|
||||
testname=$( echo $test )
|
||||
runtest testcpp "$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"
|
||||
echo "Skipped: $COUNT_SKIPPED"
|
||||
|
||||
if [ $COUNT_FAILED -gt 0 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
Referencia en una nueva incidencia
Block a user