Skip to content

Commit

Permalink
Merge tag 'landlock-6.12-rc1' of git://git.kernel.org/pub/scm/linux/k…
Browse files Browse the repository at this point in the history
…ernel/git/mic/linux

Pull landlock updates from Mickaël Salaün:
 "We can now scope a Landlock domain thanks to a new "scoped" field that
  can deny interactions with resources outside of this domain.

  The LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET flag denies connections to an
  abstract UNIX socket created outside of the current scoped domain, and
  the LANDLOCK_SCOPE_SIGNAL flag denies sending a signal to processes
  outside of the current scoped domain.

  These restrictions also apply to nested domains according to their
  scope. The related changes will also be useful to support other kind
  of IPC isolations"

* tag 'landlock-6.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  landlock: Document LANDLOCK_SCOPE_SIGNAL
  samples/landlock: Add support for signal scoping
  selftests/landlock: Test signal created by out-of-bound message
  selftests/landlock: Test signal scoping for threads
  selftests/landlock: Test signal scoping
  landlock: Add signal scoping
  landlock: Document LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
  samples/landlock: Add support for abstract UNIX socket scoping
  selftests/landlock: Test inherited restriction of abstract UNIX socket
  selftests/landlock: Test connected and unconnected datagram UNIX socket
  selftests/landlock: Test UNIX sockets with any address formats
  selftests/landlock: Test abstract UNIX socket scoping
  selftests/landlock: Test handling of unknown scope
  landlock: Add abstract UNIX socket scoping
  • Loading branch information
torvalds committed Sep 24, 2024
2 parents 24f772d + 1ca9808 commit e1b061b
Show file tree
Hide file tree
Showing 21 changed files with 2,359 additions and 47 deletions.
58 changes: 56 additions & 2 deletions Documentation/userspace-api/landlock.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Landlock: unprivileged access control
=====================================

:Author: Mickaël Salaün
:Date: July 2024
:Date: September 2024

The goal of Landlock is to enable to restrict ambient rights (e.g. global
filesystem or network access) for a set of processes. Because Landlock
Expand Down Expand Up @@ -81,6 +81,9 @@ to be explicit about the denied-by-default access rights.
.handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped =
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
};
Because we may not know on which kernel version an application will be
Expand Down Expand Up @@ -119,6 +122,11 @@ version, and only use the available subset of access rights:
case 4:
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
__attribute__((fallthrough));
case 5:
/* Removes LANDLOCK_SCOPE_* for ABI < 6 */
ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL);
}
This enables to create an inclusive ruleset that will contain our rules.
Expand Down Expand Up @@ -306,6 +314,38 @@ To be allowed to use :manpage:`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.

IPC scoping
-----------

Similar to the implicit `Ptrace restrictions`_, we may want to further restrict
interactions between sandboxes. Each Landlock domain can be explicitly scoped
for a set of actions by specifying it on a ruleset. For example, if a
sandboxed process should not be able to :manpage:`connect(2)` to a
non-sandboxed process through abstract :manpage:`unix(7)` sockets, we can
specify such restriction with ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET``.
Moreover, if a sandboxed process should not be able to send a signal to a
non-sandboxed process, we can specify this restriction with
``LANDLOCK_SCOPE_SIGNAL``.

A sandboxed process can connect to a non-sandboxed process when its domain is
not scoped. If a process's domain is scoped, it can only connect to sockets
created by processes in the same scope.
Moreover, If a process is scoped to send signal to a non-scoped process, it can
only send signals to processes in the same scope.

A connected datagram socket behaves like a stream socket when its domain is
scoped, meaning if the domain is scoped after the socket is connected , it can
still :manpage:`send(2)` data just like a stream socket. However, in the same
scenario, a non-connected datagram socket cannot send data (with
:manpage:`sendto(2)`) outside its scope.

A process with a scoped domain can inherit a socket created by a non-scoped
process. The process cannot connect to this socket since it has a scoped
domain.

IPC scoping does not support exceptions, so if a domain is scoped, no rules can
be added to allow access to resources or processes outside of the scope.

Truncating files
----------------

Expand Down Expand Up @@ -404,7 +444,7 @@ Access rights
-------------

.. kernel-doc:: include/uapi/linux/landlock.h
:identifiers: fs_access net_access
:identifiers: fs_access net_access scope

Creating a new ruleset
----------------------
Expand Down Expand Up @@ -541,6 +581,20 @@ earlier ABI.
Starting with the Landlock ABI version 5, it is possible to restrict the use of
:manpage:`ioctl(2)` using the new ``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right.

Abstract UNIX socket scoping (ABI < 6)
--------------------------------------

Starting with the Landlock ABI version 6, it is possible to restrict
connections to an abstract :manpage:`unix(7)` socket by setting
``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` to the ``scoped`` ruleset attribute.

