Commit 136cc1e1 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull landlock updates from Mickaël Salaün:
 "A Landlock ruleset can now handle two new access rights:
  LANDLOCK_ACCESS_NET_BIND_TCP and LANDLOCK_ACCESS_NET_CONNECT_TCP. When
  handled, the related actions are denied unless explicitly allowed by a
  Landlock network rule for a specific port.

  The related patch series has been reviewed for almost two years, it
  has evolved a lot and we now have reached a decent design, code and
  testing. The refactored kernel code and the new test helpers also
  bring the foundation to support more network protocols.

  Test coverage for security/landlock is 92.4% of 710 lines according to
  gcc/gcov-13, and it was 93.1% of 597 lines before this series. The
  decrease in coverage is due to code refactoring to make the ruleset
  management more generic (i.e. dealing with inodes and ports) that also
  added new WARN_ON_ONCE() checks not possible to test from user space.

  syzkaller has been updated accordingly [4], and such patched instance
  (tailored to Landlock) has been running for a month, covering all the
  new network-related code [5]"

Link: https://lore.kernel.org/r/20231026014751.414649-1-konstantin.meskhidze@huawei.com [1]
Link: https://lore.kernel.org/r/CAHC9VhS1wwgH6NNd+cJz4MYogPiRV8NyPDd1yj5SpaxeUB4UVg@mail.gmail.com [2]
Link: https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next-history.git/commit/?id=c8dc5ee69d3a [3]
Link: https://github.com/google/syzkaller/pull/4266 [4]
Link: https://storage.googleapis.com/syzbot-assets/82e8608dec36/ci-upstream-linux-next-kasan-gce-root-ab577164.html#security%2flandlock%2fnet.c [5]

* tag 'landlock-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  selftests/landlock: Add tests for FS topology changes with network rules
  landlock: Document network support
  samples/landlock: Support TCP restrictions
  selftests/landlock: Add network tests
  selftests/landlock: Share enforce_ruleset() helper
  landlock: Support network rules with TCP bind and connect
  landlock: Refactor landlock_add_rule() syscall
  landlock: Refactor layer helpers
  landlock: Move and rename layer helpers
  landlock: Refactor merge/inherit_ruleset helpers
  landlock: Refactor landlock_find_rule/insert_rule helpers
  landlock: Allow FS topology changes for domains without such rule type
  landlock: Make ruleset's access masks more generic
parents 7ab89417 f12f8f84
Loading
Loading
Loading
Loading
+74 −25
Original line number Diff line number Diff line
@@ -8,13 +8,13 @@ Landlock: unprivileged access control
=====================================

:Author: Mickaël Salaün
:Date: October 2022
:Date: October 2023

The goal of Landlock is to enable to restrict ambient rights (e.g. global
filesystem access) for a set of processes.  Because Landlock is a stackable
LSM, it makes possible to create safe security sandboxes as new security layers
in addition to the existing system-wide access-controls. This kind of sandbox
is expected to help mitigate the security impact of bugs or
filesystem or network access) for a set of processes.  Because Landlock
is a stackable LSM, it makes possible to create safe security sandboxes as new
security layers in addition to the existing system-wide access-controls. This
kind of sandbox is expected to help mitigate the security impact of bugs or
unexpected/malicious behaviors in user space applications.  Landlock empowers
any process, including unprivileged ones, to securely restrict themselves.

@@ -28,20 +28,34 @@ appropriately <kernel_support>`.
Landlock rules
==============

A Landlock rule describes an action on an object.  An object is currently a
file hierarchy, and the related filesystem actions are defined with `access
rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
A Landlock rule describes an action on an object which the process intends to
perform.  A set of rules is aggregated in a ruleset, which can then restrict
the thread enforcing it, and its future children.

The two existing types of rules are:

Filesystem rules
    For these rules, the object is a file hierarchy,
    and the related filesystem actions are defined with
    `filesystem access rights`.

Network rules (since ABI v4)
    For these rules, the object is a TCP port,
    and the related actions are defined with `network access rights`.

Defining and enforcing a security policy
----------------------------------------

