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:
commit
6c4a30f494
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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';
|
||||||
|
}
|
||||||
|
|
|
@ -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) */
|
||||||
|
|
|
@ -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
|
@ -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,
|
||||||
|
};
|
|
@ -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));
|
||||||
|
}
|
|
@ -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 */
|
||||||
|
};
|
|
@ -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
|
Loading…
Reference in New Issue