Landlock: userspace documentation

Landlock rules

A Landlock rule enables to describe an action on an object. An object is currently a file hierarchy, and the related filesystem actions are defined in Access rights. A set of rules are aggregated in a ruleset, which can then restricts the thread enforcing it, and its future children.

Defining and enforcing a security policy

Before defining a security policy, an application should first probe for the features supported by the running kernel, which is important to be compatible with older kernels. This can be done thanks to the landlock syscall (cf. The landlock syscall and its arguments).

struct landlock_attr_features attr_features;

if (landlock(LANDLOCK_CMD_GET_FEATURES, LANDLOCK_OPT_GET_FEATURES,
        sizeof(attr_features), &attr_features)) {
    perror("Failed to probe the Landlock supported features");
    return 1;
}

Then, we need to create the ruleset that will contains our rules. For this example, the ruleset will contains rules which only allow read actions, but write actions will be denied. The ruleset then needs to handle both of these kind of actions. To have a backward compatibility, these actions should be ANDed with the supported ones.

int ruleset_fd;
struct landlock_attr_ruleset ruleset = {
    .handled_access_fs =
        LANDLOCK_ACCESS_FS_READ |
        LANDLOCK_ACCESS_FS_READDIR |
        LANDLOCK_ACCESS_FS_EXECUTE |
        LANDLOCK_ACCESS_FS_WRITE |
        LANDLOCK_ACCESS_FS_TRUNCATE |
        LANDLOCK_ACCESS_FS_CHMOD |
        LANDLOCK_ACCESS_FS_CHOWN |
        LANDLOCK_ACCESS_FS_CHGRP |
        LANDLOCK_ACCESS_FS_LINK_TO |
        LANDLOCK_ACCESS_FS_RENAME_FROM |
        LANDLOCK_ACCESS_FS_RENAME_TO |
        LANDLOCK_ACCESS_FS_RMDIR |
        LANDLOCK_ACCESS_FS_UNLINK |
        LANDLOCK_ACCESS_FS_MAKE_CHAR |
        LANDLOCK_ACCESS_FS_MAKE_DIR |
        LANDLOCK_ACCESS_FS_MAKE_REG |
        LANDLOCK_ACCESS_FS_MAKE_SOCK |
        LANDLOCK_ACCESS_FS_MAKE_FIFO |
        LANDLOCK_ACCESS_FS_MAKE_BLOCK |
        LANDLOCK_ACCESS_FS_MAKE_SYM,
};

ruleset.handled_access_fs &= attr_features.access_fs;
ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
                LANDLOCK_OPT_CREATE_RULESET, sizeof(ruleset), &ruleset);
if (ruleset_fd < 0) {
    perror("Failed to create a ruleset");
    return 1;
}

We can now add a new rule to this ruleset thanks to the returned file descriptor referring to this ruleset. The rule will only enable to read the file hierarchy /usr. Without other rule, write actions would then be denied by the ruleset. To add /usr to the ruleset, we open it with the O_PATH flag and fill the &struct landlock_attr_path_beneath with this file descriptor.

int err;
struct landlock_attr_path_beneath path_beneath = {
    .ruleset_fd = ruleset_fd,
    .allowed_access =
        LANDLOCK_ACCESS_FS_READ |
        LANDLOCK_ACCESS_FS_READDIR |
        LANDLOCK_ACCESS_FS_EXECUTE,
};

path_beneath.allowed_access &= attr_features.access_fs;
path_beneath.parent_fd = open("/usr", O_PATH | O_CLOEXEC);
if (path_beneath.parent_fd < 0) {
    perror("Failed to open file");
    close(ruleset_fd);
    return 1;
}
err = landlock(LANDLOCK_CMD_ADD_RULE, LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
        sizeof(path_beneath), &path_beneath);
close(path_beneath.parent_fd);
if (err) {
    perror("Failed to update ruleset");
    close(ruleset_fd);
    return 1;
}

We now have a ruleset with one rule allowing read access to /usr while denying all accesses featured in attr_features.access_fs to everything else on the filesystem. The next step is to restrict the current thread from gaining more privileges (e.g. thanks to a SUID binary).

