Commitok összehasonlítása

43 Commit-ok

Szerző SHA1 Üzenet Dátum
42d44b0cc1 README.md: Minor improvements throughout the file 2022-06-06 14:07:37 +02:00
bd3641981c Introduce EXILE_SYSCALL_DENY_RET_NOSYS for syscalls like clone3()
clone3() is used more and more, but we cannot filter it. We can either
allow it fully or return ENONYS. Some libraries perform fallbacks to the
older clone() in that case, which we can filter again.
2022-06-06 14:07:37 +02:00
bbbdfc44da exile.hpp: do_clone(): free stack memory 2022-05-29 19:25:53 +02:00
2dc61828f1 README: Clarify limitations 2022-04-29 21:25:21 +02:00
cdc265cedf c++: exile_launch(): Correct std::enable_if logic if type is a ptr 2022-04-29 21:23:53 +02:00
91858efa51 vows map: Add memfd_create, rseq 2022-04-22 08:37:34 +02:00
88995d214d README.md: Minor improvements (typos, rephrasing) 2022-04-07 00:04:52 +02:00
6eb47daf84 README: Update Debian section 2022-03-28 19:25:55 +02:00
8bf87717a5 vows: ioctl: Make TIOCSTI illegal even when IOCTL vow is set 2022-03-28 19:14:02 +02:00
bcaefffbe8 Improve various error messages 2022-03-28 19:04:28 +02:00
ed5098f2c6 README: Begin demo section 2022-03-17 17:10:38 +01:00
ea66ef76eb exile_flags_to_landlock(): Cover more with ALL_WRITE, except devices
More consistent with mount(), where MS_NODEV disallows those.

We may need to introduce a flag that simply allows everything
2022-03-17 15:47:22 +01:00
66def7a28f append_syscall_to_bpf(): Check for unlikely case of too many sock_filters 2022-03-17 15:47:22 +01:00
dbf8e87440 exile.hpp: Mark do_clone inline, not static 2022-03-17 15:47:22 +01:00
98421fab90 Makefile: Build exile.o separately, link it in all tests 2022-03-17 15:47:22 +01:00
70c3fef500 exile.h: Retire static child_read/write_pipe vars 2022-03-17 15:47:22 +01:00
69829374c7 exile.h: Move definitions to new file exile.c
Especially with exile_launch(), we will be included
from more than one translation unit. Thus, ODR becomes
a headache now.

So move definitions to exile.c.
2022-03-17 15:47:22 +01:00
005851c645 exile.h: Add extern "C" guards 2022-03-17 15:47:22 +01:00
95fa11e928 c++: Add explicit exile_launch() std::basic_string variant 2022-03-17 15:47:22 +01:00
97e2025758 c++: Retire exile_launch_trivial(), use std::enable_if 2022-03-17 15:47:22 +01:00
8cfb73568a Makefile: Add 'tests' target, depend on headers too to rebuild on changes of those 2022-03-17 15:47:22 +01:00
e7a5ba7f7f test.sh: Also run C++ tests 2022-03-17 15:47:22 +01:00
e52eda186b Add test.cpp to test C++ API 2022-03-17 15:47:22 +01:00
90ed5bbae9 Begin C++ API: Add exile.hpp with exile_launch() wrappers 2022-03-17 15:47:22 +01:00
48b6de9036 struct syscall_vow_map: change 'str' to const char* 2022-03-17 15:47:22 +01:00
93acb13929 test: Introduce LOG(), avoid inconsistent printf/fprintf 2022-03-17 15:47:22 +01:00
9247a6636b Introduce exile_vows_from_str() 2022-03-17 15:47:22 +01:00
4a3ac8e0bc exile_launch(): Improve handling/logging of errors 2022-01-16 21:46:11 +01:00
ed54575b89 exile_launch(): Open another pipe to also write to child 2022-01-16 21:46:11 +01:00
0caff45600 EXILE_LOG_ERROR: Prepend function name 2022-01-16 21:46:11 +01:00
080c0e53c2 test: test_mkpath(): Cleanup before run and on success 2022-01-16 21:46:11 +01:00
4adc13215b exile_append_path_policies(): Add sentinel macro, making *policy() version redundant 2022-01-16 21:46:11 +01:00
bf29edf213 Update README with most recent draft 2022-01-16 21:46:11 +01:00
68bfd7e66c Update copyright header 2022-01-16 21:46:11 +01:00
58bc50db61 test: Begin testing exile_launch*() 2022-01-16 21:46:11 +01:00
1e63fa75ef Introduce exile_launch*(): Simplifies launching functions protected by policy
Those functions clone(), then activate the specified policy.
They then jump to the supplied function and pass an argument to it.