We first need to define the ruleset that will contain our rules.  For this
example, the ruleset will contain rules that only allow read actions, but write
actions will be denied.  The ruleset then needs to handle both of these kind of
actions.  This is required for backward and forward compatibility (i.e. the
kernel and user space may not know each other's supported restrictions), hence
the need to be explicit about the denied-by-default access rights.
We first need to define the ruleset that will contain our rules.

For this example, the ruleset will contain rules that only allow filesystem
read actions and establish a specific TCP connection. Filesystem write
actions and other TCP actions will be denied.

The ruleset then needs to handle both these kinds of actions.  This is
required for backward and forward compatibility (i.e. the kernel and user
space may not know each other's supported restrictions), hence the need
to be explicit about the denied-by-default access rights.

.. code-block:: c

@@ -62,6 +76,9 @@ the need to be explicit about the denied-by-default access rights.
            LANDLOCK_ACCESS_FS_MAKE_SYM |
            LANDLOCK_ACCESS_FS_REFER |
            LANDLOCK_ACCESS_FS_TRUNCATE,
        .handled_access_net =
            LANDLOCK_ACCESS_NET_BIND_TCP |
            LANDLOCK_ACCESS_NET_CONNECT_TCP,
    };

Because we may not know on which kernel version an application will be
@@ -70,9 +87,7 @@ should try to protect users as much as possible whatever the kernel they are
using.  To avoid binary enforcement (i.e. either all security features or
none), we can leverage a dedicated Landlock command to get the current version
of the Landlock ABI and adapt the handled accesses.  Let's check if we should
remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
access rights, which are only supported starting with the second and third
version of the ABI.
remove access rights which are only supported in higher versions of the ABI.

.. code-block:: c

@@ -92,6 +107,12 @@ version of the ABI.
    case 2:
        /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
        ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
        __attribute__((fallthrough));
    case 3:
        /* Removes network support for ABI < 4 */
        ruleset_attr.handled_access_net &=
            ~(LANDLOCK_ACCESS_NET_BIND_TCP |
              LANDLOCK_ACCESS_NET_CONNECT_TCP);
    }

This enables to create an inclusive ruleset that will contain our rules.
@@ -143,10 +164,23 @@ for the ruleset creation, by filtering access rights according to the Landlock
ABI version.  In this example, this is not required because all of the requested
``allowed_access`` rights are already available in ABI 1.

We now have a ruleset with one rule allowing read access to ``/usr`` while
denying all other handled accesses for the filesystem.  The next step is to
restrict the current thread from gaining more privileges (e.g. thanks to a SUID
binary).
For network access-control, we can add a set of rules that allow to use a port
number for a specific action: HTTPS connections.

.. code-block:: c

    struct landlock_net_port_attr net_port = {
        .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
        .port = 443,
    };

    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
                            &net_port, 0);

The next step is to restrict the current thread from gaining more privileges
(e.g. through a SUID binary).  We now have a ruleset with the first rule
allowing read access to ``/usr`` while denying all other handled accesses for
the filesystem, and a second rule allowing HTTPS connections.

.. code-block:: c

@@ -355,7 +389,7 @@ Access rights
-------------

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

Creating a new ruleset
----------------------
@@ -374,6 +408,7 @@ Extending a ruleset

.. kernel-doc:: include/uapi/linux/landlock.h
    :identifiers: landlock_rule_type landlock_path_beneath_attr
                  landlock_net_port_attr

Enforcing a ruleset
-------------------
@@ -387,9 +422,9 @@ Current limitations
Filesystem topology modification
--------------------------------

As for file renaming and linking, a sandboxed thread cannot modify its
filesystem topology, whether via :manpage:`mount(2)` or
:manpage:`pivot_root(2)`.  However, :manpage:`chroot(2)` calls are not denied.
Threads sandboxed with filesystem restrictions cannot modify filesystem
topology, whether via :manpage:`mount(2)` or :manpage:`pivot_root(2)`.
However, :manpage:`chroot(2)` calls are not denied.

Special filesystems
-------------------
@@ -451,6 +486,14 @@ always allowed when using a kernel that only supports the first or second ABI.
Starting with the Landlock ABI version 3, it is now possible to securely control
truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.

Network support (ABI < 4)
-------------------------

Starting with the Landlock ABI version 4, it is now possible to restrict TCP
bind and connect actions to only a set of allowed ports thanks to the new
``LANDLOCK_ACCESS_NET_BIND_TCP`` and ``LANDLOCK_ACCESS_NET_CONNECT_TCP``
access rights.

.. _kernel_support:

Kernel support
@@ -469,6 +512,12 @@ still enable it by adding ``lsm=landlock,[...]`` to
Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
configuration.