Signal scoping (ABI < 6)
------------------------

Starting with the Landlock ABI version 6, it is possible to restrict
:manpage:`signal(7)` sending by setting ``LANDLOCK_SCOPE_SIGNAL`` to the
``scoped`` ruleset attribute.

.. _kernel_support:

Kernel support
Expand Down
30 changes: 30 additions & 0 deletions include/uapi/linux/landlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ struct landlock_ruleset_attr {
* flags`_).
*/
__u64 handled_access_net;
/**
* @scoped: Bitmask of scopes (cf. `Scope flags`_)
* restricting a Landlock domain from accessing outside
* resources (e.g. IPCs).
*/
__u64 scoped;
};

/*
Expand Down Expand Up @@ -274,4 +280,28 @@ struct landlock_net_port_attr {
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
/* clang-format on */

/**
* DOC: scope
*
* Scope flags
* ~~~~~~~~~~~
*
* These flags enable to isolate a sandboxed process from a set of IPC actions.
* Setting a flag for a ruleset will isolate the Landlock domain to forbid
* connections to resources outside the domain.
*
* Scopes:
*
* - %LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process from
* connecting to an abstract UNIX socket created by a process outside the
* related Landlock domain (e.g. a parent domain or a non-sandboxed process).
* - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal
* to another process outside the domain.
*/
/* clang-format off */
#define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1)
/* clang-format on*/

#endif /* _UAPI_LINUX_LANDLOCK_H */
73 changes: 69 additions & 4 deletions samples/landlock/sandboxer.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <fcntl.h>
#include <linux/landlock.h>
#include <linux/prctl.h>
#include <linux/socket.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
Expand All @@ -22,6 +23,7 @@
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdbool.h>

#ifndef landlock_create_ruleset
static inline int
Expand Down Expand Up @@ -55,6 +57,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_FS_RW_NAME "LL_FS_RW"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
#define ENV_SCOPED_NAME "LL_SCOPED"
#define ENV_DELIMITER ":"

static int parse_path(char *env_path, const char ***const path_list)
Expand Down Expand Up @@ -184,6 +187,55 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
return ret;
}

/* Returns true on error, false otherwise. */
static bool check_ruleset_scope(const char *const env_var,
struct landlock_ruleset_attr *ruleset_attr)
{
char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
bool error = false;
bool abstract_scoping = false;
bool signal_scoping = false;

/* Scoping is not supported by Landlock ABI */
if (!(ruleset_attr->scoped &
(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL)))
goto out_unset;

env_type_scope = getenv(env_var);
/* Scoping is not supported by the user */
if (!env_type_scope || strcmp("", env_type_scope) == 0)
goto out_unset;

env_type_scope = strdup(env_type_scope);
env_type_scope_next = env_type_scope;
while ((ipc_scoping_name =
strsep(&env_type_scope_next, ENV_DELIMITER))) {
if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) {
abstract_scoping = true;
} else if (strcmp("s", ipc_scoping_name) == 0 &&
!signal_scoping) {
signal_scoping = true;
} else {
fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",
ipc_scoping_name);
error = true;
goto out_free_name;
}
}

out_free_name:
free(env_type_scope);

out_unset:
if (!abstract_scoping)
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
if (!signal_scoping)
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL;

unsetenv(env_var);
return error;
}

/* clang-format off */

#define ACCESS_FS_ROUGHLY_READ ( \
Expand All @@ -208,7 +260,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,

/* clang-format on */

#define LANDLOCK_ABI_LAST 5
#define LANDLOCK_ABI_LAST 6

int main(const int argc, char *const argv[], char *const *const envp)
{
Expand All @@ -223,14 +275,16 @@ int main(const int argc, char *const argv[], char *const *const envp)
.handled_access_fs = access_fs_rw,
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
};

if (argc < 2) {
fprintf(stderr,
"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s "
"<cmd> [args]...\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
ENV_TCP_CONNECT_NAME, argv[0]);
ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
fprintf(stderr,
"Execute a command in a restricted environment.\n\n");
fprintf(stderr,
Expand All @@ -251,15 +305,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
fprintf(stderr,
"* %s: list of ports allowed to connect (client).\n",
ENV_TCP_CONNECT_NAME);
fprintf(stderr, "* %s: list of scoped IPCs.\n",
ENV_SCOPED_NAME);
fprintf(stderr,
"\nexample:\n"
"%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
"%s=\"9418\" "
"%s=\"80:443\" "
"%s=\"a:s\" "
"%s bash -i\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
ENV_TCP_CONNECT_NAME, argv[0]);
ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
fprintf(stderr,
"This sandboxer can use Landlock features "
"up to ABI version %d.\n",
Expand Down Expand Up @@ -327,6 +384,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;

__attribute__((fallthrough));
case 5:
/* Removes LANDLOCK_SCOPE_* for ABI < 6 */
ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL);
fprintf(stderr,
"Hint: You should update the running kernel "
"to leverage Landlock features "
Expand Down Expand Up @@ -358,6 +420,9 @@ int main(const int argc, char *const argv[], char *const *const envp)
~LANDLOCK_ACCESS_NET_CONNECT_TCP;
}

if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
return 1;

ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
Expand Down
2 changes: 1 addition & 1 deletion security/landlock/cred.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ landlock_cred(const struct cred *cred)
return cred->security + landlock_blob_sizes.lbs_cred;
}

static inline const struct landlock_ruleset *landlock_get_current_domain(void)
static inline struct landlock_ruleset *landlock_get_current_domain(void)
{
return landlock_cred(current_cred())->domain;
}
Expand Down
25 changes: 25 additions & 0 deletions security/landlock/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,29 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
return -EACCES;
}

static void hook_file_set_fowner(struct file *file)
{
struct landlock_ruleset *new_dom, *prev_dom;

/*
* Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix
* file_set_fowner LSM hook inconsistencies").
*/
lockdep_assert_held(&file_f_owner(file)->lock);
new_dom = landlock_get_current_domain();
landlock_get_ruleset(new_dom);
prev_dom = landlock_file(file)->fown_domain;
landlock_file(file)->fown_domain = new_dom;

/* Called in an RCU read-side critical section. */
landlock_put_ruleset_deferred(prev_dom);
}

static void hook_file_free_security(struct file *file)
{
landlock_put_ruleset_deferred(landlock_file(file)->fown_domain);
}

static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),

Expand All @@ -1663,6 +1686,8 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(file_truncate, hook_file_truncate),
LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner),
LSM_HOOK_INIT(file_free_security, hook_file_free_security),
};

__init void landlock_add_fs_hooks(void)
Expand Down
7 changes: 7 additions & 0 deletions security/landlock/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ struct landlock_file_security {
* needed to authorize later operations on the open file.
*/
access_mask_t allowed_access;
/**
* @fown_domain: Domain of the task that set the PID that may receive a
* signal e.g., SIGURG when writing MSG_OOB to the related socket.
* This pointer is protected by the related file->f_owner->lock, as for
* fown_struct's members: pid, uid, and euid.
*/
struct landlock_ruleset *fown_domain;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions security/landlock/limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)

#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
/* clang-format on */

#endif /* _SECURITY_LANDLOCK_LIMITS_H */
7 changes: 5 additions & 2 deletions security/landlock/ruleset.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)

struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t fs_access_mask,
const access_mask_t net_access_mask)
const access_mask_t net_access_mask,
const access_mask_t scope_mask)
{
struct landlock_ruleset *new_ruleset;

/* Informs about useless ruleset. */
if (!fs_access_mask && !net_access_mask)
if (!fs_access_mask && !net_access_mask && !scope_mask)
return ERR_PTR(-ENOMSG);
new_ruleset = create_ruleset(1);
if (IS_ERR(new_ruleset))
Expand All @@ -66,6 +67,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
if (net_access_mask)
landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
if (scope_mask)
landlock_add_scope_mask(new_ruleset, scope_mask, 0);
return new_ruleset;
}

Expand Down
Loading

0 comments on commit e1b061b

Please sign in to comment.