exile_launch() returns a read file descriptor, that can be
used by the parent process to get the data.

exile_launch_get() is a convenience wrapper, return a buffer
containing everything read from the sandboxed function.
2022-01-16 21:46:11 +01:00
6c44c88397 create_chroot_dirs(): Correct comment 2022-01-16 21:46:11 +01:00
3780509078 Introduce flags indicating errors to catch non-checked return codes
Certain functions can fail before we execute exile_enable_policy().

While the return code should be checked, it's easily forgotten. For
most users, checking just the exile_enable_policy() return code
should suffice.

exile_append_path_policies(): Add check whether a path exists. If not,
set the error flag.

This also allows an early exit, allowing to cleanly handle the case
when a path does not exist. Previously, this was only caught
during activation, and a failure there is generally undefined.
2022-01-16 21:46:11 +01:00
fd4dfb12f0 vow: Add prlimit64(),arch_prctl() 2022-01-16 21:46:11 +01:00
a9e6b3ee67 chroot: Create all paths first, then mount
We mounted after creating dirs, this was potentially problematic
for the next path policy to follow.

Perform two passes on the path_policies list, first creates all
dirs, second does the mounts.
2022-01-16 21:46:11 +01:00
3b61e90761 test: Add mkpath() test 2022-01-16 20:38:03 +01:00
0e27b19999 Handle files for bind-mounts too, rename mkdir_structure() to mkpath() 2022-01-16 20:38:03 +01:00
ff70142e04 exile_flags_to_landlock(): Only add flags for a path that a reasonable 2022-01-08 12:19:31 +01:00
8 fájl változott, egészen pontosan 2673 új sor hozzáadva és 1517 régi sor törölve

Fájl megtekintése

@ -1,17 +1,27 @@
prefix = /usr/local
bindir = $(prefix)/bin
CFLAGS = -std=c99 -Wall -Wextra -pedantic
CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic
.DEFAULT_GOAL := test
.DEFAULT_GOAL := tests
clean:
rm -f test
rm -f test exile.o testcpp
test: test.c
$(CC) test.c -g $(CFLAGS) -o test
check: test
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

207
README.md
Fájl megtekintése

