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 :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 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 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 = .handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_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 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: case 4:
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */ /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; 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. 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, 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. 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 Truncating files
---------------- ----------------
@ -404,7 +437,7 @@ Access rights
------------- -------------
.. kernel-doc:: include/uapi/linux/landlock.h .. kernel-doc:: include/uapi/linux/landlock.h
:identifiers: fs_access net_access :identifiers: fs_access net_access scope
Creating a new ruleset 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 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. :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:
Kernel support Kernel support

View File

@ -106,6 +106,7 @@ struct sk_psock {
struct mutex work_mutex; struct mutex work_mutex;
struct sk_psock_work_state work_state; struct sk_psock_work_state work_state;
struct work_struct work; struct work_struct work;
struct sock *sk_pair;
struct rcu_work rwork; 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); spin_lock_nested(&unix_sk(sk)->lock, subclass);
} }
#define unix_peer(sk) (unix_sk(sk)->peer)
#define peer_wait peer_wq.wait #define peer_wait peer_wq.wait
long unix_inq_len(struct sock *sk); long unix_inq_len(struct sock *sk);

View File

@ -12,31 +12,44 @@
#include <linux/types.h> #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 * Argument of sys_landlock_create_ruleset().
* future versions. *
* 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 { struct landlock_ruleset_attr {
/** /**
* @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_) * @handled_access_fs: Bitmask of handled filesystem actions
* that is handled by this ruleset and should then be forbidden if no * (cf. `Filesystem flags`_).
* 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.
*/ */
__u64 handled_access_fs; __u64 handled_access_fs;
/** /**
* @handled_access_net: Bitmask of actions (cf. `Network flags`_) * @handled_access_net: Bitmask of handled network actions (cf. `Network
* that is handled by this ruleset and should then be forbidden if no * flags`_).
* rule explicitly allow them.
*/ */
__u64 handled_access_net; __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 { 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`_). * (cf. `Network flags`_).
*/ */
__u64 allowed_access; __u64 allowed_access;
/** /**
* @port: Network port in host endianness. * @port: Network port in host endianness.
* *
* It should be noted that port 0 passed to :manpage:`bind(2)` will * It should be noted that port 0 passed to :manpage:`bind(2)` will bind
* bind to an available port from a specific port range. This can be * to an available port from the ephemeral port range. This can be
* configured thanks to the ``/proc/sys/net/ipv4/ip_local_port_range`` * configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
* sysctl (also used for IPv6). A Landlock rule with port 0 and the * (also used for IPv6).
* ``LANDLOCK_ACCESS_NET_BIND_TCP`` right means that requesting to bind *
* on port 0 is allowed and it will automatically translate to binding * A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP``
* on the related port range. * 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; __u64 port;
}; };
@ -131,10 +145,10 @@ struct landlock_net_port_attr {
* The following access rights apply only to files: * The following access rights apply only to files:
* *
* - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file. * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. When
* you might additionally need the %LANDLOCK_ACCESS_FS_TRUNCATE right in order * opening files for writing, you will often additionally need the
* to overwrite files with :manpage:`open(2)` using ``O_TRUNC`` or * %LANDLOCK_ACCESS_FS_TRUNCATE right. In many cases, these system calls
* :manpage:`creat(2)`. * 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_READ_FILE: Open a file with read access.
* - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`, * - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`,
* :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with * :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 * These flags enable to restrict a sandboxed process to a set of network
* actions. This is supported since the Landlock ABI version 4. * 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_BIND_TCP: Bind a TCP socket to a local port.
* - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to * - %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_BIND_TCP (1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
/* clang-format on */ /* 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 */ #endif /* _UAPI_LINUX_LANDLOCK_H */

View File

@ -833,6 +833,8 @@ static void sk_psock_destroy(struct work_struct *work)
if (psock->sk_redir) if (psock->sk_redir)
sock_put(psock->sk_redir); sock_put(psock->sk_redir);
if (psock->sk_pair)
sock_put(psock->sk_pair);
sock_put(psock->sk); sock_put(psock->sk);
kfree(psock); 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 */ #endif /* CONFIG_SECURITY_NETWORK */
#define unix_peer(sk) (unix_sk(sk)->peer)
static inline int unix_our_peer(struct sock *sk, struct sock *osk) static inline int unix_our_peer(struct sock *sk, struct sock *osk)
{ {
return unix_peer(osk) == sk; 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) int unix_stream_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)
{ {
struct sock *sk_pair;
if (restore) { if (restore) {
sk->sk_write_space = psock->saved_write_space; sk->sk_write_space = psock->saved_write_space;
sock_replace_proto(sk, psock->sk_proto); sock_replace_proto(sk, psock->sk_proto);
return 0; 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); unix_stream_bpf_check_needs_rebuild(psock->sk_proto);
sock_replace_proto(sk, &unix_stream_bpf_prot); sock_replace_proto(sk, &unix_stream_bpf_prot);
return 0; return 0;

View File

@ -14,6 +14,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <linux/landlock.h> #include <linux/landlock.h>
#include <linux/prctl.h> #include <linux/prctl.h>
#include <linux/socket.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -22,6 +23,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
#include <stdbool.h>
#ifndef landlock_create_ruleset #ifndef landlock_create_ruleset
static inline int 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_FS_RW_NAME "LL_FS_RW"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND" #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
#define ENV_SCOPED_NAME "LL_SCOPED"
#define ENV_DELIMITER ":" #define ENV_DELIMITER ":"
static int str2num(const char *numstr, __u64 *num_dst) static int str2num(const char *numstr, __u64 *num_dst)
@ -212,6 +215,48 @@ out_free_name:
return ret; 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 */ /* clang-format off */
#define ACCESS_FS_ROUGHLY_READ ( \ #define ACCESS_FS_ROUGHLY_READ ( \
@ -236,7 +281,7 @@ out_free_name:
/* clang-format on */ /* 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) 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_fs = access_fs_rw,
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP, LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
}; };
if (argc < 2) { if (argc < 2) {
fprintf(stderr, fprintf(stderr,
"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s " "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s "
"<cmd> [args]...\n\n", "<cmd> [args]...\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, 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, fprintf(stderr,
"Execute a command in a restricted environment.\n\n"); "Execute a command in a restricted environment.\n\n");
fprintf(stderr, fprintf(stderr,
@ -279,15 +325,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
fprintf(stderr, fprintf(stderr,
"* %s: list of ports allowed to connect (client).\n", "* %s: list of ports allowed to connect (client).\n",
ENV_TCP_CONNECT_NAME); ENV_TCP_CONNECT_NAME);
fprintf(stderr, "* %s: list of scoped IPCs.\n",
ENV_SCOPED_NAME);
fprintf(stderr, fprintf(stderr,
"\nexample:\n" "\nexample:\n"
"%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" " "%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
"%s=\"9418\" " "%s=\"9418\" "
"%s=\"80:443\" " "%s=\"80:443\" "
"%s=\"a\" "
"%s bash -i\n\n", "%s bash -i\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, 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, fprintf(stderr,
"This sandboxer can use Landlock features " "This sandboxer can use Landlock features "
"up to ABI version %d.\n", "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 */ /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; 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, fprintf(stderr,
"Hint: You should update the running kernel " "Hint: You should update the running kernel "
"to leverage Landlock features " "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; ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
} }
if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
return 1;
ruleset_fd = ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) { if (ruleset_fd < 0) {

View File

@ -21,13 +21,14 @@
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV #define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) #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_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) #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 */ /* clang-format on */
#endif /* _SECURITY_LANDLOCK_LIMITS_H */ #endif /* _SECURITY_LANDLOCK_LIMITS_H */

View File

@ -52,12 +52,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
struct landlock_ruleset * struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t fs_access_mask, 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; struct landlock_ruleset *new_ruleset;
/* Informs about useless 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); return ERR_PTR(-ENOMSG);
new_ruleset = create_ruleset(1); new_ruleset = create_ruleset(1);
if (IS_ERR(new_ruleset)) 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); landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
if (net_access_mask) if (net_access_mask)
landlock_add_net_access_mask(new_ruleset, net_access_mask, 0); 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; return new_ruleset;
} }
@ -169,13 +172,9 @@ static void build_check_ruleset(void)
.num_rules = ~0, .num_rules = ~0,
.num_layers = ~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_rules < LANDLOCK_MAX_NUM_RULES);
BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); 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); static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
/* Makes sure all network access rights can be stored. */ /* Makes sure all network access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET); 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. */ /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
/* Ruleset access masks. */ /* Ruleset access masks. */
typedef u32 access_masks_t; struct access_masks {
/* Makes sure all ruleset access rights can be stored. */ access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
static_assert(BITS_PER_TYPE(access_masks_t) >= access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET); access_mask_t scope : LANDLOCK_NUM_SCOPE;
};
typedef u16 layer_mask_t; typedef u16 layer_mask_t;
/* Makes sure all layers can be checked. */ /* Makes sure all layers can be checked. */
@ -226,14 +229,15 @@ struct landlock_ruleset {
* layers are set once and never changed for the * layers are set once and never changed for the
* lifetime of the ruleset. * lifetime of the ruleset.
*/ */
access_masks_t access_masks[]; struct access_masks access_masks[];
}; };
}; };
}; };
struct landlock_ruleset * struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t access_mask_fs, 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(struct landlock_ruleset *const ruleset);
void landlock_put_ruleset_deferred(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(). */ /* Should already be checked in sys_landlock_create_ruleset(). */
WARN_ON_ONCE(fs_access_mask != fs_mask); WARN_ON_ONCE(fs_access_mask != fs_mask);
ruleset->access_masks[layer_level] |= ruleset->access_masks[layer_level].fs |= fs_mask;
(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
} }
static inline void 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(). */ /* Should already be checked in sys_landlock_create_ruleset(). */
WARN_ON_ONCE(net_access_mask != net_mask); WARN_ON_ONCE(net_access_mask != net_mask);
ruleset->access_masks[layer_level] |= ruleset->access_masks[layer_level].net |= net_mask;
(net_mask << LANDLOCK_SHIFT_ACCESS_NET); }
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 static inline access_mask_t
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset, landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level) const u16 layer_level)
{ {
return (ruleset->access_masks[layer_level] >> return ruleset->access_masks[layer_level].fs;
LANDLOCK_SHIFT_ACCESS_FS) &
LANDLOCK_MASK_ACCESS_FS;
} }
static inline access_mask_t 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, landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level) const u16 layer_level)
{ {
return (ruleset->access_masks[layer_level] >> return ruleset->access_masks[layer_level].net;
LANDLOCK_SHIFT_ACCESS_NET) & }
LANDLOCK_MASK_ACCESS_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, 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_fs);
ruleset_size += sizeof(ruleset_attr.handled_access_net); 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) != 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.allowed_access);
path_beneath_size += sizeof(path_beneath_attr.parent_fd); path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@ -149,7 +150,7 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write, .write = fop_dummy_write,
}; };
#define LANDLOCK_ABI_VERSION 5 #define LANDLOCK_ABI_VERSION 6
/** /**
* sys_landlock_create_ruleset - Create a new ruleset * sys_landlock_create_ruleset - Create a new ruleset
@ -170,8 +171,9 @@ static const struct file_operations ruleset_fops = {
* Possible returned errors are: * Possible returned errors are:
* *
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
* - %EINVAL: unknown @flags, or unknown access, or too small @size; * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
* - %E2BIG or %EFAULT: @attr or @size inconsistencies; * - %E2BIG: @attr or @size inconsistencies;
* - %EFAULT: @attr or @size inconsistencies;
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
*/ */
SYSCALL_DEFINE3(landlock_create_ruleset, SYSCALL_DEFINE3(landlock_create_ruleset,
@ -213,9 +215,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
LANDLOCK_MASK_ACCESS_NET) LANDLOCK_MASK_ACCESS_NET)
return -EINVAL; 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. */ /* Checks arguments and transforms to kernel struct. */
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, 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)) if (IS_ERR(ruleset))
return PTR_ERR(ruleset); return PTR_ERR(ruleset);
@ -378,8 +385,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* with the new rule. * with the new rule.
* @rule_type: Identify the structure type pointed to by @rule_attr: * @rule_type: Identify the structure type pointed to by @rule_attr:
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT. * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
* @rule_attr: Pointer to a rule (only of type &struct * @rule_attr: Pointer to a rule (matching the @rule_type).
* landlock_path_beneath_attr for now).
* @flags: Must be 0. * @flags: Must be 0.
* *
* This system call enables to define a new rule and add it to an existing * 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; * - %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 * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
* supported by the running kernel; * 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_path_beneath_attr.allowed_access or
* &landlock_net_port_attr.allowed_access is not a subset of the * &landlock_net_port_attr.allowed_access is not a subset of the ruleset
* ruleset handled accesses), or &landlock_net_port_attr.port is * handled accesses)
* greater than 65535; * - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); * - %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 * - %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; * member of @rule_attr is not a file descriptor as expected;
* - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of * - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
* @rule_attr is not the expected file descriptor type; * @rule_attr is not the expected file descriptor type;
* - %EPERM: @ruleset_fd has no write access to the underlying ruleset; * - %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, SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
const enum landlock_rule_type, rule_type, const enum landlock_rule_type, rule_type,

View File

@ -13,6 +13,8 @@
#include <linux/lsm_hooks.h> #include <linux/lsm_hooks.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <net/af_unix.h>
#include <net/sock.h>
#include "common.h" #include "common.h"
#include "cred.h" #include "cred.h"
@ -108,9 +110,144 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
return task_ptrace(parent, current); 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 = { static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), 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) __init void landlock_add_task_hooks(void)

View File

@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = { const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, .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)); LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,

View File

@ -7,6 +7,7 @@
* Copyright © 2021 Microsoft Corporation * Copyright © 2021 Microsoft Corporation
*/ */
#include <arpa/inet.h>
#include <errno.h> #include <errno.h>
#include <linux/landlock.h> #include <linux/landlock.h>
#include <linux/securebits.h> #include <linux/securebits.h>
@ -14,11 +15,14 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "../kselftest_harness.h" #include "../kselftest_harness.h"
#define TMP_DIR "tmp"
#ifndef __maybe_unused #ifndef __maybe_unused
#define __maybe_unused __attribute__((__unused__)) #define __maybe_unused __attribute__((__unused__))
#endif #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)); 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) #define RENAME_EXCHANGE (1 << 1)
#endif #endif
#define TMP_DIR "tmp"
#define BINARY_PATH "./true" #define BINARY_PATH "./true"
/* Paths (sibling number and depth) */ /* Paths (sibling number and depth) */

View File

@ -36,30 +36,6 @@ enum sandbox_type {
TCP_SANDBOX, 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, static int set_service(struct service_fixture *const srv,
const struct protocol_variant prot, const struct protocol_variant prot,
const unsigned short index) const unsigned short index)
@ -92,12 +68,7 @@ static int set_service(struct service_fixture *const srv,
return 0; return 0;
case AF_UNIX: case AF_UNIX:
srv->unix_addr.sun_family = prot.domain; set_unix_address(srv, index);
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';
return 0; return 0;
} }
return 1; 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