C library
While we have good libraries (e.g. Rust, Go, Haskell...) we need a C library. I'd like this library to be light and only provide a set of small helpers for common use cases (e.g. drop all access rights according to a Landlock ABI version).
We already wrote most of the mechanic with the sandboxer tool and tests, and we could move some part of this code into a standalone library.
This C library should live close to the kernel source code, in the same Git repository, because:
- Linux is written in C and kernel developers can easily review such code,
- we have a test framework that would be useful to test this library and ease some kernel tests writing,
- it eases maintenance to update the kernel code and user space library with the same review, and
- it makes sense to use the Linux repository as the authority for "landlock_" symbol prefixes (because the libc doesn't provide wrappers for Landlock syscalls).
To ease consumption of this C library, we should also have a dedicated repository in this GitHub organization, synchronized with the code hosted by the kernel repository, similar to libbpf (but simpler).
https://github.com/gnoack/landlock-examples/blob/main/landlock_compat.h
I've tried to find out what a "minimal" C library would need to look like, by growing it from the surrounding CLI tool examples.
The way I see this, we have two main complications:
- Determining the right ruleset to enable.
- Dealing with the "refer" special case.
To determine the right ruleset, you can source a ruleset in one of these ways:
- Use the ruleset that characterizes a given (hardcoded) ABI version
- Look up the best ruleset that can be used on the currently running system, and look it up by ABI version
- Define your own ruleset (for instance, if you only want to restrict file access)
Based on these rulesets, you can build the least common denominator between these with a helper function that applies bitwise-AND on the struct.
In previous versions, I've played with helper functions that do more of that at the same time, but I'm currently leaning towards exposing a bit more of Landlock's internals, but providing some more orthogonal helper functions and good documentation. The helpers offer the following things:
- Look up a
struct landlock_ruleset_attrby ABI version (compatibility table).- This is just exposed as an array.
- Combine two
struct landlock_ruleset_attrs into one (bitwise-AND on all fields) landlock_get_abi()to determine the best ABI version.- Guaranteed to return a number >= 0, where 0 means "unsupported". The result can be used as an index into the ABI table.
Apart from that, it's still slightly annoying that syscalls need to be called with syscall().
And yes, I should absolutely move this into the kernel samples directory or into the landlock-lsm Github organization. :)
https://github.com/gnoack/landlock-examples/blob/main/landlock_compat.h
I've tried to find out what a "minimal" C library would need to look like, by growing it from the surrounding CLI tool examples.
The way I see this, we have two main complications:
- Determining the right ruleset to enable.
- Dealing with the "refer" special case.
To determine the right ruleset, you can source a ruleset in one of these ways:
- Use the ruleset that characterizes a given (hardcoded) ABI version
- Look up the best ruleset that can be used on the currently running system, and look it up by ABI version
- Define your own ruleset (for instance, if you only want to restrict file access)
Based on these rulesets, you can build the least common denominator between these with a helper function that applies bitwise-AND on the struct.
In previous versions, I've played with helper functions that do more of that at the same time, but I'm currently leaning towards exposing a bit more of Landlock's internals, but providing some more orthogonal helper functions and good documentation. The helpers offer the following things:
Look up a
struct landlock_ruleset_attrby ABI version (compatibility table).
- This is just exposed as an array.
Combine two
struct landlock_ruleset_attrs into one (bitwise-AND on all fields)
landlock_get_abi()to determine the best ABI version.
- Guaranteed to return a number >= 0, where 0 means "unsupported". The result can be used as an index into the ABI table.
Great idea! I like this landlock_min_ruleset_attr().
Apart from that, it's still slightly annoying that syscalls need to be called with
syscall().
Yes, this C library should declare the Landlock syscalls with proper names.
And yes, I should absolutely move this into the kernel samples directory or into the landlock-lsm Github organization. :)
As explain above, this C library should live close to the kernel source code, in the same Git repository, but we should make it easy to synchronize with a dedicated GitHub repository that would be used by downstream.
Shameless self-advertisement, but I have come up with a couple of macros to ease the use of landlock:
https://git.sr.ht/~shtrophic/landloc.h/tree/main/item/landloc.h
I was primarily inspired by the API of OpenBSD's unveil(2) and pledge(2).
You can see example usage in: https://codeberg.org/grunfink/snac2/src/commit/46985835fc312e1035b5df6daa0b8c9e0d2bcdd2/sandbox.c#L50
I have a similar library I'm building / playing with, that uses an unveil/pledge like interface. Very much like @shtrophic
It has a bunch of string based access letters, with some convenience helpers.
This isn't a serious library I made, but just an idea for what a more user-friendly c library for landlock could look like.
It only has one function I just called "landlock".
/**
* Simple OpenBSD inspired unveil-like interface for Landlock.
*
* Each call either stages a filesystem rule, stages a per-port network rule,
* extends the handled access masks, or enforces the staged ruleset.
* Internally the library keeps a per-thread (_Thread_local) ruleset cache
* until landlock(NULL, NULL) is invoked.
*
* Usage pattern:
* landlock("/bin/bash", "rx"); // allow exec+read+list on /bin/bash, and adds rx to handled accesses.
* landlock("/tmp", "rw"); // allow write combo on /tmp
* landlock(LL_HANDLE_RIGHTS, "w"); // declare filesystem writes handled. same as landlock(NULL, "w").
* landlock("8080", "net:B"); // allow bind on TCP port 8080
* landlock(NULL, "net:C"); // declare connect handled
* landlock(LL_START_ENFORCING); // enforce and clear the staged ruleset. same as landlock(NULL, NULL).
*
* Arguments:
* - object:
* * Filesystem rule: absolute or relative path (must not be NULL).
* * Network rule: decimal TCP port string (e.g., "8080") when the
* rights string starts with "net:".
* * Handled-access update: pass NULL to update handled rights without
* attaching them to a concrete object.
* * Finalize: pass NULL together with NULL rights to enforce the pending
* ruleset; afterwards the internal cache is cleared.
* - rights: combination of permission characters. A single rights prefix may be specified.
* * Filesystem rights have no prefix.
* Filesystem characters map directly to linux/landlock.h constants:
* E -> LANDLOCK_ACCESS_FS_EXECUTE
* R -> LANDLOCK_ACCESS_FS_READ_FILE
* L -> LANDLOCK_ACCESS_FS_READ_DIR
* W -> LANDLOCK_ACCESS_FS_WRITE_FILE
* A -> LANDLOCK_ACCESS_FS_REMOVE_DIR
* U -> LANDLOCK_ACCESS_FS_REMOVE_FILE
* C -> LANDLOCK_ACCESS_FS_MAKE_CHAR
* D -> LANDLOCK_ACCESS_FS_MAKE_DIR
* F -> LANDLOCK_ACCESS_FS_MAKE_REG
* S -> LANDLOCK_ACCESS_FS_MAKE_SOCK
* P -> LANDLOCK_ACCESS_FS_MAKE_FIFO
* B -> LANDLOCK_ACCESS_FS_MAKE_BLOCK
* Y -> LANDLOCK_ACCESS_FS_MAKE_SYM
* M -> LANDLOCK_ACCESS_FS_REFER
* T -> LANDLOCK_ACCESS_FS_TRUNCATE
* I -> LANDLOCK_ACCESS_FS_IOCTL_DEV
* X -> LANDLOCK_ACCESS_FS_EXECUTE
* Convenience combinations:
* r -> (E | R | L) convenience combo
* w -> (W | F | D | T) convenience combo
* x -> LANDLOCK_ACCESS_FS_EXECUTE
* c -> (C | D | F | S | P | B | Y) creation combo
* * -> LLANDLOCK_HANDLED_FS_ALL
* * Network rights must start with the literal prefix "net:" (lowercase).
* After the prefix, valid characters are capitalized:
* B -> LANDLOCK_ACCESS_NET_BIND_TCP
* C -> LANDLOCK_ACCESS_NET_CONNECT_TCP
* * -> LLANDLOCK_HANDLED_NET_ALL
* * Scope restrictions must start with the literal prefix "scope:".
* Scope restrictions cannot be attached to concrete objects, so
* object must be NULL in this case.
* After the prefix, valid characters are uppercase:
* S -> LANDLOCK_SCOPE_SIGNAL (if available)
* U -> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (if available)
* * -> LLANDLOCK_HANDLED_SCOPED_ALL
* * Logging controls start with the literal prefix "log:" and also
* require a NULL object. Available uppercase characters:
* S -> LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
* N -> LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
* D -> LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
* * -> All supported logging flags
*
* Return value: 0 on success, or a positive errno-style error when the
* staged rule cannot be applied.
*/
llandlock_error_t landlock(const char *object, const char *rights);
So you can do:
landlock(LL_HANDLE_RIGHTS, "rwx"); // handle rwx access rights
landlock(LL_HANDLE_RIGHTS,"net:*") // handle all network access rights. (bind and connect).
landlock("/etc/passwd","r"); //give etc passwd read only access
landlock("8080","net:B"); // let process bind to port 8080
landlock(LL_START_ENFORCING); // restrict the current thread.
I think a dead-simple lib in the spirit of this would be nice and easy for developers to add in their application.