@ -1,52 +1,217 @@
# exile.h
`exile.h` is a simple header-only library that provides an interface to isolate 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.
`exile.h` is a header-only library, enabling processes to easily isolate themselves on Linux for exploit mitigation purposes. 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.
The following section offers small examples. Then the motivation is explained in more detail.
Proper API documentation will be maintained in other files.
## Quick demo
This section quickly demonstrates the simplicity of the API. It serves as an overview to get
a first impression.
system() is used to keep the example C code short. It also demonstrates that subprocesses are also subject to restrictions imposed by exile.h.
While the examples show different features separately, it is generally possible to combine those.
### Filesystem isolation
```c
#include "exile.h"
#include <assert.h>
int main(void)
{
system("echo test > /home/user/testfile");
struct exile_policy *policy = exile_init_policy();
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/home/user");
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, "/tmp");
int ret = exile_enable_policy(policy);
if(ret != 0)
{
exit(EXIT_FAILURE);
}
int fd = open("/home/user/test", O_CREAT | O_WRONLY | O_TRUNC, 0600);
assert(fd == -1);
fd = open("/home/user/testfile", O_RDONLY);
//use fd
assert(fd != -1);
fd = open("/tmp/testfile", O_CREAT | O_WRONLY | O_TRUNC, 0600);
//use fd
assert(fd != -1);
return 0;
}
```
The assert() calls won't be fired, consistent with the policy.
### System call policies / vows
exile.h allows specifying which syscalls are permitted or denied. In the following example,
ls is never executed, as the specified "vows" do not allow the execve() system call. The
process will be killed.
```c
#include "exile.h"
int main(void)
{
struct exile_policy *policy = exile_init_policy();
policy->vow_promises = exile_vows_from_str("stdio rpath wpath cpath");
exile_enable_policy(policy);
printf("Trying to execute...");
execlp("/bin/ls", "ls", "/", NULL);
}
```
### Isolation from network
exile offers a quick way to isolate a process from the default network namespace.
```c
#include "exile.h"
int main(void)
{
struct exile_policy *policy = exile_init_policy();
policy->namespace_options |= EXILE_UNSHARE_NETWORK;
int ret = exile_enable_policy(policy);
if(ret != 0)
{
exit(EXIT_FAILURE);
}
system("curl -I https://evil.tld");
}
```
Produces ```curl: (6) Could not resolve host: evil.tld```. For example, this is useful for subprocesses which do not need
network access, but perform tasks such as parsing user-supplied file formats.
### Isolation of single functions
Currently, work is being done that hopefully will allow isolation of individual function calls in a mostly pain-free manner.
Consider the following C++ code:
```cpp
#include <iostream>
#include <fstream>
#include "exile.hpp"
std::string cat(std::string path)
{
std::fstream f1;
f1.open(path.c_str(), std::ios::in);
std::string content;
std::string line;
while(getline(f1, line)) {
content += line + "\n";
}
return content;
}
int main(void)
{
struct exile_policy *policy = exile_init_policy();
policy->vow_promises = exile_vows_from_str("stdio rpath");
std::string content = exile_launch<std::string>(policy, cat, "/etc/hosts");
std::cout << content;
policy = exile_init_policy();
policy->vow_promises = exile_vows_from_str("stdio");
try
{
content = exile_launch<std::string>(policy, cat, "/etc/hosts");
std::cout << content;
}
catch(std::exception &e)
{
std::cout << "launch failure: " << e.what() << std::endl;
}
}
```
We execute "cat()". The first call succeeds. In the second, we get an exception, because
the subprocess "cat()" was launched in violated the policy (missing "rpath" vow).
Naturally, there is a performance overhead. Certain challenges remain, such as the fact
that being executed in a subprocess, we operate on copies, so handling references
is not something that has been given much thought. There is also the fact
that clone()ing from threads opens a can of worms, particularly with locks. Hence, exile_launch()
is best avoided in multi-threaded contexts.
## Status
No release yet, expiremental, API is unstable, builds will break on updates of this library.
No release yet, experimental, API is unstable, builds will break on updates of this library.
Currently, it's mainly evolving according to the needs of my other projects.
Currently, it's mainly evolving from the needs of my other projects.
## Motivation and Background
exile.h unlocks existing Linux mechanisms to facilitate 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 restricting the 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.
## 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
- Dropping privileges in general, such as capabilities
- Isolating the application from the network, etc. through Namespaces
- Helpers to isolate single functions
- Systemcall filtering (using seccomp-bpf)
- restricting file system access (using Landlock and/or Namespaces)
- dropping privileges
- isolating the application from the network, etc.
## 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. This allows a more fine-grained approach, as the developers are more familiar with their software. Applying restrictions with solutions like AppArmor requires
them to be present and installed 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 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.
While mostly transparent to users of this API, kernel >= 5.13 is required to take advantage of Landlock. 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
### Does the process need to be priviliged to utilize the library?
### Does the process need to be privileged to utilize the library?
No.
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
### It doesn't work on my Debian version!
You can thank a Debian-specific kernel patch for that. Execute
`echo 1 > /proc/sys/kernel/unprivileged_userns_clone` to disable that patch for now.
### Examples
Note that newer releases should not cause this problem any longer, as [explained](https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#linux-user-namespaces) in the Debian release notes.
### Real-world usage
- 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
### Other projects
- [sandbox2](https://developers.google.com/code-sandboxing/sandbox2/)
### Contributing
Contributions are very welcome. Options:
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.

1864
exile.c Normal file

A különbségek nem kerülnek megjelenítésre, mivel a fájl túl nagy Load Diff

1540
exile.h

A különbségek nem kerülnek megjelenítésre, mivel a fájl túl nagy Load Diff

201
exile.hpp Normal file
Fájl megtekintése

@ -0,0 +1,201 @@
#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);
char *stackbegin = stack;
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);
free(stackbegin);
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> && !std::is_pointer_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_pointer_v<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) ...);
}

248
test.c
Fájl megtekintése