To be able to explicitly allow TCP operations (e.g., adding a network rule with
``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support TCP
(``CONFIG_INET=y``).  Otherwise, sys_landlock_add_rule() returns an
``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP
operation is already not possible.

Questions and answers
=====================

+55 −0
Original line number Diff line number Diff line
@@ -31,6 +31,12 @@ struct landlock_ruleset_attr {
	 * this access right.
	 */
	__u64 handled_access_fs;
	/**
	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
	 * that is handled by this ruleset and should then be forbidden if no
	 * rule explicitly allow them.
	 */
	__u64 handled_access_net;
};

/*
@@ -54,6 +60,11 @@ enum landlock_rule_type {
	 * landlock_path_beneath_attr .
	 */
	LANDLOCK_RULE_PATH_BENEATH = 1,
	/**
	 * @LANDLOCK_RULE_NET_PORT: Type of a &struct
	 * landlock_net_port_attr .
	 */
	LANDLOCK_RULE_NET_PORT,
};

/**
@@ -79,6 +90,31 @@ struct landlock_path_beneath_attr {
	 */
} __attribute__((packed));

/**
 * struct landlock_net_port_attr - Network port definition
 *
 * Argument of sys_landlock_add_rule().
 */
struct landlock_net_port_attr {
	/**
	 * @allowed_access: Bitmask of allowed access network for a port
	 * (cf. `Network flags`_).
	 */
	__u64 allowed_access;
	/**
	 * @port: Network port in host endianness.
	 *
	 * It should be noted that port 0 passed to :manpage:`bind(2)` will
	 * bind to an available port from a specific port range. This can be
	 * configured thanks to the ``/proc/sys/net/ipv4/ip_local_port_range``
	 * sysctl (also used for IPv6). A Landlock rule with port 0 and the
	 * ``LANDLOCK_ACCESS_NET_BIND_TCP`` right means that requesting to bind
	 * on port 0 is allowed and it will automatically translate to binding
	 * on the related port range.
	 */
	__u64 port;
};

/**
 * DOC: fs_access
 *
@@ -189,4 +225,23 @@ struct landlock_path_beneath_attr {
#define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
/* clang-format on */

/**
 * DOC: net_access
 *
 * Network flags
 * ~~~~~~~~~~~~~~~~
 *
 * These flags enable to restrict a sandboxed process to a set of network
 * actions. This is supported since the Landlock ABI version 4.
 *
 * TCP sockets with allowed actions:
 *
 * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
 * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
 *   a remote port.
 */
/* clang-format off */
#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
/* clang-format on */
#endif /* _UAPI_LINUX_LANDLOCK_H */
+100 −15
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@
 */

#define _GNU_SOURCE
#define __SANE_USERSPACE_TYPES__
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/landlock.h>
@@ -51,7 +53,9 @@ static inline int landlock_restrict_self(const int ruleset_fd,

#define ENV_FS_RO_NAME "LL_FS_RO"
#define ENV_FS_RW_NAME "LL_FS_RW"
#define ENV_PATH_TOKEN ":"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
#define ENV_DELIMITER ":"

static int parse_path(char *env_path, const char ***const path_list)
{
@@ -60,13 +64,13 @@ static int parse_path(char *env_path, const char ***const path_list)
	if (env_path) {
		num_paths++;
		for (i = 0; env_path[i]; i++) {
			if (env_path[i] == ENV_PATH_TOKEN[0])
			if (env_path[i] == ENV_DELIMITER[0])
				num_paths++;
		}
	}
	*path_list = malloc(num_paths * sizeof(**path_list));
	for (i = 0; i < num_paths; i++)
		(*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN);
		(*path_list)[i] = strsep(&env_path, ENV_DELIMITER);

	return num_paths;
}
@@ -81,7 +85,7 @@ static int parse_path(char *env_path, const char ***const path_list)

/* clang-format on */

static int populate_ruleset(const char *const env_var, const int ruleset_fd,
static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
			       const __u64 allowed_access)
{
	int num_paths, i, ret = 1;
@@ -143,6 +147,39 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
	return ret;
}

static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
				const __u64 allowed_access)
{
	int ret = 1;
	char *env_port_name, *strport;
	struct landlock_net_port_attr net_port = {
		.allowed_access = allowed_access,
		.port = 0,
	};

	env_port_name = getenv(env_var);
	if (!env_port_name)
		return 0;
	env_port_name = strdup(env_port_name);
	unsetenv(env_var);

	while ((strport = strsep(&env_port_name, ENV_DELIMITER))) {
		net_port.port = atoi(strport);
		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
				      &net_port, 0)) {
			fprintf(stderr,
				"Failed to update the ruleset with port \"%llu\": %s\n",
				net_port.port, strerror(errno));
			goto out_free_name;
		}
	}
	ret = 0;

