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
|
||||
:Date: April 2024
|
||||
:Date: September 2024
|
||||
|
||||
The goal of Landlock is to enable to restrict ambient rights (e.g. global
|
||||
filesystem or network access) for a set of processes. Because Landlock
|
||||
|
@ -81,6 +81,8 @@ to be explicit about the denied-by-default access rights.
|
|||
.handled_access_net =
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
.scoped =
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
};
|
||||
|
||||
Because we may not know on which kernel version an application will be
|
||||
|
@ -119,6 +121,10 @@ version, and only use the available subset of access rights:
|
|||
case 4:
|
||||
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
|
||||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
__attribute__((fallthrough));
|
||||
case 5:
|
||||
/* Removes LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET for ABI < 6 */
|
||||
ruleset_attr.scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
|
||||
}
|
||||
|
||||
This enables to create an inclusive ruleset that will contain our rules.
|
||||
|
@ -306,6 +312,33 @@ To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target
|
|||
process, a sandboxed process should have a subset of the target process rules,
|
||||
which means the tracee must be in a sub-domain of the tracer.
|
||||
|
||||
IPC scoping
|
||||
-----------
|
||||
|
||||
Similar to the implicit `Ptrace restrictions`_, we may want to further restrict
|
||||
interactions between sandboxes. Each Landlock domain can be explicitly scoped
|
||||
for a set of actions by specifying it on a ruleset. For example, if a
|
||||
sandboxed process should not be able to :manpage:`connect(2)` to a
|
||||
non-sandboxed process through abstract :manpage:`unix(7)` sockets, we can
|
||||
specify such restriction with ``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET``.
|
||||
|
||||
A sandboxed process can connect to a non-sandboxed process when its domain is
|
||||
not scoped. If a process's domain is scoped, it can only connect to sockets
|
||||
created by processes in the same scope.
|
||||
|
||||
A connected datagram socket behaves like a stream socket when its domain is
|
||||
scoped, meaning if the domain is scoped after the socket is connected , it can
|
||||
still :manpage:`send(2)` data just like a stream socket. However, in the same
|
||||
scenario, a non-connected datagram socket cannot send data (with
|
||||
:manpage:`sendto(2)`) outside its scope.
|
||||
|
||||
A process with a scoped domain can inherit a socket created by a non-scoped
|
||||
process. The process cannot connect to this socket since it has a scoped
|
||||
domain.
|
||||
|
||||
IPC scoping does not support exceptions, so if a domain is scoped, no rules can
|
||||
be added to allow access to resources or processes outside of the scope.
|
||||
|
||||
Truncating files
|
||||
----------------
|
||||
|
||||
|
@ -404,7 +437,7 @@ Access rights
|
|||
-------------
|
||||
|
||||
.. kernel-doc:: include/uapi/linux/landlock.h
|
||||
:identifiers: fs_access net_access
|
||||
:identifiers: fs_access net_access scope
|
||||
|
||||
Creating a new ruleset
|
||||
----------------------
|
||||
|
@ -541,6 +574,13 @@ earlier ABI.
|
|||
Starting with the Landlock ABI version 5, it is possible to restrict the use of
|
||||
:manpage:`ioctl(2)` using the new ``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right.
|
||||
|
||||
Abstract UNIX socket scoping (ABI < 6)
|
||||
--------------------------------------
|
||||
|
||||
Starting with the Landlock ABI version 6, it is possible to restrict
|
||||
connections to an abstract :manpage:`unix(7)` socket by setting
|
||||
``LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET`` to the ``scoped`` ruleset attribute.
|
||||
|
||||
.. _kernel_support:
|
||||
|
||||
Kernel support
|
||||
|
|
|
@ -106,6 +106,7 @@ struct sk_psock {
|
|||
struct mutex work_mutex;
|
||||
struct sk_psock_work_state work_state;
|
||||
struct work_struct work;
|
||||
struct sock *sk_pair;
|
||||
struct rcu_work rwork;
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ static inline void unix_state_lock_nested(struct sock *sk,
|
|||
spin_lock_nested(&unix_sk(sk)->lock, subclass);
|
||||
}
|
||||
|
||||
#define unix_peer(sk) (unix_sk(sk)->peer)
|
||||
#define peer_wait peer_wq.wait
|
||||
|
||||
long unix_inq_len(struct sock *sk);
|
||||
|
|
|
@ -12,31 +12,44 @@
|
|||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* struct landlock_ruleset_attr - Ruleset definition
|
||||
* struct landlock_ruleset_attr - Ruleset definition.
|
||||
*
|
||||
* Argument of sys_landlock_create_ruleset(). This structure can grow in
|
||||
* future versions.
|
||||
* Argument of sys_landlock_create_ruleset().
|
||||
*
|
||||
* This structure defines a set of *handled access rights*, a set of actions on
|
||||
* different object types, which should be denied by default when the ruleset is
|
||||
* enacted. Vice versa, access rights that are not specifically listed here are
|
||||
* not going to be denied by this ruleset when it is enacted.
|
||||
*
|
||||
* For historical reasons, the %LANDLOCK_ACCESS_FS_REFER right is always denied
|
||||
* by default, even when its bit is not set in @handled_access_fs. In order to
|
||||
* add new rules with this access right, the bit must still be set explicitly
|
||||
* (cf. `Filesystem flags`_).
|
||||
*
|
||||
* The explicit listing of *handled access rights* is required for backwards
|
||||
* compatibility reasons. In most use cases, processes that use Landlock will
|
||||
* *handle* a wide range or all access rights that they know about at build time
|
||||
* (and that they have tested with a kernel that supported them all).
|
||||
*
|
||||
* This structure can grow in future Landlock versions.
|
||||
*/
|
||||
struct landlock_ruleset_attr {
|
||||
/**
|
||||
* @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_)
|
||||
* that is handled by this ruleset and should then be forbidden if no
|
||||
* rule explicitly allow them: it is a deny-by-default list that should
|
||||
* contain as much Landlock access rights as possible. Indeed, all
|
||||
* Landlock filesystem access rights that are not part of
|
||||
* handled_access_fs are allowed. This is needed for backward
|
||||
* compatibility reasons. One exception is the
|
||||
* %LANDLOCK_ACCESS_FS_REFER access right, which is always implicitly
|
||||
* handled, but must still be explicitly handled to add new rules with
|
||||
* this access right.
|
||||
* @handled_access_fs: Bitmask of handled filesystem actions
|
||||
* (cf. `Filesystem flags`_).
|
||||
*/
|
||||
__u64 handled_access_fs;
|
||||
/**
|
||||
* @handled_access_net: Bitmask of actions (cf. `Network flags`_)
|
||||
* that is handled by this ruleset and should then be forbidden if no
|
||||
* rule explicitly allow them.
|
||||
* @handled_access_net: Bitmask of handled network actions (cf. `Network
|
||||
* flags`_).
|
||||
*/
|
||||
__u64 handled_access_net;
|
||||
/**
|
||||
* @scoped: Bitmask of scopes (cf. `Scope flags`_)
|
||||
* restricting a Landlock domain from accessing outside
|
||||
* resources (e.g. IPCs).
|
||||
*/
|
||||
__u64 scoped;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -97,20 +110,21 @@ struct landlock_path_beneath_attr {
|
|||
*/
|
||||
struct landlock_net_port_attr {
|
||||
/**
|
||||
* @allowed_access: Bitmask of allowed access network for a port
|
||||
* @allowed_access: Bitmask of allowed network actions for a port
|
||||
* (cf. `Network flags`_).
|
||||
*/
|
||||
__u64 allowed_access;
|
||||
/**
|
||||
* @port: Network port in host endianness.
|
||||
*
|
||||
* It should be noted that port 0 passed to :manpage:`bind(2)` will
|
||||
* bind to an available port from a specific port range. This can be
|
||||
* configured thanks to the ``/proc/sys/net/ipv4/ip_local_port_range``
|
||||
* sysctl (also used for IPv6). A Landlock rule with port 0 and the
|
||||
* ``LANDLOCK_ACCESS_NET_BIND_TCP`` right means that requesting to bind
|
||||
* on port 0 is allowed and it will automatically translate to binding
|
||||
* on the related port range.
|
||||
* It should be noted that port 0 passed to :manpage:`bind(2)` will bind
|
||||
* to an available port from the ephemeral port range. This can be
|
||||
* configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
|
||||
* (also used for IPv6).
|
||||
*
|
||||
* A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP``
|
||||
* right means that requesting to bind on port 0 is allowed and it will
|
||||
* automatically translate to binding on the related port range.
|
||||
*/
|
||||
__u64 port;
|
||||
};
|
||||
|
@ -131,10 +145,10 @@ struct landlock_net_port_attr {
|
|||
* The following access rights apply only to files:
|
||||
*
|
||||
* - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
|
||||
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that
|
||||
* you might additionally need the %LANDLOCK_ACCESS_FS_TRUNCATE right in order
|
||||
* to overwrite files with :manpage:`open(2)` using ``O_TRUNC`` or
|
||||
* :manpage:`creat(2)`.
|
||||
* - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. When
|
||||
* opening files for writing, you will often additionally need the
|
||||
* %LANDLOCK_ACCESS_FS_TRUNCATE right. In many cases, these system calls
|
||||
* truncate existing files when overwriting them (e.g., :manpage:`creat(2)`).
|
||||
* - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.
|
||||
* - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`,
|
||||
* :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with
|
||||
|
@ -256,7 +270,7 @@ struct landlock_net_port_attr {
|
|||
* These flags enable to restrict a sandboxed process to a set of network
|
||||
* actions. This is supported since the Landlock ABI version 4.
|
||||
*
|
||||
* TCP sockets with allowed actions:
|
||||
* The following access rights apply to TCP port numbers:
|
||||
*
|
||||
* - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
|
||||
* - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
|
||||
|
@ -266,4 +280,25 @@ struct landlock_net_port_attr {
|
|||
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
|
||||
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
* DOC: scope
|
||||
*
|
||||
* Scope flags
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* These flags enable to isolate a sandboxed process from a set of IPC actions.
|
||||
* Setting a flag for a ruleset will isolate the Landlock domain to forbid
|
||||
* connections to resources outside the domain.
|
||||
*
|
||||
* Scopes:
|
||||
*
|
||||
* - %LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process from
|
||||
* connecting to an abstract UNIX socket created by a process outside the
|
||||
* related Landlock domain (e.g. a parent domain or a non-sandboxed process).
|
||||
*/
|
||||
/* clang-format off */
|
||||
#define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0)
|
||||
/* clang-format on*/
|
||||
|
||||
#endif /* _UAPI_LINUX_LANDLOCK_H */
|
||||
|
|
|
@ -833,6 +833,8 @@ static void sk_psock_destroy(struct work_struct *work)
|
|||
|
||||
if (psock->sk_redir)
|
||||
sock_put(psock->sk_redir);
|
||||
if (psock->sk_pair)
|
||||
sock_put(psock->sk_pair);
|
||||
sock_put(psock->sk);
|
||||
kfree(psock);
|
||||
}
|
||||
|
|
|
@ -205,8 +205,6 @@ static inline bool unix_secdata_eq(struct scm_cookie *scm, struct sk_buff *skb)
|
|||
}
|
||||
#endif /* CONFIG_SECURITY_NETWORK */
|
||||
|
||||
#define unix_peer(sk) (unix_sk(sk)->peer)
|
||||
|
||||
static inline int unix_our_peer(struct sock *sk, struct sock *osk)
|
||||
{
|
||||
return unix_peer(osk) == sk;
|
||||
|
|
|
@ -160,12 +160,17 @@ int unix_dgram_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool re
|
|||
|
||||
int unix_stream_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)
|
||||
{
|
||||
struct sock *sk_pair;
|
||||
|
||||
if (restore) {
|
||||
sk->sk_write_space = psock->saved_write_space;
|
||||
sock_replace_proto(sk, psock->sk_proto);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sk_pair = unix_peer(sk);
|
||||
sock_hold(sk_pair);
|
||||
psock->sk_pair = sk_pair;
|
||||
unix_stream_bpf_check_needs_rebuild(psock->sk_proto);
|
||||
sock_replace_proto(sk, &unix_stream_bpf_prot);
|
||||
return 0;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <linux/landlock.h>
|
||||
#include <linux/prctl.h>
|
||||
#include <linux/socket.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -22,6 +23,7 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef landlock_create_ruleset
|
||||
static inline int
|
||||
|
@ -55,6 +57,7 @@ static inline int landlock_restrict_self(const int ruleset_fd,
|
|||
#define ENV_FS_RW_NAME "LL_FS_RW"
|
||||
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
|
||||
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
|
||||
#define ENV_SCOPED_NAME "LL_SCOPED"
|
||||
#define ENV_DELIMITER ":"
|
||||
|
||||
static int str2num(const char *numstr, __u64 *num_dst)
|
||||
|
@ -212,6 +215,48 @@ out_free_name:
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Returns true on error, false otherwise. */
|
||||
static bool check_ruleset_scope(const char *const env_var,
|
||||
struct landlock_ruleset_attr *ruleset_attr)
|
||||
{
|
||||
char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
|
||||
bool error = false;
|
||||
bool abstract_scoping = false;
|
||||
|
||||
/* Scoping is not supported by Landlock ABI */
|
||||
if (!(ruleset_attr->scoped & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET))
|
||||
goto out_unset;
|
||||
|
||||
env_type_scope = getenv(env_var);
|
||||
/* Scoping is not supported by the user */
|
||||
if (!env_type_scope || strcmp("", env_type_scope) == 0)
|
||||
goto out_unset;
|
||||
|
||||
env_type_scope = strdup(env_type_scope);
|
||||
env_type_scope_next = env_type_scope;
|
||||
while ((ipc_scoping_name =
|
||||
strsep(&env_type_scope_next, ENV_DELIMITER))) {
|
||||
if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) {
|
||||
abstract_scoping = true;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",
|
||||
ipc_scoping_name);
|
||||
error = true;
|
||||
goto out_free_name;
|
||||
}
|
||||
}
|
||||
|
||||
out_free_name:
|
||||
free(env_type_scope);
|
||||
|
||||
out_unset:
|
||||
if (!abstract_scoping)
|
||||
ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
|
||||
|
||||
unsetenv(env_var);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
#define ACCESS_FS_ROUGHLY_READ ( \
|
||||
|
@ -236,7 +281,7 @@ out_free_name:
|
|||
|
||||
/* clang-format on */
|
||||
|
||||
#define LANDLOCK_ABI_LAST 5
|
||||
#define LANDLOCK_ABI_LAST 6
|
||||
|
||||
int main(const int argc, char *const argv[], char *const *const envp)
|
||||
{
|
||||
|
@ -251,14 +296,15 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
.handled_access_fs = access_fs_rw,
|
||||
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||
LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
};
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr,
|
||||
"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
|
||||
"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s "
|
||||
"<cmd> [args]...\n\n",
|
||||
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
|
||||
ENV_TCP_CONNECT_NAME, argv[0]);
|
||||
ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
|
||||
fprintf(stderr,
|
||||
"Execute a command in a restricted environment.\n\n");
|
||||
fprintf(stderr,
|
||||
|
@ -279,15 +325,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
fprintf(stderr,
|
||||
"* %s: list of ports allowed to connect (client).\n",
|
||||
ENV_TCP_CONNECT_NAME);
|
||||
fprintf(stderr, "* %s: list of scoped IPCs.\n",
|
||||
ENV_SCOPED_NAME);
|
||||
fprintf(stderr,
|
||||
"\nexample:\n"
|
||||
"%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
|
||||
"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
|
||||
"%s=\"9418\" "
|
||||
"%s=\"80:443\" "
|
||||
"%s=\"a\" "
|
||||
"%s bash -i\n\n",
|
||||
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
|
||||
ENV_TCP_CONNECT_NAME, argv[0]);
|
||||
ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]);
|
||||
fprintf(stderr,
|
||||
"This sandboxer can use Landlock features "
|
||||
"up to ABI version %d.\n",
|
||||
|
@ -355,6 +404,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
|
||||
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
|
||||
__attribute__((fallthrough));
|
||||
case 5:
|
||||
/* Removes LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET for ABI < 6 */
|
||||
ruleset_attr.scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
|
||||
fprintf(stderr,
|
||||
"Hint: You should update the running kernel "
|
||||
"to leverage Landlock features "
|
||||
|
@ -386,6 +439,9 @@ int main(const int argc, char *const argv[], char *const *const envp)
|
|||
~LANDLOCK_ACCESS_NET_CONNECT_TCP;
|
||||
}
|
||||
|
||||
if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
|
||||
return 1;
|
||||
|
||||
ruleset_fd =
|
||||
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0) {
|
||||
|
|
|
@ -21,13 +21,14 @@
|
|||
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV
|
||||
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
|
||||
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
|
||||
#define LANDLOCK_SHIFT_ACCESS_FS 0
|
||||
|
||||
#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
|
||||
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
|
||||
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
|
||||
#define LANDLOCK_SHIFT_ACCESS_NET LANDLOCK_NUM_ACCESS_FS
|
||||
|
||||
#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
||||
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
|
||||
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
|
||||
/* clang-format on */
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_LIMITS_H */
|
||||
|
|
|
@ -52,12 +52,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
|
|||
|
||||
struct landlock_ruleset *
|
||||
landlock_create_ruleset(const access_mask_t fs_access_mask,
|
||||
const access_mask_t net_access_mask)
|
||||
const access_mask_t net_access_mask,
|
||||
const access_mask_t scope_mask)
|
||||
{
|
||||
struct landlock_ruleset *new_ruleset;
|
||||
|
||||
/* Informs about useless ruleset. */
|
||||
if (!fs_access_mask && !net_access_mask)
|
||||
if (!fs_access_mask && !net_access_mask && !scope_mask)
|
||||
return ERR_PTR(-ENOMSG);
|
||||
new_ruleset = create_ruleset(1);
|
||||
if (IS_ERR(new_ruleset))
|
||||
|
@ -66,6 +67,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
|
|||
landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
|
||||
if (net_access_mask)
|
||||
landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
|
||||
if (scope_mask)
|
||||
landlock_add_scope_mask(new_ruleset, scope_mask, 0);
|
||||
return new_ruleset;
|
||||
}
|
||||
|
||||
|
@ -169,13 +172,9 @@ static void build_check_ruleset(void)
|
|||
.num_rules = ~0,
|
||||
.num_layers = ~0,
|
||||
};
|
||||
typeof(ruleset.access_masks[0]) access_masks = ~0;
|
||||
|
||||
BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
|
||||
BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
|
||||
BUILD_BUG_ON(access_masks <
|
||||
((LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) |
|
||||
(LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,14 +35,17 @@ typedef u16 access_mask_t;
|
|||
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
|
||||
/* Makes sure all network access rights can be stored. */
|
||||
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
|
||||
/* Makes sure all scoped rights can be stored. */
|
||||
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
|
||||
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
|
||||
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
|
||||
|
||||
/* Ruleset access masks. */
|
||||
typedef u32 access_masks_t;
|
||||
/* Makes sure all ruleset access rights can be stored. */
|
||||
static_assert(BITS_PER_TYPE(access_masks_t) >=
|
||||
LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
|
||||
struct access_masks {
|
||||
access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
|
||||
access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
|
||||
access_mask_t scope : LANDLOCK_NUM_SCOPE;
|
||||
};
|
||||
|
||||
typedef u16 layer_mask_t;
|
||||
/* Makes sure all layers can be checked. */
|
||||
|
@ -226,14 +229,15 @@ struct landlock_ruleset {
|
|||
* layers are set once and never changed for the
|
||||
* lifetime of the ruleset.
|
||||
*/
|
||||
access_masks_t access_masks[];
|
||||
struct access_masks access_masks[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
struct landlock_ruleset *
|
||||
landlock_create_ruleset(const access_mask_t access_mask_fs,
|
||||
const access_mask_t access_mask_net);
|
||||
const access_mask_t access_mask_net,
|
||||
const access_mask_t scope_mask);
|
||||
|
||||
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
|
||||
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
|
||||
|
@ -265,8 +269,7 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
|
|||
|
||||
/* Should already be checked in sys_landlock_create_ruleset(). */
|
||||
WARN_ON_ONCE(fs_access_mask != fs_mask);
|
||||
ruleset->access_masks[layer_level] |=
|
||||
(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
|
||||
ruleset->access_masks[layer_level].fs |= fs_mask;
|
||||
}
|
||||
|
||||
static inline void
|
||||
|
@ -278,17 +281,25 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
|
|||
|
||||
/* Should already be checked in sys_landlock_create_ruleset(). */
|
||||
WARN_ON_ONCE(net_access_mask != net_mask);
|
||||
ruleset->access_masks[layer_level] |=
|
||||
(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
|
||||
ruleset->access_masks[layer_level].net |= net_mask;
|
||||
}
|
||||
|
||||
static inline void
|
||||
landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
|
||||
const access_mask_t scope_mask, const u16 layer_level)
|
||||
{
|
||||
access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
|
||||
|
||||
/* Should already be checked in sys_landlock_create_ruleset(). */
|
||||
WARN_ON_ONCE(scope_mask != mask);
|
||||
ruleset->access_masks[layer_level].scope |= mask;
|
||||
}
|
||||
|
||||
static inline access_mask_t
|
||||
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
|
||||
const u16 layer_level)
|
||||
{
|
||||
return (ruleset->access_masks[layer_level] >>
|
||||
LANDLOCK_SHIFT_ACCESS_FS) &
|
||||
LANDLOCK_MASK_ACCESS_FS;
|
||||
return ruleset->access_masks[layer_level].fs;
|
||||
}
|
||||
|
||||
static inline access_mask_t
|
||||
|
@ -304,9 +315,14 @@ static inline access_mask_t
|
|||
landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
|
||||
const u16 layer_level)
|
||||
{
|
||||
return (ruleset->access_masks[layer_level] >>
|
||||
LANDLOCK_SHIFT_ACCESS_NET) &
|
||||
LANDLOCK_MASK_ACCESS_NET;
|
||||
return ruleset->access_masks[layer_level].net;
|
||||
}
|
||||
|
||||
static inline access_mask_t
|
||||
landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
|
||||
const u16 layer_level)
|
||||
{
|
||||
return ruleset->access_masks[layer_level].scope;
|
||||
}
|
||||
|
||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||
|
|
|
@ -97,8 +97,9 @@ static void build_check_abi(void)
|
|||
*/
|
||||
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
|
||||
ruleset_size += sizeof(ruleset_attr.handled_access_net);
|
||||
ruleset_size += sizeof(ruleset_attr.scoped);
|
||||
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
|
||||
BUILD_BUG_ON(sizeof(ruleset_attr) != 16);
|
||||
BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
|
||||
|
||||
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
|
||||
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
|
||||
|
@ -149,7 +150,7 @@ static const struct file_operations ruleset_fops = {
|
|||
.write = fop_dummy_write,
|
||||
};
|
||||
|
||||
#define LANDLOCK_ABI_VERSION 5
|
||||
#define LANDLOCK_ABI_VERSION 6
|
||||
|
||||
/**
|
||||
* sys_landlock_create_ruleset - Create a new ruleset
|
||||
|
@ -170,8 +171,9 @@ static const struct file_operations ruleset_fops = {
|
|||
* Possible returned errors are:
|
||||
*
|
||||
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
|
||||
* - %EINVAL: unknown @flags, or unknown access, or too small @size;
|
||||
* - %E2BIG or %EFAULT: @attr or @size inconsistencies;
|
||||
* - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
|
||||
* - %E2BIG: @attr or @size inconsistencies;
|
||||
* - %EFAULT: @attr or @size inconsistencies;
|
||||
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
|
||||
*/
|
||||
SYSCALL_DEFINE3(landlock_create_ruleset,
|
||||
|
@ -213,9 +215,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
|
|||
LANDLOCK_MASK_ACCESS_NET)
|
||||
return -EINVAL;
|
||||
|
||||
/* Checks IPC scoping content (and 32-bits cast). */
|
||||
if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
|
||||
return -EINVAL;
|
||||
|
||||
/* Checks arguments and transforms to kernel struct. */
|
||||
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
|
||||
ruleset_attr.handled_access_net);
|
||||
ruleset_attr.handled_access_net,
|
||||
ruleset_attr.scoped);
|
||||
if (IS_ERR(ruleset))
|
||||
return PTR_ERR(ruleset);
|
||||
|
||||
|
@ -378,8 +385,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
|||
* with the new rule.
|
||||
* @rule_type: Identify the structure type pointed to by @rule_attr:
|
||||
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
|
||||
* @rule_attr: Pointer to a rule (only of type &struct
|
||||
* landlock_path_beneath_attr for now).
|
||||
* @rule_attr: Pointer to a rule (matching the @rule_type).
|
||||
* @flags: Must be 0.
|
||||
*
|
||||
* This system call enables to define a new rule and add it to an existing
|
||||
|
@ -390,18 +396,20 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
|
|||
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
|
||||
* - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
|
||||
* supported by the running kernel;
|
||||
* - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
|
||||
* - %EINVAL: @flags is not 0;
|
||||
* - %EINVAL: The rule accesses are inconsistent (i.e.
|
||||
* &landlock_path_beneath_attr.allowed_access or
|
||||
* &landlock_net_port_attr.allowed_access is not a subset of the
|
||||
* ruleset handled accesses), or &landlock_net_port_attr.port is
|
||||
* greater than 65535;
|
||||
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
|
||||
* &landlock_net_port_attr.allowed_access is not a subset of the ruleset
|
||||
* handled accesses)
|
||||
* - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
|
||||
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
|
||||
* 0);
|
||||
* - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
|
||||
* member of @rule_attr is not a file descriptor as expected;
|
||||
* - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
|
||||
* @rule_attr is not the expected file descriptor type;
|
||||
* - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
|
||||
* - %EFAULT: @rule_attr inconsistency.
|
||||
* - %EFAULT: @rule_attr was not a valid address.
|
||||
*/
|
||||
SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
|
||||
const enum landlock_rule_type, rule_type,
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <linux/lsm_hooks.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/sched.h>
|
||||
#include <net/af_unix.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "cred.h"
|
||||
|
@ -108,9 +110,144 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
|
|||
return task_ptrace(parent, current);
|
||||
}
|
||||
|
||||
/**
|
||||
* domain_is_scoped - Checks if the client domain is scoped in the same
|
||||
* domain as the server.
|
||||
*
|
||||
* @client: IPC sender domain.
|
||||
* @server: IPC receiver domain.
|
||||
* @scope: The scope restriction criteria.
|
||||
*
|
||||
* Returns: True if the @client domain is scoped to access the @server,
|
||||
* unless the @server is also scoped in the same domain as @client.
|
||||
*/
|
||||
static bool domain_is_scoped(const struct landlock_ruleset *const client,
|
||||
const struct landlock_ruleset *const server,
|
||||
access_mask_t scope)
|
||||
{
|
||||
int client_layer, server_layer;
|
||||
struct landlock_hierarchy *client_walker, *server_walker;
|
||||
|
||||
/* Quick return if client has no domain */
|
||||
if (WARN_ON_ONCE(!client))
|
||||
return false;
|
||||
|
||||
client_layer = client->num_layers - 1;
|
||||
client_walker = client->hierarchy;
|
||||
/*
|
||||
* client_layer must be a signed integer with greater capacity
|
||||
* than client->num_layers to ensure the following loop stops.
|
||||
*/
|
||||
BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers));
|
||||
|
||||
server_layer = server ? (server->num_layers - 1) : -1;
|
||||
server_walker = server ? server->hierarchy : NULL;
|
||||
|
||||
/*
|
||||
* Walks client's parent domains down to the same hierarchy level
|
||||
* as the server's domain, and checks that none of these client's
|
||||
* parent domains are scoped.
|
||||
*/
|
||||
for (; client_layer > server_layer; client_layer--) {
|
||||
if (landlock_get_scope_mask(client, client_layer) & scope)
|
||||
return true;
|
||||
|
||||
client_walker = client_walker->parent;
|
||||
}
|
||||
/*
|
||||
* Walks server's parent domains down to the same hierarchy level as
|
||||
* the client's domain.
|
||||
*/
|
||||
for (; server_layer > client_layer; server_layer--)
|
||||
server_walker = server_walker->parent;
|
||||
|
||||
for (; client_layer >= 0; client_layer--) {
|
||||
if (landlock_get_scope_mask(client, client_layer) & scope) {
|
||||
/*
|
||||
* Client and server are at the same level in the
|
||||
* hierarchy. If the client is scoped, the request is
|
||||
* only allowed if this domain is also a server's
|
||||
* ancestor.
|
||||
*/
|
||||
return server_walker != client_walker;
|
||||
}
|
||||
client_walker = client_walker->parent;
|
||||
server_walker = server_walker->parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sock_is_scoped(struct sock *const other,
|
||||
const struct landlock_ruleset *const domain)
|
||||
{
|
||||
const struct landlock_ruleset *dom_other;
|
||||
|
||||
/* The credentials will not change. */
|
||||
lockdep_assert_held(&unix_sk(other)->lock);
|
||||
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
|
||||
return domain_is_scoped(domain, dom_other,
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||||
}
|
||||
|
||||
static bool is_abstract_socket(struct sock *const sock)
|
||||
{
|
||||
struct unix_address *addr = unix_sk(sock)->addr;
|
||||
|
||||
if (!addr)
|
||||
return false;
|
||||
|
||||
if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
|
||||
addr->name->sun_path[0] == '\0')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int hook_unix_stream_connect(struct sock *const sock,
|
||||
struct sock *const other,
|
||||
struct sock *const newsk)
|
||||
{
|
||||
const struct landlock_ruleset *const dom =
|
||||
landlock_get_current_domain();
|
||||
|
||||
/* Quick return for non-landlocked tasks. */
|
||||
if (!dom)
|
||||
return 0;
|
||||
|
||||
if (is_abstract_socket(other) && sock_is_scoped(other, dom))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hook_unix_may_send(struct socket *const sock,
|
||||
struct socket *const other)
|
||||
{
|
||||
const struct landlock_ruleset *const dom =
|
||||
landlock_get_current_domain();
|
||||
|
||||
if (!dom)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Checks if this datagram socket was already allowed to be connected
|
||||
* to other.
|
||||
*/
|
||||
if (unix_peer(sock->sk) == other->sk)
|
||||
return 0;
|
||||
|
||||
if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct security_hook_list landlock_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
|
||||
LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
|
||||
|
||||
LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect),
|
||||
LSM_HOOK_INIT(unix_may_send, hook_unix_may_send),
|
||||
};
|
||||
|
||||
__init void landlock_add_task_hooks(void)
|
||||
|
|
|
@ -76,7 +76,7 @@ TEST(abi_version)
|
|||
const struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
|
||||
};
|
||||
ASSERT_EQ(5, landlock_create_ruleset(NULL, 0,
|
||||
ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION));
|
||||
|
||||
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
* Copyright © 2021 Microsoft Corporation
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <linux/landlock.h>
|
||||
#include <linux/securebits.h>
|
||||
|
@ -14,11 +15,14 @@
|
|||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
#define TMP_DIR "tmp"
|
||||
|
||||
#ifndef __maybe_unused
|
||||
#define __maybe_unused __attribute__((__unused__))
|
||||
#endif
|
||||
|
@ -226,3 +230,38 @@ enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
|
|||
TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
struct protocol_variant {
|
||||
int domain;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct service_fixture {
|
||||
struct protocol_variant protocol;
|
||||
/* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
|
||||
unsigned short port;
|
||||
union {
|
||||
struct sockaddr_in ipv4_addr;
|
||||
struct sockaddr_in6 ipv6_addr;
|
||||
struct {
|
||||
struct sockaddr_un unix_addr;
|
||||
socklen_t unix_addr_len;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
static pid_t __maybe_unused sys_gettid(void)
|
||||
{
|
||||
return syscall(__NR_gettid);
|
||||
}
|
||||
|
||||
static void __maybe_unused set_unix_address(struct service_fixture *const srv,
|
||||
const unsigned short index)
|
||||
{
|
||||
srv->unix_addr.sun_family = AF_UNIX;
|
||||
sprintf(srv->unix_addr.sun_path,
|
||||
"_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(),
|
||||
index);
|
||||
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
|
||||
srv->unix_addr.sun_path[0] = '\0';
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ int open_tree(int dfd, const char *filename, unsigned int flags)
|
|||
#define RENAME_EXCHANGE (1 << 1)
|
||||
#endif
|
||||
|
||||
#define TMP_DIR "tmp"
|
||||
#define BINARY_PATH "./true"
|
||||
|
||||
/* Paths (sibling number and depth) */
|
||||
|
|
|
@ -36,30 +36,6 @@ enum sandbox_type {
|
|||
TCP_SANDBOX,
|
||||
};
|
||||
|
||||
struct protocol_variant {
|
||||
int domain;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct service_fixture {
|
||||
struct protocol_variant protocol;
|
||||
/* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */
|
||||
unsigned short port;
|
||||
union {
|
||||
struct sockaddr_in ipv4_addr;
|
||||
struct sockaddr_in6 ipv6_addr;
|
||||
struct {
|
||||
struct sockaddr_un unix_addr;
|
||||
socklen_t unix_addr_len;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
static pid_t sys_gettid(void)
|
||||
{
|
||||
return syscall(__NR_gettid);
|
||||
}
|
||||
|
||||
static int set_service(struct service_fixture *const srv,
|
||||
const struct protocol_variant prot,
|
||||
const unsigned short index)
|
||||
|
@ -92,12 +68,7 @@ static int set_service(struct service_fixture *const srv,
|
|||
return 0;
|
||||
|
||||
case AF_UNIX:
|
||||
srv->unix_addr.sun_family = prot.domain;
|
||||
sprintf(srv->unix_addr.sun_path,
|
||||
"_selftests-landlock-net-tid%d-index%d", sys_gettid(),
|
||||
index);
|
||||
srv->unix_addr_len = SUN_LEN(&srv->unix_addr);
|
||||
srv->unix_addr.sun_path[0] = '\0';
|
||||
set_unix_address(srv, index);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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