@ -6,12 +6,14 @@
#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)
{
fprintf(stderr, "exile_enable_policy() failed: %i\n", ret);
LOG("failed: %i\n", ret);
exit(EXIT_FAILURE);
}
return 0;
@ -38,16 +40,16 @@ static int test_expected_kill(int (*f)())
int c = WTERMSIG(status);
if(c == SIGSYS)
{
printf("Got expected signal\n");
LOG("Got expected signal\n");
return 0;
}
printf("Unexpected status code: %i\n", c);
LOG("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);
LOG("Process was not killed, test fails. Status code of exit: %i\n", c);
return 1;
}
return 0;
@ -67,7 +69,7 @@ static int test_successful_exit(int (*f)())
if(WIFSIGNALED(status))
{
int c = WTERMSIG(status);
printf("Received signal, which was not expected. Signal was: %i\n", c);
LOG("Received signal, which was not expected. Signal was: %i\n", c);
return 1;
}
else
@ -75,11 +77,11 @@ static int test_successful_exit(int (*f)())
int c = WEXITSTATUS(status);
if(c != 0)
{
printf("Process failed to exit properly. Status code is: %i\n", c);
LOG("Process failed to exit properly. Status code is: %i\n", c);
}
return c;
}
printf("Process exited sucessfully as expected");
LOG("Process exited sucessfully as expected");
return 0;
}
@ -153,7 +155,7 @@ int test_seccomp_require_last_matchall()
int status = exile_enable_policy(policy);
if(status == 0)
{
printf("Failed. Should not have been enabled!");
LOG("Failed. Should not have been enabled!");
return 1;
}
return 0;
@ -170,7 +172,7 @@ static int do_test_seccomp_errno()
uid_t id = syscall(EXILE_SYS(getuid));
int fd = syscall(EXILE_SYS(close), 0);
printf("close() return code: %i, errno: %s\n", fd, strerror(errno));
LOG("close() return code: %i, errno: %s\n", fd, strerror(errno));
return fd == -1 ? 0 : 1;
}
@ -254,14 +256,14 @@ int test_seccomp_argfilter_mixed()
int s = (int) syscall(EXILE_SYS(stat), "/dev/urandom", &statbuf);
if(s != -1)
{
printf("Failed: stat was expected to fail, but returned %i\n", s);
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)
{
printf("Failed: getpid was expected to fail, but returned %i\n", p);
LOG("Failed: getpid was expected to fail, but returned %i\n", p);
return 1;
}
@ -269,13 +271,13 @@ int test_seccomp_argfilter_mixed()
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);
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)
{
printf("Failed: open with O_RDONLY was expected to succeed, but returned %i\n", ret);
LOG("Failed: open with O_RDONLY was expected to succeed, but returned %i\n", ret);
return 1;
}
return 0;
@ -291,13 +293,13 @@ int do_test_seccomp_vow_socket()
int s = socket(AF_INET, SOCK_STREAM, 0);
if(s == -1)
{
printf("Failed: socket was expected to succeed, but returned %i\n", s);
LOG("Failed: socket was expected to succeed, but returned %i\n", s);
return 1;
}
s = socket(AF_UNIX, SOCK_DGRAM, 0);
if(s != -1)
{
printf("Failed: socket was expected to fail, but returned %i\n", s);
LOG("Failed: socket was expected to fail, but returned %i\n", s);
return 1;
}
return 0;
@ -312,19 +314,19 @@ int do_test_seccomp_vow_open()
int ret = open("/dev/urandom", O_WRONLY | O_APPEND);
if(ret != -1)
{
printf("Failed: open was expected to fail, but returned %i\n", ret);
LOG("Failed: open was expected to fail, but returned %i\n", ret);
return 1;
}
ret = open("/dev/urandom", O_RDWR);
if(ret != -1)
{
printf("Failed: open O_RDWR was expected to fail, but returned %i\n", ret);
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)
{
printf("Failed: open was expected to succceed, but returned %i\n", ret);
LOG("Failed: open was expected to succceed, but returned %i\n", ret);
return 1;
}
return 0;
@ -335,13 +337,13 @@ int test_seccomp_vow()
int ret = test_successful_exit(&do_test_seccomp_vow_open);
if(ret != 0)
{
printf("Failed: do_test_seccomp_vow_open()\n");
LOG("Failed: do_test_seccomp_vow_open()\n");
return 1;
}
ret = test_successful_exit(&do_test_seccomp_vow_socket);
if(ret != 0)
{
printf("Failed: do_test_seccomp_vow_socket()\n");
LOG("Failed: do_test_seccomp_vow_socket()\n");
return 1;
}
return 0;
@ -353,13 +355,13 @@ 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)
{
printf("Failed: exile_vow() call 1 failed\n");
LOG("Failed: exile_vow() call 1 failed\n");
return 1;
}
int s = socket(AF_UNIX, SOCK_STREAM, 0);
if(s == -1)
{
printf("Failed: socket was expected to succeed, but returned %i\n", s);
LOG("Failed: socket was expected to succeed, but returned %i\n", s);
return 1;
}
@ -367,13 +369,13 @@ int test_seccomp_exile_vow_multiple()
ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
if(ret != 0)
{
printf("Failed: exile_vow() call 2 failed\n");
LOG("Failed: exile_vow() call 2 failed\n");
return 1;
}
s = socket(AF_UNIX, SOCK_STREAM, 0);
if(s != -1)
{
printf("Failed: socket was expected to fail, but returned %i\n", s);
LOG("Failed: socket was expected to fail, but returned %i\n", s);
return 1;
}
@ -381,13 +383,13 @@ int test_seccomp_exile_vow_multiple()
ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
if(ret != 0)
{
printf("Failed: exile_vow() call 3 failed\n");
LOG("Failed: exile_vow() call 3 failed\n");
return 1;
}
s = socket(AF_UNIX, SOCK_STREAM, 0);
if(s != -1)
{
printf("Failed: socket was still expected to fail, but returned %i\n", s);
LOG("Failed: socket was still expected to fail, but returned %i\n", s);
return 1;
}
@ -400,11 +402,11 @@ int test_landlock()
{
if(!exile_landlock_is_available())
{
printf("landlock not available, so cannot test\n");
LOG("landlock not available, so cannot test\n");
return 1;
}
struct exile_policy *policy = exile_init_policy();
exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ, "/proc/self/fd");
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/proc/self/fd");
xexile_enable_policy(policy);
int fd = open("/", O_RDONLY | O_CLOEXEC);
@ -418,7 +420,7 @@ int test_landlock()
int test_landlock_deny_write()
{
struct exile_policy *policy = exile_init_policy();
exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ, "/tmp/");
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/tmp/");
xexile_enable_policy(policy);
int fd = open("/tmp/a", O_WRONLY | O_CLOEXEC);
@ -449,14 +451,14 @@ int test_nofs()
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");
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)
{
fprintf(stderr, "Failed: We do not expect write access\n");
LOG("Failed: We do not expect write access\n");
return 1;
}
@ -472,14 +474,14 @@ int test_no_new_fds()
if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0)
{
fprintf(stderr, "Failed: Could open new file descriptor\n");
LOG("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");
LOG("Failed: socket got opened but policy denied\n");
return -1;
}
@ -487,6 +489,178 @@ int test_no_new_fds()
}
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 = &num;
params.policy = policy;
read_pipe = &params.child_write_pipe[0];
int launchfd = exile_launch(&params, &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(&params, &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;
}
int test_clone3_nosys()
{
struct exile_policy *policy = exile_init_policy();
policy->vow_promises = exile_vows_from_str("stdio rpath wpath cpath thread error");
exile_enable_policy(policy);
/* While args are invalid, it should never reach clone3 syscall handler, so it's irrelevant for
our test*/
long ret = syscall(__NR_clone3, NULL, 0);
if(ret == -1 && errno != ENOSYS)
{
LOG("clone3() was not allowed but did not return ENOSYS. It returned: %li, errno: %i\n", ret, errno);
return 1;
}
return 0;
}
struct dispatcher
{
char *name;
@ -508,7 +682,13 @@ struct dispatcher dispatchers[] = {
{ "landlock", &test_landlock},
{ "landlock-deny-write", &test_landlock_deny_write },
{ "no_fs", &test_nofs},
{ "no_new_fds", &test_no_new_fds}
{ "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},
{ "clone3_nosys", &test_clone3_nosys},
};
int main(int argc, char *argv[])

92
test.cpp Normal file
Fájl megtekintése

@ -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;
}

18
test.sh
Fájl megtekintése

@ -44,14 +44,15 @@ function runtest_skipped()
function runtest()
{
testname="$1"
test_log_file="$2"
testbin="$1"
testname="$2"
test_log_file="$3"
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
echo -n "Running $1... "
echo -n "Running $testname... "
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
(./test $1 || exit $?) &>> "${test_log_file}"
(./$testbin "$testname" || exit $?) &>> "${test_log_file}"
ret=$?
SUCCESS="no"
if [ $ret -eq 0 ] ; then
@ -64,7 +65,7 @@ function runtest()
runtest_fail
fi
echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
echo "Finished: ${testname} (${testbin}). Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
}
GIT_ID=$( git log --pretty="format:%h" -n1 )
@ -79,7 +80,12 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/exile_test_${GIT_ID}_${TIMESTAMP}"
for test in $( ./test --dumptests ) ; do
testname=$( echo $test )
runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
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})"