Merge: landlock: update landlock-lsm headers for api v5 and v6 in RHEL-9

MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/7037

JIRA: https://issues.redhat.com/browse/RHEL-94688

Customer request to update landlock-lsm headers to better match RHEL-10

Omitted-fix: 8d6650646ce4 ("bpf: syzkaller found null ptr deref in unix_bpf proto add")
Omitted-fix: 16b2f264983d ("bpf: sockmap, fix proto update hook to avoid dup calls")

Signed-off-by: Ryan Sullivan <rysulliv@redhat.com>

Approved-by: Felix Maurer <fmaurer@redhat.com>
Approved-by: Jay Shin <jaeshin@redhat.com>
Approved-by: Ondrej Mosnáček <omosnacek@gmail.com>
Approved-by: CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com>

Merged-by: Augusto Caringi <acaringi@redhat.com>
This commit is contained in:
Augusto Caringi 2025-07-30 15:15:21 -03:00
commit 6c4a30f494
22 changed files with 1824 additions and 106 deletions

View File

@ -8,7 +8,7 @@ Landlock: unprivileged access control
=====================================
:Author: Mickaël Salaün
:Date: April 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
@ -81,6 +81,8 @@ 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,
};
Because we may not know on which kernel version an application will be
@ -119,6 +121,10 @@ 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_ABSTRACT_UNIX_SOCKET for ABI < 6 */
ruleset_attr.scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
}
This enables to create an inclusive ruleset that will contain our rules.
@ -306,6 +312,33 @@ 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``.
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.
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
----------------
@ -404,7 +437,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
----------------------
@ -541,6 +574,13 @@ 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.
.. _kernel_support:
Kernel support

View File

@ -106,6 +106,7 @@ struct sk_psock {
struct mutex work_mutex;
struct sk_psock_work_state work_state;
struct work_struct work;
struct sock *sk_pair;
struct rcu_work rwork;
};

View File

@ -88,6 +88,7 @@ static inline void unix_state_lock_nested(struct sock *sk,
spin_lock_nested(&unix_sk(sk)->lock, subclass);
}
#define unix_peer(sk) (unix_sk(sk)->peer)
#define peer_wait peer_wq.wait
long unix_inq_len(struct sock *sk);

View File