if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
    perror("Failed to restrict privileges");
    close(ruleset_fd);
    return 1;
}

The current thread is now ready to sandbox itself with the ruleset.

struct landlock_attr_enforce attr_enforce = {
    .ruleset_fd = ruleset_fd,
};

if (landlock(LANDLOCK_CMD_ENFORCE_RULESET, LANDLOCK_OPT_ENFORCE_RULESET,
        sizeof(attr_enforce), &attr_enforce)) {
    perror("Failed to enforce ruleset");
    close(ruleset_fd);
    return 1;
}
close(ruleset_fd);

If this last system call succeeds, the current thread is now restricted and this policy will be enforced on all its subsequently created children as well. Once a thread is landlocked, there is no way to remove its security policy, only adding more restrictions is allowed. These threads are now in a new Landlock domain, merge of their parent one (if any) with the new ruleset.

A full working code can be found in samples/landlock/sandboxer.c.

Inheritance

Every new thread resulting from a clone(2) inherits Landlock program restrictions from its parent. This is similar to the seccomp inheritance (cf. Seccomp BPF (SECure COMPuting with filters)) or any other LSM dealing with task’s credentials(7). For instance, one process’ thread may apply Landlock rules to itself, but they will not be automatically applied to other sibling threads (unlike POSIX thread credential changes, cf. nptl(7)).

Ptrace restrictions

A sandboxed process has less privileges than a non-sandboxed process and must then be subject to additional restrictions when manipulating another process. To be allowed to use ptrace(2) and related syscalls on a target process, a sandboxed process should have a subset of the target process rules, which means the tracee must be in a sub-domain of the tracer.

The landlock syscall and its arguments

long sys_landlock(unsigned int command, unsigned int options, size_t attr1_size, void __user * attr1_ptr, size_t attr2_size, void __user * attr2_ptr)

System call to enable a process to safely sandbox itself

Parameters

unsigned int command
Landlock command to perform miscellaneous, but safe, actions. Cf. Commands.
unsigned int options
Bitmask of options dedicated to one command. Cf. Options.
size_t attr1_size
First attribute size (i.e. size of the struct).
void __user * attr1_ptr
Pointer to the first attribute. Cf. Attributes.
size_t attr2_size
Unused for now.
void __user * attr2_ptr
Unused for now.

Description

The command and options arguments enable a seccomp-bpf policy to control the requested actions. However, it should be noted that Landlock is designed from the ground to enable unprivileged process to drop privileges and accesses in a way that can not harm other processes. This syscall and all its arguments should then be allowed for any process, which will then enable applications to strengthen the security of the whole system.

attr2_size and attr2_ptr describe a second attribute which could be used in the future to compose with the first attribute (e.g. a landlock_attr_path_beneath with a landlock_attr_ioctl).

The order of return errors begins with ENOPKG (disabled Landlock), EOPNOTSUPP (unknown command or option) and then EINVAL (invalid attribute). The other error codes may be specific to each command.

Commands

enum landlock_cmd

Landlock commands

Constants

LANDLOCK_CMD_GET_FEATURES
Asks the kernel for supported Landlock features. The option argument must contains LANDLOCK_OPT_GET_FEATURES. This commands fills the struct landlock_attr_features provided as first attribute.
LANDLOCK_CMD_CREATE_RULESET
Creates a new ruleset and return its file descriptor on success. The option argument must contains LANDLOCK_OPT_CREATE_RULESET. The ruleset is defined by the struct landlock_attr_ruleset provided as first attribute.
LANDLOCK_CMD_ADD_RULE
Adds a rule to a ruleset. The option argument must contains LANDLOCK_OPT_ADD_RULE_PATH_BENEATH. The ruleset and the rule are both defined by the struct landlock_attr_path_beneath provided as first attribute.
LANDLOCK_CMD_ENFORCE_RULESET
Enforces a ruleset on the current process. The option argument must contains LANDLOCK_OPT_ENFORCE_RULESET. The ruleset is defined by the struct landlock_attr_enforce provided as first attribute.

Description

First argument of sys_landlock().

Options

