Compare commits

16 Commits

Author SHA1 Message Date
73dae3a102 append_syscall_to_bpf(): Check for unlikely case of too many sock_filters 2022-03-17 15:17:28 +01:00
f2ca26010a exile.hpp: Mark do_clone inline, not static 2022-03-15 08:48:04 +01:00
0f39ee7061 Makefile: Build exile.o separately, link it in all tests 2022-03-15 08:48:04 +01:00
41bd6e8f10 exile.h: Retire static child_read/write_pipe vars 2022-03-15 08:48:04 +01:00
7f083909e6 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-15 08:48:04 +01:00
732623fc6f exile.h: Add extern "C" guards 2022-03-15 08:48:04 +01:00
dcfbe641f9 c++: Add explicit exile_launch() std::basic_string variant 2022-03-15 08:48:04 +01:00
72a3b041d9 c++: Retire exile_launch_trivial(), use std::enable_if 2022-03-15 08:48:04 +01:00
c57ba807d7 Makefile: Add 'tests' target, depend on headers too to rebuild on changes of those 2022-03-15 08:48:04 +01:00
6f19c53acf test.sh: Also run C++ tests 2022-03-15 08:48:04 +01:00
99d26480d7 Add test.cpp to test C++ API 2022-03-15 08:48:04 +01:00
f13cff754c Begin C++ API: Add exile.hpp with exile_launch() wrappers 2022-03-15 08:48:04 +01:00
278ae31e2e fixup! Introduce exile_vows_from_str() 2022-01-30 10:45:05 +01:00
5ef54a08b4 struct syscall_vow_map: change 'str' to const char* 2022-01-30 10:42:46 +01:00
29b5864dd3 test: Introduce LOG(), avoid inconsistent printf/fprintf 2022-01-17 22:48:29 +01:00
0a4e4850f9 Introduce exile_vows_from_str() 2022-01-17 22:42:26 +01:00
2 changed files with 12 additions and 123 deletions

123
README.md
View File

@ -6,124 +6,17 @@ The following section gives small quick examples. Then the motivation is explain
Proper API documentation will be maintained in other files. Proper API documentation will be maintained in other files.
## Quick demo ## Quick demo
This section quickly demonstrates the simplicity of the API. It serves as an overview to get TODO This section will demonstrate the simplicity of the API, but only serves as an overview.
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.
### 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.
### System call policies / vows`
exile.h allows specifying which syscalls are permitted or denied. In the folloing example,
ls is never executed, as the specificed "vows" do not allow the execve system call. The
process will be killed.
```c ### System call policies / vows
#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 ### Isolation of single functions
Currently, working is being done to enable to quickly isolate individual function calls. exile_launch() demo
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).
## 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.
@ -184,7 +77,7 @@ TODO:
## 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. 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 and furthermore it depends on distro-provided kernels being reasonable and enabling it by default. In practise, this means that Landlock probably won't be used for now, and exile.h will use a combination of namespaces, bind mounts and chroot as fallbacks.
## FAQ ## FAQ
@ -194,11 +87,11 @@ While mostly transparent to users of this API, kernel >= 5.13 is required to tak
No. No.
### It doesn't work on my Debian version! ### It doesn't work on Debian!
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.
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. 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.
### Examples ### Examples
- looqs: https://gitea.quitesimple.org/crtxcr/looqs - looqs: https://gitea.quitesimple.org/crtxcr/looqs

12
exile.c
View File

@ -430,7 +430,6 @@ int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *f
struct exile_syscall_filter ioctl_filter[] = { struct exile_syscall_filter ioctl_filter[] = {
EXILE_SYSCALL_FILTER_LOAD_ARG(1), EXILE_SYSCALL_FILTER_LOAD_ARG(1),
{ EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_NO_MATCH_SET(TIOCSTI), 1 },
{ EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_RETURN_MATCHING, 1 }, { EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_RETURN_MATCHING, 1 },
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1},
{ EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONBIO), 1}, { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONBIO), 1},
@ -644,7 +643,7 @@ int (exile_append_path_policies)(struct exile_policy *exile_policy, unsigned int
int fd = open(path, O_PATH); int fd = open(path, O_PATH);
if(fd == -1) if(fd == -1)
{ {
EXILE_LOG_ERROR("Failed to open %s: %s\n", path, strerror(errno)); EXILE_LOG_ERROR("Failed to open the specified path: %s\n", strerror(errno));
exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL; exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL;
return -1; return -1;
} }
@ -852,7 +851,7 @@ static int create_chroot_dirs(const char *chroot_target_path, struct exile_path_
ret = mkpath(path_inside_chroot, 0700, baseisfile); ret = mkpath(path_inside_chroot, 0700, baseisfile);
if(ret < 0) if(ret < 0)
{ {
EXILE_LOG_ERROR("Error creating directory structure %s while mounting paths to chroot: %s\n", path_inside_chroot, strerror(errno)); EXILE_LOG_ERROR("Error creating directory structure while mounting paths to chroot. %s\n", strerror(errno));
free(path_inside_chroot); free(path_inside_chroot);
return ret; return ret;
} }
@ -1209,12 +1208,9 @@ static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode)
result |= LANDLOCK_ACCESS_FS_WRITE_FILE; result |= LANDLOCK_ACCESS_FS_WRITE_FILE;
if(S_ISDIR(statmode)) if(S_ISDIR(statmode))
{ {
result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; result |= LANDLOCK_ACCESS_FS_REMOVE_FILE;
result |= LANDLOCK_ACCESS_FS_MAKE_DIR;
result |= LANDLOCK_ACCESS_FS_MAKE_FIFO;
result |= LANDLOCK_ACCESS_FS_MAKE_REG; result |= LANDLOCK_ACCESS_FS_MAKE_REG;
result |= LANDLOCK_ACCESS_FS_MAKE_SOCK; result |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
result |= LANDLOCK_ACCESS_FS_MAKE_SYM; result |= LANDLOCK_ACCESS_FS_MAKE_SYM;
} }
} }
@ -1351,7 +1347,7 @@ static int check_policy_sanity(struct exile_policy *policy)
{ {
if(path_policy_needs_landlock(path_policy)) if(path_policy_needs_landlock(path_policy))
{ {
EXILE_LOG_ERROR("A path policy (%s) needs landlock, but landlock is not available. Fallback not possible\n", path_policy->path); EXILE_LOG_ERROR("A path policy needs landlock, but landlock is not available. Fallback not possible\n");
return -1; return -1;
} }
path_policy = path_policy->next; path_policy = path_policy->next;