@ -12,31 +12,44 @@
#include <linux/types.h>
/**
* struct landlock_ruleset_attr - Ruleset definition
* struct landlock_ruleset_attr - Ruleset definition.
*
* Argument of sys_landlock_create_ruleset(). This structure can grow in
* future versions.
* Argument of sys_landlock_create_ruleset().
*
* This structure defines a set of *handled access rights*, a set of actions on
* different object types, which should be denied by default when the ruleset is
* enacted. Vice versa, access rights that are not specifically listed here are
* not going to be denied by this ruleset when it is enacted.
*
* For historical reasons, the %LANDLOCK_ACCESS_FS_REFER right is always denied
* by default, even when its bit is not set in @handled_access_fs. In order to
* add new rules with this access right, the bit must still be set explicitly
* (cf. `Filesystem flags`_).
*
* The explicit listing of *handled access rights* is required for backwards
* compatibility reasons. In most use cases, processes that use Landlock will
* *handle* a wide range or all access rights that they know about at build time
* (and that they have tested with a kernel that supported them all).
*
* This structure can grow in future Landlock versions.
*/
struct landlock_ruleset_attr {
/**
* @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: it is a deny-by-default list that should
* contain as much Landlock access rights as possible. Indeed, all
* Landlock filesystem access rights that are not part of
* handled_access_fs are allowed. This is needed for backward
* compatibility reasons. One exception is the
* %LANDLOCK_ACCESS_FS_REFER access right, which is always implicitly
* handled, but must still be explicitly handled to add new rules with
* this access right.
* @handled_access_fs: Bitmask of handled filesystem actions
* (cf. `Filesystem flags`_).
*/
__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.
* @handled_access_net: Bitmask of handled network actions (cf. `Network
* 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;
};
/*
@ -97,20 +110,21 @@ struct landlock_path_beneath_attr {
*/
struct landlock_net_port_attr {
/**
* @allowed_access: Bitmask of allowed access network for a port
* @allowed_access: Bitmask of allowed network actions 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.
* It should be noted that port 0 passed to :manpage:`bind(2)` will bind
* to an available port from the ephemeral port range. This can be
* configured with 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;
};
@ -131,10 +145,10 @@ struct landlock_net_port_attr {
* The following access rights apply only to files:
*
* - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that
* you might additionally need the %LANDLOCK_ACCESS_FS_TRUNCATE right in order
* to overwrite files with :manpage:`open(2)` using ``O_TRUNC`` or
* :manpage:`creat(2)`.
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. When
* opening files for writing, you will often additionally need the
* %LANDLOCK_ACCESS_FS_TRUNCATE right. In many cases, these system calls
* truncate existing files when overwriting them (e.g., :manpage:`creat(2)`).
* - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.
* - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`,
* :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with
@ -256,7 +270,7 @@ struct landlock_net_port_attr {
* 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:
* The following access rights apply to TCP port numbers:
*
* - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
* - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
@ -266,4 +280,25 @@ 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).
*/
/* clang-format off */
#define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
/* clang-format on*/
#endif /* _UAPI_LINUX_LANDLOCK_H */

View File

@ -833,6 +833,8 @@ static void sk_psock_destroy(struct work_struct *work)
if (psock->sk_redir)
sock_put(psock->sk_redir);
if (psock->sk_pair)
sock_put(psock->sk_pair);
sock_put(psock->sk);
kfree(psock);
}

View File

@ -205,8 +205,6 @@ static inline bool unix_secdata_eq(struct scm_cookie *scm, struct sk_buff *skb)
}
#endif /* CONFIG_SECURITY_NETWORK */
#define unix_peer(sk) (unix_sk(sk)->peer)
static inline int unix_our_peer(struct sock *sk, struct sock *osk)
{
return unix_peer(osk) == sk;

View File

@ -160,12 +160,17 @@ int unix_dgram_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool re
int unix_stream_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)
{
struct sock *sk_pair;
if (restore) {
sk->sk_write_space = psock->saved_write_space;
sock_replace_proto(sk, psock->sk_proto);
return 0;
}
sk_pair = unix_peer(sk);
sock_hold(sk_pair);
psock->sk_pair = sk_pair;
unix_stream_bpf_check_needs_rebuild(psock->sk_proto);
sock_replace_proto(sk, &unix_stream_bpf_prot);
return 0;

View File

@ -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>
@ -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
@ -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 str2num(const char *numstr, __u64 *num_dst)
@ -212,6 +215,48 @@ out_free_name:
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;
/* Scoping is not supported by Landlock ABI */
if (!(ruleset_attr->scoped & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET))
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 {
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;
unsetenv(env_var);
return error;
}
/* clang-format off */
#define ACCESS_FS_ROUGHLY_READ ( \
@ -236,7 +281,7 @@ out_free_name:
/* 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)
{
@ -251,14 +296,15 @@ 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,
};
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,
@ -279,15 +325,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 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",
@ -355,6 +404,10 @@ 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_ABSTRACT_UNIX_SOCKET for ABI < 6 */
ruleset_attr.scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
fprintf(stderr,
"Hint: You should update the running kernel "
"to leverage Landlock features "
@ -386,6 +439,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) {

View File

@ -21,13 +21,14 @@
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
#define LANDLOCK_SHIFT_ACCESS_FS 0
#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
#define LANDLOCK_SHIFT_ACCESS_NET LANDLOCK_NUM_ACCESS_FS
#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
#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 */

View File

@ -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))
@ -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;
}
@ -169,13 +172,9 @@ static void build_check_ruleset(void)
.num_rules = ~0,
.num_layers = ~0,
};
typeof(ruleset.access_masks[0]) access_masks = ~0;
BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
BUILD_BUG_ON(access_masks <
((LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) |
(LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET)));
}
/**

View File

@ -35,14 +35,17 @@ typedef u16 access_mask_t;
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
/* Makes sure all network access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
/* Makes sure all scoped rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
/* Ruleset access masks. */
typedef u32 access_masks_t;
/* Makes sure all ruleset access rights can be stored. */
static_assert(BITS_PER_TYPE(access_masks_t) >=
LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
struct access_masks {
access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
access_mask_t scope : LANDLOCK_NUM_SCOPE;
};
typedef u16 layer_mask_t;
/* Makes sure all layers can be checked. */
@ -226,14 +229,15 @@ struct landlock_ruleset {
* layers are set once and never changed for the
* lifetime of the ruleset.
*/
access_masks_t access_masks[];
struct access_masks access_masks[];
};
};
};
struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t access_mask_fs,
const access_mask_t access_mask_net);
const access_mask_t access_mask_net,
const access_mask_t scope_mask);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@ -265,8 +269,7 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
/* Should already be checked in sys_landlock_create_ruleset(). */
WARN_ON_ONCE(fs_access_mask != fs_mask);
ruleset->access_masks[layer_level] |=
(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
ruleset->access_masks[layer_level].fs |= fs_mask;
}
static inline void
@ -278,17 +281,25 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
/* Should already be checked in sys_landlock_create_ruleset(). */
WARN_ON_ONCE(net_access_mask != net_mask);
ruleset->access_masks[layer_level] |=
(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
ruleset->access_masks[layer_level].net |= net_mask;
}
static inline void
landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
const access_mask_t scope_mask, const u16 layer_level)
{
access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
/* Should already be checked in sys_landlock_create_ruleset(). */
WARN_ON_ONCE(scope_mask != mask);
ruleset->access_masks[layer_level].scope |= mask;
}
static inline access_mask_t
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level)
{
return (ruleset->access_masks[layer_level] >>
LANDLOCK_SHIFT_ACCESS_FS) &
LANDLOCK_MASK_ACCESS_FS;
return ruleset->access_masks[layer_level].fs;
}
static inline access_mask_t
@ -304,9 +315,14 @@ static inline access_mask_t
landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level)
{
return (ruleset->access_masks[layer_level] >>
LANDLOCK_SHIFT_ACCESS_NET) &
LANDLOCK_MASK_ACCESS_NET;
return ruleset->access_masks[layer_level].net;
}
static inline access_mask_t
landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level)
{
return ruleset->access_masks[layer_level].scope;
}
bool landlock_unmask_layers(const struct landlock_rule *const rule,

View File

@ -97,8 +97,9 @@ static void build_check_abi(void)
*/
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
ruleset_size += sizeof(ruleset_attr.handled_access_net);
ruleset_size += sizeof(ruleset_attr.scoped);
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
BUILD_BUG_ON(sizeof(ruleset_attr) != 16);
BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@ -149,7 +150,7 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write,
};
#define LANDLOCK_ABI_VERSION 5
#define LANDLOCK_ABI_VERSION 6
/**
* sys_landlock_create_ruleset - Create a new ruleset
@ -170,8 +171,9 @@ static const struct file_operations ruleset_fops = {
* Possible returned errors are:
*
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
* - %EINVAL: unknown @flags, or unknown access, or too small @size;
* - %E2BIG or %EFAULT: @attr or @size inconsistencies;
* - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
* - %E2BIG: @attr or @size inconsistencies;
* - %EFAULT: @attr or @size inconsistencies;
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
*/
SYSCALL_DEFINE3(landlock_create_ruleset,
@ -213,9 +215,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
LANDLOCK_MASK_ACCESS_NET)
return -EINVAL;
/* Checks IPC scoping content (and 32-bits cast). */
if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
return -EINVAL;
/* Checks arguments and transforms to kernel struct. */
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
ruleset_attr.handled_access_net);
ruleset_attr.handled_access_net,
ruleset_attr.scoped);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
@ -378,8 +385,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* with the new rule.
* @rule_type: Identify the structure type pointed to by @rule_attr:
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
* @rule_attr: Pointer to a rule (only of type &struct
* landlock_path_beneath_attr for now).
* @rule_attr: Pointer to a rule (matching the @rule_type).
* @flags: Must be 0.
*
* This system call enables to define a new rule and add it to an existing
@ -390,18 +396,20 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
* - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
* supported by the running kernel;
* - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
* - %EINVAL: @flags is not 0;
* - %EINVAL: The rule accesses are inconsistent (i.e.
* &landlock_path_beneath_attr.allowed_access or
* &landlock_net_port_attr.allowed_access is not a subset of the
* ruleset handled accesses), or &landlock_net_port_attr.port is
* greater than 65535;
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
* &landlock_net_port_attr.allowed_access is not a subset of the ruleset
* handled accesses)
* - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
* 0);
* - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
* member of @rule_attr is not a file descriptor as expected;
* - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
* @rule_attr is not the expected file descriptor type;
* - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
* - %EFAULT: @rule_attr inconsistency.
* - %EFAULT: @rule_attr was not a valid address.
*/
SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
const enum landlock_rule_type, rule_type,

