509 lines · 13.8 KB · C
RawDownload
// 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;	u64 tracee_domain_id = 0;	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_SECURITY_LANDLOCK_LOG		if (child_dom)			tracee_domain_id = child_dom->hierarchy->id;#endif /* CONFIG_SECURITY_LANDLOCK_LOG */	} 	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,			.other_domain_id = tracee_domain_id,		}); 	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;	u64 tracee_domain_id = 0;	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; #ifdef CONFIG_SECURITY_LANDLOCK_LOG	/* The tracee is the current task; its domain is stable here. */	if (child_dom)		tracee_domain_id = child_dom->hierarchy->id;#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ 	/*	 * 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,		.other_domain_id = tracee_domain_id,	});	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,			   u64 *const peer_domain_id){	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;#ifdef CONFIG_SECURITY_LANDLOCK_LOG	*peer_domain_id = dom_other ? dom_other->hierarchy->id : 0;#endif /* CONFIG_SECURITY_LANDLOCK_LOG */	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;	u64 peer_domain_id = 0;	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, &peer_domain_id))		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,		.other_domain_id = peer_domain_id,	});	return -EPERM;} static int hook_unix_may_send(struct socket *const sock,			      struct socket *const other){	size_t handle_layer;	u64 peer_domain_id = 0;	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, &peer_domain_id))		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,		.other_domain_id = peer_domain_id,	});	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;	u64 target_domain_id = 0;	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_SECURITY_LANDLOCK_LOG		if (other)			target_domain_id = other->hierarchy->id;#endif /* CONFIG_SECURITY_LANDLOCK_LOG */	} 	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,		.other_domain_id = target_domain_id,	});	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;	u64 target_domain_id = 0; 	/* 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_SECURITY_LANDLOCK_LOG		if (other)			target_domain_id = other->hierarchy->id;#endif /* CONFIG_SECURITY_LANDLOCK_LOG */	} 	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 */		.other_domain_id = target_domain_id,	});	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), 

Lines 1–500 of 509.