These options may be used as second argument of sys_landlock(). Each command have a dedicated set of options, represented as bitmasks. For two different commands, their options may overlap. Each command have at least one option defining the used attribute type. This also enables to always have a usable struct landlock_attr_features (i.e. filled with bits).

Options for LANDLOCK_CMD_GET_FEATURES

  • LANDLOCK_OPT_GET_FEATURES: the attr type is struct landlock_attr_features.

Options for LANDLOCK_CMD_CREATE_RULESET

  • LANDLOCK_OPT_CREATE_RULESET: the attr type is struct landlock_attr_ruleset.

Options for LANDLOCK_CMD_ADD_RULE

  • LANDLOCK_OPT_ADD_RULE_PATH_BENEATH: the attr type is struct landlock_attr_path_beneath.

Options for LANDLOCK_CMD_ENFORCE_RULESET

  • LANDLOCK_OPT_ENFORCE_RULESET: the attr type is struct landlock_attr_enforce.

Attributes

struct landlock_attr_features

Receives the supported features

Definition

struct landlock_attr_features {
  __aligned_u64 options_get_features;
  __aligned_u64 options_create_ruleset;
  __aligned_u64 options_add_rule;
  __aligned_u64 options_enforce_ruleset;
  __aligned_u64 access_fs;
  __aligned_u64 size_attr_ruleset;
  __aligned_u64 size_attr_path_beneath;
};

Members

options_get_features
Options supported by the LANDLOCK_CMD_GET_FEATURES command. Cf. Options.
options_create_ruleset
Options supported by the LANDLOCK_CMD_CREATE_RULESET command. Cf. Options.
options_add_rule
Options supported by the LANDLOCK_CMD_ADD_RULE command. Cf. Options.
options_enforce_ruleset
Options supported by the LANDLOCK_CMD_ENFORCE_RULESET command. Cf. Options.
access_fs
Subset of file system access supported by the running kernel, used in struct landlock_attr_ruleset and struct landlock_attr_path_beneath. Cf. Filesystem flags.
size_attr_ruleset
Size of the struct landlock_attr_ruleset as known by the kernel (i.e. sizeof(struct landlock_attr_ruleset)).
size_attr_path_beneath
Size of the struct landlock_attr_path_beneath as known by the kernel (i.e. sizeof(struct landlock_path_beneath)).

Description

This struct should be allocated by user space but it will be filled by the kernel to indicate the subset of Landlock features effectively handled by the running kernel. This enables backward compatibility for applications which are developed on a newer kernel than the one running the application. This helps avoid hard errors that may entirely disable the use of Landlock features because some of them may not be supported. Indeed, because Landlock is a security feature, even if the kernel doesn’t support all the requested features, user space applications should still use the subset which is supported by the running kernel. Indeed, a partial security policy can still improve the security of the application and better protect the user (i.e. best-effort approach). The LANDLOCK_CMD_GET_FEATURES command and struct landlock_attr_features are future-proof because the future unknown fields requested by user space (i.e. a larger struct landlock_attr_features) can still be filled with zeros.

The Landlock commands will fail if an unsupported option or access is requested. By firstly requesting the supported options and accesses, it is quite easy for the developer to binary AND these returned bitmasks with the used options and accesses from the attribute structs (e.g. struct landlock_attr_ruleset), and even infer the supported Landlock commands. Indeed, because each command must support at least one option, the options_* fields are always filled if the related commands are supported. The supported attributes are also discoverable thanks to the size_* fields. All this data enable to create applications doing their best to sandbox themselves regardless of the running kernel.

struct landlock_attr_ruleset

Defines a new ruleset

Definition

struct landlock_attr_ruleset {
  __aligned_u64 handled_access_fs;
};

Members

handled_access_fs
Bitmask of actions (cf. Filesystem flags) that is handled by this ruleset and should then be forbidden if no rule explicitly allow them. This is needed for backward compatibility reasons. The user space code should check the effectively supported actions thanks to LANDLOCK_CMD_GET_SUPPORTED and struct landlock_attr_features, and then adjust the arguments of the next calls to sys_landlock() accordingly.

