| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Landlock - Ptrace and scope hooks
- *
- * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
- * Copyright © 2019-2020 ANSSI
- * Copyright © 2024-2025 Microsoft Corporation
- */
- #include <asm/current.h>
- #include <linux/cleanup.h>
- #include <linux/cred.h>
- #include <linux/errno.h>
- #include <linux/kernel.h>
- #include <linux/lsm_audit.h>
- #include <linux/lsm_hooks.h>
- #include <linux/rcupdate.h>
- #include <linux/sched.h>
- #include <linux/sched/signal.h>
- #include <net/af_unix.h>
- #include <net/sock.h>
- #include "audit.h"
- #include "common.h"
- #include "cred.h"
- #include "domain.h"
- #include "fs.h"
- #include "ruleset.h"
- #include "setup.h"
- #include "task.h"
- /**
- * domain_scope_le - Checks domain ordering for scoped ptrace
- *
- * @parent: Parent domain.
- * @child: Potential child of @parent.
- *
- * Checks if the @parent domain is less or equal to (i.e. an ancestor, which
- * means a subset of) the @child domain.
- */
- static bool domain_scope_le(const struct landlock_ruleset *const parent,
- const struct landlock_ruleset *const child)
- {
- const struct landlock_hierarchy *walker;
- /* Quick return for non-landlocked tasks. */
- if (!parent)
- return true;
- if (!child)
- return false;
- for (walker = child->hierarchy; walker; walker = walker->parent) {
- if (walker == parent->hierarchy)
- /* @parent is in the scoped hierarchy of @child. */
- return true;
- }
- /* There is no relationship between @parent and @child. */
- return false;
- }
- static int domain_ptrace(const struct landlock_ruleset *const parent,
- const struct landlock_ruleset *const child)
- {
- if (domain_scope_le(parent, child))
- return 0;
- return -EPERM;
- }
- /**
- * hook_ptrace_access_check - Determines whether the current process may access
- * another
- *
- * @child: Process to be accessed.
- * @mode: Mode of attachment.
- *
- * If the current task has Landlock rules, then the child must have at least
- * the same rules. Else denied.
- *
- * Determines whether a process may access another, returning 0 if permission
- * granted, -errno if denied.
- */
- static int hook_ptrace_access_check(struct task_struct *const child,
- const unsigned int mode)
- {
- const struct landlock_cred_security *parent_subject;
- int err;
- /* Quick return for non-landlocked tasks. */
- parent_subject = landlock_cred(current_cred());
- if (!parent_subject)
- return 0;
- scoped_guard(rcu)
- {
- const struct landlock_ruleset *const child_dom =
- landlock_get_task_domain(child);
- err = domain_ptrace(parent_subject->domain, child_dom);
- }
- if (!err)
- return 0;
- /*
- * For the ptrace_access_check case, we log the current/parent domain
- * and the child task.
- */
- if (!(mode & PTRACE_MODE_NOAUDIT))
- landlock_log_denial(parent_subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_PTRACE,
- .audit = {
- .type = LSM_AUDIT_DATA_TASK,
- .u.tsk = child,
- },
- .layer_plus_one = parent_subject->domain->num_layers,
- });
- return err;
- }
- /**
- * hook_ptrace_traceme - Determines whether another process may trace the
- * current one
- *
- * @parent: Task proposed to be the tracer.
- *
- * If the parent has Landlock rules, then the current task must have the same
- * or more rules. Else denied.
- *
- * Determines whether the nominated task is permitted to trace the current
- * process, returning 0 if permission is granted, -errno if denied.
- */
- static int hook_ptrace_traceme(struct task_struct *const parent)
- {
- const struct landlock_cred_security *parent_subject;
- const struct landlock_ruleset *child_dom;
- int err;
- child_dom = landlock_get_current_domain();
- guard(rcu)();
- parent_subject = landlock_cred(__task_cred(parent));
- err = domain_ptrace(parent_subject->domain, child_dom);
- if (!err)
- return 0;
- /*
- * For the ptrace_traceme case, we log the domain which is the cause of
- * the denial, which means the parent domain instead of the current
- * domain. This may look unusual because the ptrace_traceme action is a
- * request to be traced, but the semantic is consistent with
- * hook_ptrace_access_check().
- */
- landlock_log_denial(parent_subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_PTRACE,
- .audit = {
- .type = LSM_AUDIT_DATA_TASK,
- .u.tsk = current,
- },
- .layer_plus_one = parent_subject->domain->num_layers,
- });
- return err;
- }
- /**
- * domain_is_scoped - Check if an interaction from a client/sender to a
- * server/receiver should be restricted based on scope controls.
- *
- * @client: IPC sender domain.
- * @server: IPC receiver domain.
- * @scope: The scope restriction criteria.
- *
- * Returns: True if @server is in a different domain from @client, and @client
- * is scoped to access @server (i.e. access should be denied).
- */
- 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;
- const 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 const struct access_masks unix_scope = {
- .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
- };
- static int hook_unix_stream_connect(struct sock *const sock,
- struct sock *const other,
- struct sock *const newsk)
- {
- size_t handle_layer;
- const struct landlock_cred_security *const subject =
- landlock_get_applicable_subject(current_cred(), unix_scope,
- &handle_layer);
- /* Quick return for non-landlocked tasks. */
- if (!subject)
- return 0;
- if (!is_abstract_socket(other))
- return 0;
- if (!sock_is_scoped(other, subject->domain))
- return 0;
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
- .audit = {
- .type = LSM_AUDIT_DATA_NET,
- .u.net = &(struct lsm_network_audit) {
- .sk = other,
- },
- },
- .layer_plus_one = handle_layer + 1,
- });
- return -EPERM;
- }
- static int hook_unix_may_send(struct socket *const sock,
- struct socket *const other)
- {
- size_t handle_layer;
- const struct landlock_cred_security *const subject =
- landlock_get_applicable_subject(current_cred(), unix_scope,
- &handle_layer);
- if (!subject)
- 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))
- return 0;
- if (!sock_is_scoped(other->sk, subject->domain))
- return 0;
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
- .audit = {
- .type = LSM_AUDIT_DATA_NET,
- .u.net = &(struct lsm_network_audit) {
- .sk = other->sk,
- },
- },
- .layer_plus_one = handle_layer + 1,
- });
- return -EPERM;
- }
- static const struct access_masks signal_scope = {
- .scope = LANDLOCK_SCOPE_SIGNAL,
- };
- static int hook_task_kill(struct task_struct *const p,
- struct kernel_siginfo *const info, const int sig,
- const struct cred *cred)
- {
- bool is_scoped;
- size_t handle_layer;
- const struct landlock_cred_security *subject;
- if (!cred) {
- /*
- * Always allow sending signals between threads of the same process.
- * This is required for process credential changes by the Native POSIX
- * Threads Library and implemented by the set*id(2) wrappers and
- * libcap(3) with tgkill(2). See nptl(7) and libpsx(3).
- *
- * This exception is similar to the __ptrace_may_access() one.
- */
- if (same_thread_group(p, current))
- return 0;
- /* Not dealing with USB IO. */
- cred = current_cred();
- }
- subject = landlock_get_applicable_subject(cred, signal_scope,
- &handle_layer);
- /* Quick return for non-landlocked tasks. */
- if (!subject)
- return 0;
- scoped_guard(rcu)
- {
- is_scoped = domain_is_scoped(subject->domain,
- landlock_get_task_domain(p),
- signal_scope.scope);
- }
- if (!is_scoped)
- return 0;
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
- .audit = {
- .type = LSM_AUDIT_DATA_TASK,
- .u.tsk = p,
- },
- .layer_plus_one = handle_layer + 1,
- });
- return -EPERM;
- }
- static int hook_file_send_sigiotask(struct task_struct *tsk,
- struct fown_struct *fown, int signum)
- {
- const struct landlock_cred_security *subject;
- bool is_scoped = false;
- /* Lock already held by send_sigio() and send_sigurg(). */
- lockdep_assert_held(&fown->lock);
- subject = &landlock_file(fown->file)->fown_subject;
- /*
- * Quick return for unowned socket.
- *
- * subject->domain has already been filtered when saved by
- * hook_file_set_fowner(), so there is no need to call
- * landlock_get_applicable_subject() here.
- */
- if (!subject->domain)
- return 0;
- scoped_guard(rcu)
- {
- is_scoped = domain_is_scoped(subject->domain,
- landlock_get_task_domain(tsk),
- signal_scope.scope);
- }
- if (!is_scoped)
- return 0;
- landlock_log_denial(subject, &(struct landlock_request) {
- .type = LANDLOCK_REQUEST_SCOPE_SIGNAL,
- .audit = {
- .type = LSM_AUDIT_DATA_TASK,
- .u.tsk = tsk,
- },
- #ifdef CONFIG_AUDIT
- .layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
- #endif /* CONFIG_AUDIT */
- });
- return -EPERM;
- }
- 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),
- LSM_HOOK_INIT(task_kill, hook_task_kill),
- LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask),
- };
- __init void landlock_add_task_hooks(void)
- {
- security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
- &landlock_lsmid);
- }
|