View File

@ -13,6 +13,8 @@
#include <linux/lsm_hooks.h>
#include <linux/rcupdate.h>
#include <linux/sched.h>
#include <net/af_unix.h>
#include <net/sock.h>
#include "common.h"
#include "cred.h"
@ -108,9 +110,144 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
return task_ptrace(parent, current);
}
/**
* domain_is_scoped - Checks if the client domain is scoped in the same
* domain as the server.
*
* @client: IPC sender domain.
* @server: IPC receiver domain.
* @scope: The scope restriction criteria.
*
* Returns: True if the @client domain is scoped to access the @server,
* unless the @server is also scoped in the same domain as @client.
*/
static bool domain_is_scoped(const struct landlock_ruleset *const client,
const struct landlock_ruleset *const server,
access_mask_t scope)
{
int client_layer, server_layer;
struct landlock_hierarchy *client_walker, *server_walker;
/* Quick return if client has no domain */
if (WARN_ON_ONCE(!client))
return false;
client_layer = client->num_layers - 1;
client_walker = client->hierarchy;
/*
* client_layer must be a signed integer with greater capacity
* than client->num_layers to ensure the following loop stops.
*/
BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
server_layer = server ? (server->num_layers - 1) : -1;
server_walker = server ? server->hierarchy : NULL;
/*
* Walks client's parent domains down to the same hierarchy level
* as the server's domain, and checks that none of these client's
* parent domains are scoped.
*/
for (; client_layer > server_layer; client_layer--) {
if (landlock_get_scope_mask(client, client_layer) & scope)
return true;
client_walker = client_walker->parent;
}
/*
* Walks server's parent domains down to the same hierarchy level as
* the client's domain.
*/
for (; server_layer > client_layer; server_layer--)
server_walker = server_walker->parent;
for (; client_layer >= 0; client_layer--) {
if (landlock_get_scope_mask(client, client_layer) & scope) {
/*
* Client and server are at the same level in the
* hierarchy. If the client is scoped, the request is
* only allowed if this domain is also a server's
* ancestor.
*/
return server_walker != client_walker;
}
client_walker = client_walker->parent;
server_walker = server_walker->parent;
}
return false;
}
static bool sock_is_scoped(struct sock *const other,
const struct landlock_ruleset *const domain)
{
const struct landlock_ruleset *dom_other;
/* The credentials will not change. */
lockdep_assert_held(&unix_sk(other)->lock);
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
return domain_is_scoped(domain, dom_other,
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
}
static bool is_abstract_socket(struct sock *const sock)
{
struct unix_address *addr = unix_sk(sock)->addr;
if (!addr)
return false;
if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
addr->name->sun_path[0] == '\0')
return true;
return false;
}
static int hook_unix_stream_connect(struct sock *const sock,
struct sock *const other,
struct sock *const newsk)
{
const struct landlock_ruleset *const dom =
landlock_get_current_domain();
/* Quick return for non-landlocked tasks. */
if (!dom)
return 0;
if (is_abstract_socket(other) && sock_is_scoped(other, dom))
return -EPERM;
return 0;
}
static int hook_unix_may_send(struct socket *const sock,
struct socket *const other)
{
const struct landlock_ruleset *const dom =
landlock_get_current_domain();
if (!dom)
return 0;
/*
* Checks if this datagram socket was already allowed to be connected
* to other.
*/
if (unix_peer(sock->sk) == other->sk)
return 0;
if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom))
return -EPERM;
return 0;
}
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),
LSM_HOOK_INIT(unix_may_send, hook_unix_may_send),
};
__init void landlock_add_task_hooks(void)