Description

Used as first attribute for the LANDLOCK_CMD_CREATE_RULESET command and with the LANDLOCK_OPT_CREATE_RULESET option.

struct landlock_attr_path_beneath

Defines a path hierarchy

Definition

struct landlock_attr_path_beneath {
  __aligned_u64 ruleset_fd;
  __aligned_u64 parent_fd;
  __aligned_u64 allowed_access;
};

Members

ruleset_fd
File descriptor tied to the ruleset which should be extended with this new access.
parent_fd
File descriptor, open with O_PATH, which identify the parent directory of a file hierarchy, or just a file.
allowed_access
Bitmask of allowed actions for this file hierarchy (cf. Filesystem flags).
struct landlock_attr_enforce

Describes the enforcement

Definition

struct landlock_attr_enforce {
  __aligned_u64 ruleset_fd;
};

Members

ruleset_fd
File descriptor tied to the ruleset to merge with the current domain.

Access rights

A set of actions on kernel objects may be defined by an attribute (e.g. struct landlock_attr_path_beneath) and a bitmask of access.

Filesystem flags

These flags enable to restrict a sandbox process to a set of of actions on files and directories. Files or directories opened before the sandboxing are not subject to these restrictions.

  • LANDLOCK_ACCESS_FS_READ: Open or map a file with read access.
  • LANDLOCK_ACCESS_FS_READDIR: List the content of a directory.
  • LANDLOCK_ACCESS_FS_GETATTR: Read metadata of a file or a directory.
  • LANDLOCK_ACCESS_FS_WRITE: Write to a file.
  • LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file.
  • LANDLOCK_ACCESS_FS_LOCK: Lock a file.
  • LANDLOCK_ACCESS_FS_CHMOD: Change DAC permissions on a file or a directory.
  • LANDLOCK_ACCESS_FS_CHOWN: Change the owner of a file or a directory.
  • LANDLOCK_ACCESS_FS_CHGRP: Change the group of a file or a directory.
  • LANDLOCK_ACCESS_FS_IOCTL: Send various command to a special file, cf. ioctl(2).
  • LANDLOCK_ACCESS_FS_LINK_TO: Link a file into a directory.
  • LANDLOCK_ACCESS_FS_RENAME_FROM: Rename a file or a directory.
  • LANDLOCK_ACCESS_FS_RENAME_TO: Rename a file or a directory.
  • LANDLOCK_ACCESS_FS_RMDIR: Remove an empty directory.
  • LANDLOCK_ACCESS_FS_UNLINK: Remove a file.
  • LANDLOCK_ACCESS_FS_MAKE_CHAR: Create a character device.
  • LANDLOCK_ACCESS_FS_MAKE_DIR: Create a directory.
  • LANDLOCK_ACCESS_FS_MAKE_REG: Create a regular file.
  • LANDLOCK_ACCESS_FS_MAKE_SOCK: Create a UNIX domain socket.
  • LANDLOCK_ACCESS_FS_MAKE_FIFO: Create a named pipe.
  • LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create a block device.
  • LANDLOCK_ACCESS_FS_MAKE_SYM: Create a symbolic link.
  • LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
  • LANDLOCK_ACCESS_FS_CHROOT: Change the root directory of the current process.
  • LANDLOCK_ACCESS_FS_OPEN: Open a file or a directory. This flag is set for any actions (e.g. read, write, execute) requested to open a file or directory.
  • LANDLOCK_ACCESS_FS_MAP: Map a file. This flag is set for any actions (e.g. read, write, execute) requested to map a file.

There is currently no restriction for directory walking e.g., chdir(2).

Questions and answers

What about user space sandbox managers?

Using user space process to enforce restrictions on kernel resources can lead to race conditions or inconsistent evaluations (i.e. Incorrect mirroring of the OS code and state).

What about namespaces and containers?

Namespaces can help create sandboxes but they are not designed for access-control and then miss useful features for such use case (e.g. no fine-grained restrictions). Moreover, their complexity can lead to security issues, especially when untrusted processes can manipulate them (cf. Controlling access to user namespaces).

Additional documentation

See https://landlock.io