OpenBSD unveil(2) like function on Linux using Landlock (starting from Linux >= 5.13). Rewritten largely based on gonack's landlockjail
See OpenBSD unveil(2) documentation for details. Like unveil(2), llunveil allows a process to submit a set of paths and permissions that it is allowed to access, then deny access to all files and directories that it didn't submit later.
The API is simple. unveil
is a function that takes two arguments: a path and a permission string. The permission string is a set of characters that represent the allowed operations on the path. The following characters are allowed:
r
- Makes the path available for read operationsw
- Makes the path available for write operationsx
- Makes the path available for execute operationsc
- Allows creation or deletion at or under the path
Then, to activate the restrictions, call unveil(NULL, NULL)
. After that, the process will only be able to access the paths that were unveiled.
The API of llunveil
is like unveil
. But with a few differences:
- Filesystem protection is activated and committed upon calling
unveil(NULL, NULL)
- Non-existing files cannot be unveiled. This is a limitation of Landlock.
Instead of activating upon calling the unveil function. Protections have to be commited in llunveil for them to take effect. For example:
#define LLUNVEIL_USE_UNVEIL // create a macro called `unveil`. Prevent conflict
#include <llunveil.h>
...
unveil("/home/user/", "r");
// Not activated until calling unveil(NULL, NULL);
assert(fopen("/tmp/some_text.txt", "r") != NULL);
unveil(NULL, NULL); // activate!
assert(fopen("/tmp/some_text.txt", "r") == NULL);
You need a C11 compatible compiler. And be on Linux >= 5.13 (for the syscall numbers). And CMake for build generation.
mkdir build
cmake ..
make -j
lljail
is a completely rewritten application based on landlockjail
that launches a program with restricted file access.
marty@zack ~/D/l/build> ./lljail -r /usr -rx /usr/lib -rx /usr/lib64 -rx /lib64 -rx /bin -r /tmp -- /bin/bash
bash: /etc/bash.bashrc: Permission denied
bash: /home/marty/.bashrc: Permission denied
bash-5.1$ # We see permission denied because we didn't allow access to /etc and ~ in lljail
bash-5.1$ # Likewise we can't write to /tmp
bash-5.1$ echo Hello World > /tmp/asd
bash: /tmp/asd: Permission denied
bash-5.1$ exit
marty@zack ~/D/l/build> ./lljail -r /usr -rx /usr/lib -rx /usr/lib64 -rx /lib64 -rx /bin -rwc /tmp -r /etc -r $HOME -rw /dev -- /bin/bash
[marty@zack build]$ # Now BASH has full access to the folders. And we gave _write_ and _create_ permission to /tmp.
[marty@zack build]$ echo Hello World > /tmp/asd # Now this works
[marty@zack build]$ cat /tmp/asd
Hello World
[marty@zack build]$ # But we still can't execute anything in /usr/local/bin
[marty@zack build]$ /usr/local/bin/example
bash: /usr/local/bin/example: Permission denied
The following example shows how to use llunveil
to restrict access to a directory and its subdirectories. The program will be able to read files in /home/user/
but not write to them. Also no error checking to keep it short.
// create a macro called `unveil`. Prevent conflict
#define LLUNVEIL_USE_UNVEIL
#include <llunveil.h>
#include <stdio.h>
int main() {
// Allow read access to /home/user/ and its subdirectories
unveil("/home/user/", "r");
unveil(NULL, NULL);
// Since read access is allowed, opening a file for reading works
FILE* file = fopen("/home/user/some_text.txt", "r");
assert(file != NULL);
char buffer[100];
fgets(buffer, 100, file);
// But writing to the file is not allowed
FILE* file2 = fopen("/home/user/other_text.txt", "w");
assert(file2 == NULL);
return 0;
}
- Make unit tests
- Ensure behaviour close to OpenBSD's
- Proper
errno