Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541// 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 "common.h"#include "cred.h"#include "domain.h"#include "fs.h"#include "log.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. * * Return: True if @parent is an ancestor of or equal to @child, false * otherwise. */static bool domain_scope_le(const struct landlock_domain *const parent, const struct landlock_domain *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_domain *const parent, const struct landlock_domain *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. * * Return: 0 if permission is 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;#ifdef CONFIG_TRACEPOINTS u64 tracee_domain_id = 0;#endif /* CONFIG_TRACEPOINTS */ 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_domain *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom);#ifdef CONFIG_TRACEPOINTS if (child_dom) tracee_domain_id = child_dom->hierarchy->id;#endif /* CONFIG_TRACEPOINTS */ }
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,#ifdef CONFIG_TRACEPOINTS .trace_ptrace = &(struct landlock_ptrace_trace) { .tracee_domain_id = tracee_domain_id, .tracer = current, },#endif /* CONFIG_TRACEPOINTS */ });
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. * * Return: 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_domain *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,#ifdef CONFIG_TRACEPOINTS .trace_ptrace = &(struct landlock_ptrace_trace) { /* The current task's domain is stable here. */ .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0, .tracer = parent, },#endif /* CONFIG_TRACEPOINTS */ }); 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. * * Return: True if @server is in a different domain from @client and @client * is scoped to access @server (i.e. access should be denied), false otherwise. */static bool domain_is_scoped(const struct landlock_domain *const client, const struct landlock_domain *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 able to represent all numbers from * LANDLOCK_MAX_NUM_LAYERS - 1 to -1 for the loop below to terminate. * (It must be large enough, and it must be signed.) */ BUILD_BUG_ON(!is_signed_type(typeof(client_layer))); BUILD_BUG_ON(LANDLOCK_MAX_NUM_LAYERS - 1 > type_max(typeof(client_layer)));
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_domain *const domain){ const struct landlock_domain *dom_other;
/* The credentials will not change. */ lockdep_assert_held(&unix_sk(other)->lock);
/* * A live kernel socket (e.g. from sock_create_kern()) has no backing * file, hence no Landlock domain, so treat it as unscoped. The * sk_socket check only guards that dereference; sk_socket is NULL * solely for a dead peer, which the caller already excludes under the * held lock, so no separate SOCK_DEAD check is needed. */ if (unlikely(!other->sk_socket || !other->sk_socket->file)) return false;
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; return domain_is_scoped(domain, dom_other, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);}
#ifdef CONFIG_TRACEPOINTS
static u64 get_socket_domain_id(const struct sock *const other){ const struct landlock_domain *domain;
lockdep_assert_held(&unix_sk(other)->lock); domain = landlock_cred(other->sk_socket->file->f_cred)->domain; return domain ? domain->hierarchy->id : 0;}
#endif /* CONFIG_TRACEPOINTS */
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,#ifdef CONFIG_TRACEPOINTS .other_domain_id = get_socket_domain_id(other),#endif /* CONFIG_TRACEPOINTS */ }); 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,#ifdef CONFIG_TRACEPOINTS .other_domain_id = get_socket_domain_id(other->sk),#endif /* CONFIG_TRACEPOINTS */ }); 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;#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0;#endif /* CONFIG_TRACEPOINTS */ 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) { const struct landlock_domain *const other = landlock_get_task_domain(p);
is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope);#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id;#endif /* CONFIG_TRACEPOINTS */ }
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,#ifdef CONFIG_TRACEPOINTS .trace_signal = &(struct landlock_signal_trace) { .target_domain_id = target_domain_id, .signal = sig, },#endif /* CONFIG_TRACEPOINTS */ }); 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;#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0;#endif /* CONFIG_TRACEPOINTS */
/* 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;
/* * Always allow delivery to the file owner's own process, including a * thread-group leader reached through a process-group owner. This * mirrors hook_task_kill()'s same-process exemption and preserves the * guarantee of commit 18eb75f3af40 ("landlock: Always allow signals * between threads of the same process"), which the registration-time * check cannot honor for a process-group target. */ if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg) return 0;
scoped_guard(rcu) { const struct landlock_domain *const other = landlock_get_task_domain(tsk);
is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope);#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id;#endif /* CONFIG_TRACEPOINTS */ }
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_SECURITY_LANDLOCK_LOG .layer_plus_one = landlock_file(fown->file)->fown_layer + 1,#endif /* CONFIG_SECURITY_LANDLOCK_LOG */#ifdef CONFIG_TRACEPOINTS .trace_signal = &(struct landlock_signal_trace) { .target_domain_id = target_domain_id, .signal = signum ? signum : SIGIO, },#endif /* CONFIG_TRACEPOINTS */ }); 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);}