View File

@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
ASSERT_EQ(5, landlock_create_ruleset(NULL, 0,
ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,

View File

@ -7,6 +7,7 @@
* Copyright © 2021 Microsoft Corporation
*/
#include <arpa/inet.h>
#include <errno.h>
#include <linux/landlock.h>
#include <linux/securebits.h>
@ -14,11 +15,14 @@
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../kselftest_harness.h"
#define TMP_DIR "tmp"
#ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__))
#endif
@ -226,3 +230,38 @@ enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
}
}
struct protocol_variant {
int domain;
int type;
};
struct service_fixture {
struct protocol_variant protocol;
/* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
unsigned short port;
union {
struct sockaddr_in ipv4_addr;
struct sockaddr_in6 ipv6_addr;
struct {
struct sockaddr_un unix_addr;
socklen_t unix_addr_len;
};
};
};
static pid_t __maybe_unused sys_gettid(void)
{
return syscall(__NR_gettid);
}
static void __maybe_unused set_unix_address(struct service_fixture *const srv,
const unsigned short index)
{
srv->unix_addr.sun_family = AF_UNIX;
sprintf(srv->unix_addr.sun_path,
"_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(),
index);
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
srv->unix_addr.sun_path[0] = '\0';
}

View File

@ -59,7 +59,6 @@ int open_tree(int dfd, const char *filename, unsigned int flags)
#define RENAME_EXCHANGE (1 << 1)
#endif
#define TMP_DIR "tmp"
#define BINARY_PATH "./true"
/* Paths (sibling number and depth) */

View File

@ -36,30 +36,6 @@ enum sandbox_type {
TCP_SANDBOX,
};
struct protocol_variant {
int domain;
int type;
};
struct service_fixture {
struct protocol_variant protocol;
/* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
unsigned short port;
union {
struct sockaddr_in ipv4_addr;
struct sockaddr_in6 ipv6_addr;
struct {
struct sockaddr_un unix_addr;
socklen_t unix_addr_len;
};
};
};
static pid_t sys_gettid(void)
{
return syscall(__NR_gettid);
}
static int set_service(struct service_fixture *const srv,
const struct protocol_variant prot,
const unsigned short index)
@ -92,12 +68,7 @@ static int set_service(struct service_fixture *const srv,
return 0;
case AF_UNIX:
srv->unix_addr.sun_family = prot.domain;
sprintf(srv->unix_addr.sun_path,
"_selftests-landlock-net-tid%d-index%d", sys_gettid(),
index);
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
srv->unix_addr.sun_path[0] = '\0';
set_unix_address(srv, index);
return 0;
}
return 1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Landlock scoped_domains variants
*
* See the hierarchy variants from ptrace_test.c
*
* Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2019-2020 ANSSI
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
/* clang-format on */
FIXTURE_VARIANT(scoped_domains)
{
bool domain_both;
bool domain_parent;
bool domain_child;
};
/*
* No domain
*
* P1-. P1 -> P2 : allow
* \ P2 -> P1 : allow
* 'P2
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, without_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = false,
.domain_child = false,
};
/*
* Child domain
*
* P1--. P1 -> P2 : allow
* \ P2 -> P1 : deny
* .'-----.
* | P2 |
* '------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, child_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = false,
.domain_child = true,
};
/*
* Parent domain
* .------.
* | P1 --. P1 -> P2 : deny
* '------' \ P2 -> P1 : allow
* '
* P2
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, parent_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = true,
.domain_child = false,
};
/*
* Parent + child domain (siblings)
* .------.
* | P1 ---. P1 -> P2 : deny
* '------' \ P2 -> P1 : deny
* .---'--.
* | P2 |
* '------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, sibling_domain) {
/* clang-format on */
.domain_both = false,
.domain_parent = true,
.domain_child = true,
};
/*
* Same domain (inherited)
* .-------------.
* | P1----. | P1 -> P2 : allow
* | \ | P2 -> P1 : allow
* | ' |
* | P2 |
* '-------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, inherited_domain) {
/* clang-format on */
.domain_both = true,
.domain_parent = false,
.domain_child = false,
};
/*
* Inherited + child domain
* .-----------------.
* | P1----. | P1 -> P2 : allow
* | \ | P2 -> P1 : deny
* | .-'----. |
* | | P2 | |
* | '------' |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, nested_domain) {
/* clang-format on */
.domain_both = true,
.domain_parent = false,
.domain_child = true,
};
/*
* Inherited + parent domain
* .-----------------.
* |.------. | P1 -> P2 : deny
* || P1 ----. | P2 -> P1 : allow
* |'------' \ |
* | ' |
* | P2 |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, nested_and_parent_domain) {
/* clang-format on */
.domain_both = true,
.domain_parent = true,
.domain_child = false,
};
/*
* Inherited + parent and child domain (siblings)
* .-----------------.
* | .------. | P1 -> P2 : deny
* | | P1 . | P2 -> P1 : deny
* | '------'\ |
* | \ |
* | .--'---. |
* | | P2 | |
* | '------' |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_domains, forked_domains) {
/* clang-format on */
.domain_both = true,
.domain_parent = true,
.domain_child = true,
};