out_free_name:
	free(env_port_name);
	return ret;
}

/* clang-format off */

#define ACCESS_FS_ROUGHLY_READ ( \
@@ -166,39 +203,58 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,

/* clang-format on */

#define LANDLOCK_ABI_LAST 3
#define LANDLOCK_ABI_LAST 4

int main(const int argc, char *const argv[], char *const *const envp)
{
	const char *cmd_path;
	char *const *cmd_argv;
	int ruleset_fd, abi;
	char *env_port_name;
	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;

	struct landlock_ruleset_attr ruleset_attr = {
		.handled_access_fs = access_fs_rw,
		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
	};

	if (argc < 2) {
		fprintf(stderr,
			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
			"usage: %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]);
		fprintf(stderr,
			"Launch a command in a restricted environment.\n\n");
		fprintf(stderr, "Environment variables containing paths, "
		fprintf(stderr,
			"Environment variables containing paths and ports "
			"each separated by a colon:\n");
		fprintf(stderr,
			"* %s: list of paths allowed to be used in a read-only way.\n",
			ENV_FS_RO_NAME);
		fprintf(stderr,
			"* %s: list of paths allowed to be used in a read-write way.\n",
			"* %s: list of paths allowed to be used in a read-write way.\n\n",
			ENV_FS_RW_NAME);
		fprintf(stderr,
			"Environment variables containing ports are optional "
			"and could be skipped.\n");
		fprintf(stderr,
			"* %s: list of ports allowed to bind (server).\n",
			ENV_TCP_BIND_NAME);
		fprintf(stderr,
			"* %s: list of ports allowed to connect (client).\n",
			ENV_TCP_CONNECT_NAME);
		fprintf(stderr,
			"\nexample:\n"
			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
			"%s=\"9418\" "
			"%s=\"80:443\" "
			"%s bash -i\n\n",
			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
			ENV_TCP_CONNECT_NAME, argv[0]);
		fprintf(stderr,
			"This sandboxer can use Landlock features "
			"up to ABI version %d.\n",
@@ -255,7 +311,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
	case 2:
		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;

		__attribute__((fallthrough));
	case 3:
		/* Removes network support for ABI < 4 */
		ruleset_attr.handled_access_net &=
			~(LANDLOCK_ACCESS_NET_BIND_TCP |
			  LANDLOCK_ACCESS_NET_CONNECT_TCP);
		fprintf(stderr,
			"Hint: You should update the running kernel "
			"to leverage Landlock features "
@@ -274,18 +335,42 @@ int main(const int argc, char *const argv[], char *const *const envp)
	access_fs_ro &= ruleset_attr.handled_access_fs;
	access_fs_rw &= ruleset_attr.handled_access_fs;

	/* Removes bind access attribute if not supported by a user. */
	env_port_name = getenv(ENV_TCP_BIND_NAME);
	if (!env_port_name) {
		ruleset_attr.handled_access_net &=
			~LANDLOCK_ACCESS_NET_BIND_TCP;
	}
	/* Removes connect access attribute if not supported by a user. */
	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
	if (!env_port_name) {
		ruleset_attr.handled_access_net &=
			~LANDLOCK_ACCESS_NET_CONNECT_TCP;
	}

	ruleset_fd =
		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
	if (ruleset_fd < 0) {
		perror("Failed to create a ruleset");
		return 1;
	}
	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {

	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
		goto err_close_ruleset;
	}
	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
		goto err_close_ruleset;
	}
	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {

	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
				 LANDLOCK_ACCESS_NET_BIND_TCP)) {
		goto err_close_ruleset;
	}
	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
		goto err_close_ruleset;
	}

	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
		perror("Failed to restrict privileges");
		goto err_close_ruleset;
+1 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
config SECURITY_LANDLOCK
	bool "Landlock support"
	depends on SECURITY
	select SECURITY_NETWORK
	select SECURITY_PATH
	help
	  Landlock is a sandboxing mechanism that enables processes to restrict
+2 −0
Original line number Diff line number Diff line
@@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o

landlock-y := setup.o syscalls.o object.o ruleset.o \
	cred.o ptrace.o fs.o

landlock-$(CONFIG_INET) += net.o
Loading