Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
4cfdead5d0 | |||
bbc8193ea9 | |||
c9fdeb4a1d | |||
3732524bfa | |||
4059c1a093 | |||
44b9a17bec | |||
f662398ac3 | |||
7b859d0aed | |||
5cd0a36ced | |||
618f223491 | |||
01c5cbf701 | |||
769f729dc5 | |||
40d23af355 | |||
b5f83499f3 | |||
ff60ec227d | |||
e711a1d53a | |||
6628bf4fb7 | |||
3fa73b0b97 | |||
8f38dc4480 | |||
42d44b0cc1 | |||
bd3641981c | |||
bbbdfc44da | |||
2dc61828f1 | |||
cdc265cedf | |||
91858efa51 | |||
88995d214d | |||
6eb47daf84 | |||
8bf87717a5 | |||
bcaefffbe8 | |||
ed5098f2c6 | |||
ea66ef76eb | |||
66def7a28f | |||
dbf8e87440 | |||
98421fab90 | |||
70c3fef500 | |||
69829374c7 | |||
005851c645 | |||
95fa11e928 | |||
97e2025758 | |||
8cfb73568a | |||
e7a5ba7f7f | |||
e52eda186b | |||
90ed5bbae9 | |||
48b6de9036 | |||
93acb13929 | |||
9247a6636b |
20
Makefile
20
Makefile
@ -1,17 +1,27 @@
|
|||||||
prefix = /usr/local
|
prefix = /usr/local
|
||||||
bindir = $(prefix)/bin
|
bindir = $(prefix)/bin
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -pedantic
|
CFLAGS = -std=c99 -Wall -Wextra -pedantic
|
||||||
|
CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic
|
||||||
|
|
||||||
.DEFAULT_GOAL := test
|
.DEFAULT_GOAL := tests
|
||||||
|
|
||||||
|
|
||||||
clean:
|
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
|
./test.sh
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
|
196
README.md
196
README.md
@ -1,60 +1,168 @@
|
|||||||
# exile.h
|
# 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
|
`exile.h` provides an API for processes on Linux to easily isolate themselves in order
|
||||||
require knowledge of details and are not trivial for developers to employ, which prevents a more widespread adoption.
|
to mitigate the effect of exploited vulnerabilities, i. e. when attacker has achieved
|
||||||
|
arbitrary code execution. exile.h makes it simpler for developers to use existing technologies such as Seccomp and Linux Namespaces. Those generally require knowledge of details and are not trivial for developers to employ, which prevents a more widespread adoption.
|
||||||
|
|
||||||
The following section gives small quick examples. Then the motivation is explained in more detail.
|
The following section offers small examples. Then the motivation is explained in more detail. Proper API documentation will be maintained in other files.
|
||||||
Proper API documentation will be maintained in other files.
|
|
||||||
|
|
||||||
## Quick demo
|
## Quick demo
|
||||||
TODO This section will demonstrate the simplicity of the API, but only serves as an overview.
|
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
|
### 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 that allows only reading
|
||||||
|
from /home/user. We can write to /tmp/ though as it was specified in the policy.
|
||||||
|
|
||||||
|
### vows(): pledge()-like API / System call policies
|
||||||
|
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.
|
||||||
|
|
||||||
### System call policies / vows
|
```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 of single functions
|
### Isolation from network
|
||||||
exile_launch() demo
|
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 (EXPERIMENTAL)
|
||||||
|
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 subproces, 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
|
## Status
|
||||||
No release yet, experimental, 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 from the needs of my other projects.
|
Currently, it's mainly evolving from the needs of my other projects which use exile.h.
|
||||||
|
|
||||||
|
|
||||||
|
### Real-world usage
|
||||||
|
- looqs: https://github.com/quitesimpleorg/looqs
|
||||||
|
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
|
||||||
|
|
||||||
|
|
||||||
## Motivation and Background
|
## 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.
|
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 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.
|
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 without learning
|
||||||
|
about BPF. 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
|
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.
|
||||||
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.
|
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
|
## Features
|
||||||
- Restricting file system access (using Landlock or Namespaces/chroot as fallback)
|
- 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.
|
- Systemcall filtering (using seccomp-bpf). An interface inspired by OpenBSD's pledge() is available
|
||||||
- Dropping privileges in general, such as capabilities
|
- Dropping privileges in general, such as capabilities
|
||||||
- Isolating the application from the network, etc. through Namespaces
|
- Isolating the application from the network, etc. through Namespaces
|
||||||
- Helpers to isolate single functions
|
- Helpers to isolate single functions
|
||||||
|
|
||||||
|
|
||||||
## What it's not
|
## 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
|
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
|
||||||
is more familiar with the 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.
|
||||||
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.
|
Therefore, software should ideally be written with sandboxing in mind from the beginning.
|
||||||
|
|
||||||
@ -66,40 +174,44 @@ It's recommended to start with [README.usage.md] to get a feeling for exile.h.
|
|||||||
API-Documentation: [README.api.md]
|
API-Documentation: [README.api.md]
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
Built upon kernel technologies, exile.h naturally inherits their limitations:
|
||||||
|
|
||||||
|
- New syscalls can be introduced by new kernel versions. exile.h must keep in sync, and users must keep the library up to date.
|
||||||
|
- seccomp has no deep argument inspection (yet), particularly new syscalls
|
||||||
|
cannot be reasonably filtered, such as clone3(), or io_uring.
|
||||||
|
- You can't know what syscalls libraries will issue. An update to existing
|
||||||
|
libraries may cause them to use different syscalls not allowed by a policy. However, using vows and keeping up to date with exile.h should cover that.
|
||||||
|
- Landlock, currently, does not apply to syscalls such as stat().
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
- seccomp must be kept up to date syscalls kernel
|
|
||||||
- ioctl does not know the fd, so checking values is kind of strange
|
- 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
|
- 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.
|
- no magic, be reasonable, devs should not get sloppy, restrict IPC.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Kernel >=3.17
|
Kernel >=3.17
|
||||||
|
|
||||||
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.
|
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, Landlock maybe won't be used in some cases so 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 privileged to utilize the library?
|
||||||
|
|
||||||
No.
|
No.
|
||||||
|
|
||||||
### It doesn't work on Debian!
|
### It doesn't work on my Debian version!
|
||||||
|
You can thank a Debian-specific kernel patch for that. Execute
|
||||||
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.
|
||||||
|
|
||||||
### 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.
|
||||||
- looqs: https://gitea.quitesimple.org/crtxcr/looqs
|
|
||||||
- qswiki: https://gitea.quitesimple.org/crtxcr/qswiki
|
|
||||||
|
|
||||||
Outdated:
|
### Why "vows"?
|
||||||
- cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb
|
pledge() cannot be properly implemented using seccomp. The "vow" concept here may look similiar, and it is, but it's not pledge().
|
||||||
- qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb
|
|
||||||
|
### Other projects
|
||||||
|
- [sandbox2](https://developers.google.com/code-sandboxing/sandbox2/)
|
||||||
|
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
201
exile.hpp
Normal file
201
exile.hpp
Normal file
@ -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) ...);
|
||||||
|
}
|
270
test.c
270
test.c
@ -6,12 +6,14 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/wait.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 xexile_enable_policy(struct exile_policy *policy)
|
||||||
{
|
{
|
||||||
int ret = exile_enable_policy(policy);
|
int ret = exile_enable_policy(policy);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "exile_enable_policy() failed: %i\n", ret);
|
LOG("failed: %i\n", ret);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -38,16 +40,16 @@ static int test_expected_kill(int (*f)())
|
|||||||
int c = WTERMSIG(status);
|
int c = WTERMSIG(status);
|
||||||
if(c == SIGSYS)
|
if(c == SIGSYS)
|
||||||
{
|
{
|
||||||
printf("Got expected signal\n");
|
LOG("Got expected signal\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
printf("Unexpected status code: %i\n", c);
|
LOG("Unexpected status code: %i\n", c);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int c = WEXITSTATUS(status);
|
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 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -67,7 +69,7 @@ static int test_successful_exit(int (*f)())
|
|||||||
if(WIFSIGNALED(status))
|
if(WIFSIGNALED(status))
|
||||||
{
|
{
|
||||||
int c = WTERMSIG(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;
|
return 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -75,11 +77,11 @@ static int test_successful_exit(int (*f)())
|
|||||||
int c = WEXITSTATUS(status);
|
int c = WEXITSTATUS(status);
|
||||||
if(c != 0)
|
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;
|
return c;
|
||||||
}
|
}
|
||||||
printf("Process exited sucessfully as expected");
|
LOG("Process exited sucessfully as expected");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ int test_seccomp_require_last_matchall()
|
|||||||
int status = exile_enable_policy(policy);
|
int status = exile_enable_policy(policy);
|
||||||
if(status == 0)
|
if(status == 0)
|
||||||
{
|
{
|
||||||
printf("Failed. Should not have been enabled!");
|
LOG("Failed. Should not have been enabled!");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -170,7 +172,7 @@ static int do_test_seccomp_errno()
|
|||||||
uid_t id = syscall(EXILE_SYS(getuid));
|
uid_t id = syscall(EXILE_SYS(getuid));
|
||||||
|
|
||||||
int fd = syscall(EXILE_SYS(close), 0);
|
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;
|
return fd == -1 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,14 +256,14 @@ int test_seccomp_argfilter_mixed()
|
|||||||
int s = (int) syscall(EXILE_SYS(stat), "/dev/urandom", &statbuf);
|
int s = (int) syscall(EXILE_SYS(stat), "/dev/urandom", &statbuf);
|
||||||
if(s != -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pid_t p = (pid_t) syscall(EXILE_SYS(getpid));
|
pid_t p = (pid_t) syscall(EXILE_SYS(getpid));
|
||||||
if(p != -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,13 +271,13 @@ int test_seccomp_argfilter_mixed()
|
|||||||
int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY);
|
int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY);
|
||||||
if(ret != -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
ret = (int) syscall(EXILE_SYS(open), t, O_RDONLY);
|
ret = (int) syscall(EXILE_SYS(open), t, O_RDONLY);
|
||||||
if(ret == -1)
|
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 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -291,13 +293,13 @@ int do_test_seccomp_vow_socket()
|
|||||||
int s = socket(AF_INET, SOCK_STREAM, 0);
|
int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if(s == -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
s = socket(AF_UNIX, SOCK_DGRAM, 0);
|
s = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||||
if(s != -1)
|
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 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -312,19 +314,19 @@ int do_test_seccomp_vow_open()
|
|||||||
int ret = open("/dev/urandom", O_WRONLY | O_APPEND);
|
int ret = open("/dev/urandom", O_WRONLY | O_APPEND);
|
||||||
if(ret != -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
ret = open("/dev/urandom", O_RDWR);
|
ret = open("/dev/urandom", O_RDWR);
|
||||||
if(ret != -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
ret = open("/dev/urandom", O_RDONLY);
|
ret = open("/dev/urandom", O_RDONLY);
|
||||||
if(ret == -1)
|
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 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -335,13 +337,13 @@ int test_seccomp_vow()
|
|||||||
int ret = test_successful_exit(&do_test_seccomp_vow_open);
|
int ret = test_successful_exit(&do_test_seccomp_vow_open);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
printf("Failed: do_test_seccomp_vow_open()\n");
|
LOG("Failed: do_test_seccomp_vow_open()\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
ret = test_successful_exit(&do_test_seccomp_vow_socket);
|
ret = test_successful_exit(&do_test_seccomp_vow_socket);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
printf("Failed: do_test_seccomp_vow_socket()\n");
|
LOG("Failed: do_test_seccomp_vow_socket()\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
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);
|
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)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
printf("Failed: exile_vow() call 1 failed\n");
|
LOG("Failed: exile_vow() call 1 failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
if(s == -1)
|
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;
|
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);
|
ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
printf("Failed: exile_vow() call 2 failed\n");
|
LOG("Failed: exile_vow() call 2 failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
if(s != -1)
|
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 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);
|
ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
printf("Failed: exile_vow() call 3 failed\n");
|
LOG("Failed: exile_vow() call 3 failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
if(s != -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +402,7 @@ int test_landlock()
|
|||||||
{
|
{
|
||||||
if(!exile_landlock_is_available())
|
if(!exile_landlock_is_available())
|
||||||
{
|
{
|
||||||
printf("landlock not available, so cannot test\n");
|
LOG("landlock not available, so cannot test\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
struct exile_policy *policy = exile_init_policy();
|
struct exile_policy *policy = exile_init_policy();
|
||||||
@ -449,14 +451,14 @@ int test_nofs()
|
|||||||
int s = socket(AF_INET,SOCK_STREAM,0);
|
int s = socket(AF_INET,SOCK_STREAM,0);
|
||||||
if(s == -1)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Expect seccomp to take care of this */
|
/* Expect seccomp to take care of this */
|
||||||
if(open("/test", O_CREAT | O_WRONLY) >= 0)
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,14 +474,14 @@ int test_no_new_fds()
|
|||||||
|
|
||||||
if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int s = socket(AF_INET,SOCK_STREAM,0);
|
int s = socket(AF_INET,SOCK_STREAM,0);
|
||||||
if(s >= 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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,6 +489,7 @@ int test_no_new_fds()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern int mkpath(const char *p, mode_t mode, int baseisfile);
|
||||||
int test_mkpath()
|
int test_mkpath()
|
||||||
{
|
{
|
||||||
system("rm -rf /tmp/.exile.h/");
|
system("rm -rf /tmp/.exile.h/");
|
||||||
@ -495,13 +498,13 @@ int test_mkpath()
|
|||||||
int ret = mkpath(filepath, 0700, 1);
|
int ret = mkpath(filepath, 0700, 1);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed: mkpath(file) returned: %i\n", ret);
|
LOG("Failed: mkpath(file) returned: %i\n", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
ret = mkpath(dirpath, 0700, 0);
|
ret = mkpath(dirpath, 0700, 0);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed: mkpath(dirpath) returned: %i\n", ret);
|
LOG("Failed: mkpath(dirpath) returned: %i\n", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,23 +512,23 @@ int test_mkpath()
|
|||||||
ret = stat(filepath, &statbuf);
|
ret = stat(filepath, &statbuf);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed: stat on filepath returned: %i\n", ret);
|
LOG("Failed: stat on filepath returned: %i\n", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(!S_ISREG(statbuf.st_mode))
|
if(!S_ISREG(statbuf.st_mode))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed: mkpath did not create a file: %i\n", ret);
|
LOG("Failed: mkpath did not create a file: %i\n", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
ret = stat(dirpath, &statbuf);
|
ret = stat(dirpath, &statbuf);
|
||||||
if(ret != 0)
|
if(ret != 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed: stat on dirpath returned: %i\n", ret);
|
LOG("Failed: stat on dirpath returned: %i\n", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(!S_ISDIR(statbuf.st_mode))
|
if(!S_ISDIR(statbuf.st_mode))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed: mkpath did not create a directory: %i\n", ret);
|
LOG("Failed: mkpath did not create a directory: %i\n", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
system("rm -rf /tmp/.exile.h/");
|
system("rm -rf /tmp/.exile.h/");
|
||||||
@ -545,12 +548,14 @@ int test_fail_flags()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int *read_pipe = NULL;
|
||||||
int do_launch_test(void *arg)
|
int do_launch_test(void *arg)
|
||||||
{
|
{
|
||||||
int num = *(int *)(arg);
|
int num = *(int *)(arg);
|
||||||
num += 1;
|
num += 1;
|
||||||
char buffer[512] = { 0 };
|
char buffer[512] = { 0 };
|
||||||
read(child_write_pipe[0], buffer, sizeof(buffer)-1);
|
read(*read_pipe, buffer, sizeof(buffer)-1);
|
||||||
printf("Sandboxed +1: %i\n", num);
|
printf("Sandboxed +1: %i\n", num);
|
||||||
printf("Echoing: %s\n", buffer);
|
printf("Echoing: %s\n", buffer);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
@ -566,10 +571,11 @@ int test_launch()
|
|||||||
params.func = &do_launch_test;
|
params.func = &do_launch_test;
|
||||||
params.funcarg = #
|
params.funcarg = #
|
||||||
params.policy = policy;
|
params.policy = policy;
|
||||||
|
read_pipe = ¶ms.child_write_pipe[0];
|
||||||
int launchfd = exile_launch(¶ms, &res);
|
int launchfd = exile_launch(¶ms, &res);
|
||||||
if(launchfd < 0)
|
if(launchfd < 0)
|
||||||
{
|
{
|
||||||
printf("Failed to launch\n");
|
LOG("Failed to launch\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,11 +583,11 @@ int test_launch()
|
|||||||
write(res.write_fd, "1234", 4);
|
write(res.write_fd, "1234", 4);
|
||||||
int s = read(res.read_fd, buffer, sizeof(buffer)-1);
|
int s = read(res.read_fd, buffer, sizeof(buffer)-1);
|
||||||
write(1, buffer, s);
|
write(1, buffer, s);
|
||||||
printf("Before wait, got: %i\n", s);
|
LOG("Before wait, got: %i\n", s);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
if(strstr(buffer, "Echoing: 1234") == NULL)
|
if(strstr(buffer, "Echoing: 1234") == NULL)
|
||||||
{
|
{
|
||||||
printf("Failed: Did not get back what we wrote\n");
|
LOG("Failed: Did not get back what we wrote\n");
|
||||||
}
|
}
|
||||||
int status = 0;
|
int status = 0;
|
||||||
waitpid(res.tid, &status, __WALL);
|
waitpid(res.tid, &status, __WALL);
|
||||||
@ -612,19 +618,189 @@ int test_launch_get()
|
|||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
char *content = exile_launch_get(¶ms, &n);
|
char *content = exile_launch_get(¶ms, &n);
|
||||||
unsigned int len = strlen(LAUNCH_GET_TEST_STR);
|
unsigned int len = strlen(LAUNCH_GET_TEST_STR);
|
||||||
if(n != strlen(LAUNCH_GET_TEST_STR))
|
if(n != len)
|
||||||
{
|
{
|
||||||
printf("Lenght does does not match: %lu vs %u\n", n, len);
|
LOG("Lenght does not match: %lu vs %u\n", n, len);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
|
if(strcmp(content, LAUNCH_GET_TEST_STR) != 0)
|
||||||
{
|
{
|
||||||
printf("Received content differs\n");
|
LOG("Received content differs\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int do_test_nsuidmap(const char *path, const char *firstfield, const char *secondfield, const char *thirdfield)
|
||||||
|
{
|
||||||
|
char *line = NULL;
|
||||||
|
size_t n = 0;
|
||||||
|
FILE *fp = fopen(path, "r");
|
||||||
|
|
||||||
|
int ret = getdelim(&line, &n, ' ', fp);
|
||||||
|
while(ret != -1 && strlen(line) == 1 && *line == ' ')
|
||||||
|
ret = getdelim(&line, &n, ' ', fp);
|
||||||
|
if(ret == -1)
|
||||||
|
{
|
||||||
|
LOG("getdelim() failed to read a line from %s\n", path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
line[ret-1] = '\0';
|
||||||
|
if(strcmp(line, firstfield) != 0)
|
||||||
|
{
|
||||||
|
LOG("Invalid value for first entry in map: Expected: %s, was: %s\n", firstfield, line);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = getdelim(&line, &n, ' ', fp);
|
||||||
|
while(ret != -1 && strlen(line) == 1 && *line == ' ')
|
||||||
|
ret = getdelim(&line, &n, ' ', fp);
|
||||||
|
if(ret == -1)
|
||||||
|
{
|
||||||
|
LOG("getdelim() failed to read a line from map\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
line[ret-1] = '\0';
|
||||||
|
|
||||||
|
if(strcmp(line, secondfield) != 0)
|
||||||
|
{
|
||||||
|
LOG("Invalid value for second entry in map: Expected: %s, was: %s\n", secondfield, line);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ret = getdelim(&line, &n, ' ', fp);
|
||||||
|
while(ret != -1 && strlen(line) == 1 && *line == ' ')
|
||||||
|
ret = getdelim(&line, &n, ' ', fp);
|
||||||
|
if(ret == -1)
|
||||||
|
{
|
||||||
|
LOG("getdelim() failed to read a line from uid_map\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
line[ret-1] = '\0';
|
||||||
|
if(strcmp(line, thirdfield) != 0)
|
||||||
|
{
|
||||||
|
LOG("Invalid value for second entry in map: Expected: %s, was: %s\n", thirdfield, line);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_unshare_user()
|
||||||
|
{
|
||||||
|
char uidstr[64];
|
||||||
|
snprintf(uidstr, sizeof(uidstr), "%u", getuid());
|
||||||
|
|
||||||
|
char gidstr[64];
|
||||||
|
snprintf(gidstr, sizeof(gidstr), "%u", getgid());
|
||||||
|
|
||||||
|
struct exile_policy *policy = exile_init_policy();
|
||||||
|
policy->namespace_options = EXILE_UNSHARE_USER;
|
||||||
|
xexile_enable_policy(policy);
|
||||||
|
|
||||||
|
if(do_test_nsuidmap("/proc/self/uid_map", "0", uidstr, "1") != 0)
|
||||||
|
{
|
||||||
|
LOG("/proc/self/uid_map failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(do_test_nsuidmap("/proc/self/gid_map", "0", gidstr, "1") != 0)
|
||||||
|
{
|
||||||
|
LOG("/proc/self/gid_map failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = fopen("/proc/self/setgroups", "r");
|
||||||
|
|
||||||
|
char buffer[4096] = { 0 };
|
||||||
|
fread(buffer, sizeof(buffer), 1, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if(strcmp(buffer, "deny\n") != 0)
|
||||||
|
{
|
||||||
|
LOG("/proc/self/setgroups does not contain 'deny'\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_unshare_user_own_uid()
|
||||||
|
{
|
||||||
|
uid_t uid = getuid();
|
||||||
|
gid_t gid = getgid();
|
||||||
|
|
||||||
|
char uidstr[64];
|
||||||
|
snprintf(uidstr, sizeof(uidstr), "%u", uid);
|
||||||
|
|
||||||
|
char gidstr[64];
|
||||||
|
snprintf(gidstr, sizeof(gidstr), "%u", gid);
|
||||||
|
|
||||||
|
struct exile_policy *policy = exile_init_policy();
|
||||||
|
policy->namespace_options = EXILE_UNSHARE_USER;
|
||||||
|
policy->namespace_gid = gid;
|
||||||
|
policy->namespace_uid = uid;
|
||||||
|
xexile_enable_policy(policy);
|
||||||
|
|
||||||
|
if(do_test_nsuidmap("/proc/self/uid_map", uidstr, uidstr, "1") != 0)
|
||||||
|
{
|
||||||
|
LOG("/proc/self/uid_map failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(do_test_nsuidmap("/proc/self/gid_map", gidstr, gidstr, "1") != 0)
|
||||||
|
{
|
||||||
|
LOG("/proc/self/gid_map failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = fopen("/proc/self/setgroups", "r");
|
||||||
|
|
||||||
|
char buffer[4096] = { 0 };
|
||||||
|
fread(buffer, sizeof(buffer), 1, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if(strcmp(buffer, "deny\n") != 0)
|
||||||
|
{
|
||||||
|
LOG("/proc/self/setgroups does not contain 'deny'\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct dispatcher
|
struct dispatcher
|
||||||
{
|
{
|
||||||
char *name;
|
char *name;
|
||||||
@ -651,6 +827,12 @@ struct dispatcher dispatchers[] = {
|
|||||||
{ "failflags", &test_fail_flags},
|
{ "failflags", &test_fail_flags},
|
||||||
{ "launch", &test_launch},
|
{ "launch", &test_launch},
|
||||||
{ "launch-get", &test_launch_get},
|
{ "launch-get", &test_launch_get},
|
||||||
|
{ "vow_from_str", &test_vows_from_str},
|
||||||
|
{ "clone3_nosys", &test_clone3_nosys},
|
||||||
|
{ "unshare-user", &test_unshare_user},
|
||||||
|
{ "unshare-user-own-uid", &test_unshare_user_own_uid},
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
92
test.cpp
Normal file
92
test.cpp
Normal file
@ -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;
|
||||||
|
}
|
39
test.sh
39
test.sh
@ -8,50 +8,52 @@ COUNT_SUCCEEDED=0
|
|||||||
COUNT_FAILED=0
|
COUNT_FAILED=0
|
||||||
COUNT_SKIPPED=0
|
COUNT_SKIPPED=0
|
||||||
|
|
||||||
function print_fail()
|
print_fail()
|
||||||
{
|
{
|
||||||
echo -e "${RED}$@${NC}" 1>&2
|
printf "${RED}$@${NC}\n" 1>&2
|
||||||
}
|
}
|
||||||
|
|
||||||
function print_success()
|
print_success()
|
||||||
{
|
{
|
||||||
echo -e "${GREEN}$@${NC}"
|
printf "${GREEN}$@${NC}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
function print_skipped()
|
print_skipped()
|
||||||
{
|
{
|
||||||
echo -e "${YELLOW}$@${NC}"
|
printf "${YELLOW}$@${NC}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
function runtest_fail()
|
runtest_fail()
|
||||||
{
|
{
|
||||||
print_fail "failed"
|
print_fail "failed"
|
||||||
COUNT_FAILED=$(($COUNT_FAILED+1))
|
COUNT_FAILED=$(($COUNT_FAILED+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
function runtest_success()
|
runtest_success()
|
||||||
{
|
{
|
||||||
print_success "ok"
|
print_success "ok"
|
||||||
COUNT_SUCCEEDED=$((COUNT_SUCCEEDED+1))
|
COUNT_SUCCEEDED=$((COUNT_SUCCEEDED+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
function runtest_skipped()
|
runtest_skipped()
|
||||||
{
|
{
|
||||||
print_skipped "skipped"
|
print_skipped "skipped"
|
||||||
COUNT_SKIPPED=$((COUNT_SKIPPED+1))
|
COUNT_SKIPPED=$((COUNT_SKIPPED+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function runtest()
|
runtest()
|
||||||
{
|
{
|
||||||
testname="$1"
|
testbin="$1"
|
||||||
test_log_file="$2"
|
testname="$2"
|
||||||
|
test_log_file="$3"
|
||||||
|
|
||||||
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
|
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"
|
#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}" 2>&1
|
||||||
|
|
||||||
ret=$?
|
ret=$?
|
||||||
SUCCESS="no"
|
SUCCESS="no"
|
||||||
if [ $ret -eq 0 ] ; then
|
if [ $ret -eq 0 ] ; then
|
||||||
@ -64,7 +66,7 @@ function runtest()
|
|||||||
runtest_fail
|
runtest_fail
|
||||||
fi
|
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 )
|
GIT_ID=$( git log --pretty="format:%h" -n1 )
|
||||||
@ -79,7 +81,12 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/exile_test_${GIT_ID}_${TIMESTAMP}"
|
|||||||
|
|
||||||
for test in $( ./test --dumptests ) ; do
|
for test in $( ./test --dumptests ) ; do
|
||||||
testname=$( echo $test )
|
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
|
done
|
||||||
echo
|
echo
|
||||||
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"
|
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"
|
||||||
|
Loading…
Reference in New Issue
Block a user