View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Landlock scope test helpers
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
#define _GNU_SOURCE
#include <sys/types.h>
static void create_scoped_domain(struct __test_metadata *const _metadata,
const __u16 scope)
{
int ruleset_fd;
const struct landlock_ruleset_attr ruleset_attr = {
.scoped = scope,
};
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd)
{
TH_LOG("Failed to create a ruleset: %s", strerror(errno));
}
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}

View File

@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Landlock variants for three processes with various domains.
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
enum sandbox_type {
NO_SANDBOX,
SCOPE_SANDBOX,
/* Any other type of sandboxing domain */
OTHER_SANDBOX,
};
/* clang-format on */
FIXTURE_VARIANT(scoped_vs_unscoped)
{
const int domain_all;
const int domain_parent;
const int domain_children;
const int domain_child;
const int domain_grand_child;
};
/*
* .-----------------.
* | ####### | P3 -> P2 : allow
* | P1----# P2 # | P3 -> P1 : deny
* | # | # |
* | # P3 # |
* | ####### |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, deny_scoped) {
.domain_all = OTHER_SANDBOX,
.domain_parent = NO_SANDBOX,
.domain_children = SCOPE_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###################
* # ####### # P3 -> P2 : allow
* # P1----# P2 # # P3 -> P1 : deny
* # # | # #
* # # P3 # #
* # ####### #
* ###################
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, all_scoped) {
.domain_all = SCOPE_SANDBOX,
.domain_parent = NO_SANDBOX,
.domain_children = SCOPE_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* .-----------------.
* | .-----. | P3 -> P2 : allow
* | P1----| P2 | | P3 -> P1 : allow
* | | | |
* | | P3 | |
* | '-----' |
* '-----------------'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_other_domain) {
.domain_all = OTHER_SANDBOX,
.domain_parent = NO_SANDBOX,
.domain_children = OTHER_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* .----. ###### P3 -> P2 : allow
* | P1 |----# P2 # P3 -> P1 : allow
* '----' ######
* |
* P3
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_one_domain) {
.domain_all = NO_SANDBOX,
.domain_parent = OTHER_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = SCOPE_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###### .-----. P3 -> P2 : allow
* # P1 #----| P2 | P3 -> P1 : allow
* ###### '-----'
* |
* P3
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_grand_parent_scoped) {
.domain_all = NO_SANDBOX,
.domain_parent = SCOPE_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = OTHER_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###### ###### P3 -> P2 : allow
* # P1 #----# P2 # P3 -> P1 : allow
* ###### ######
* |
* .----.
* | P3 |
* '----'
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_parents_domain) {
.domain_all = NO_SANDBOX,
.domain_parent = SCOPE_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = SCOPE_SANDBOX,
.domain_grand_child = NO_SANDBOX,
/* clang-format on */
};
/*
* ###### P3 -> P2 : deny
* # P1 #----P2 P3 -> P1 : deny
* ###### |
* |
* ######
* # P3 #
* ######
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(scoped_vs_unscoped, deny_with_self_and_grandparent_domain) {
.domain_all = NO_SANDBOX,
.domain_parent = SCOPE_SANDBOX,
.domain_children = NO_SANDBOX,
.domain_child = NO_SANDBOX,
.domain_grand_child = SCOPE_SANDBOX,
/* clang-format on */
};

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Landlock tests - Common scope restriction
*
* Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com>
*/
#define _GNU_SOURCE
#include <errno.h>
#include <linux/landlock.h>
#include <sys/prctl.h>
#include "common.h"
#define ACCESS_LAST LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
TEST(ruleset_with_unknown_scope)
{
__u64 scoped_mask;
for (scoped_mask = 1ULL << 63; scoped_mask != ACCESS_LAST;
scoped_mask >>= 1) {
struct landlock_ruleset_attr ruleset_attr = {
.scoped = scoped_mask,
};
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
sizeof(ruleset_attr), 0));
ASSERT_EQ(EINVAL, errno);
}
}
TEST_HARNESS_MAIN