[PATCH 2/6] landlock: Implement LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET

Justin Suess utilityemal77 at gmail.com
Sun Dec 28 16:37:50 UTC 2025


On 12/28/25 07:45, Tingmao Wang wrote:
> Extend the existing abstract UNIX socket scoping to pathname sockets as
> well.  Basically all of the logic is reused between the two types, just
> that pathname sockets scoping are controlled by another bit, and has its
> own audit request type (since the current one is named
> "abstract_unix_socket").
>
>
> Closes: https://github.com/landlock-lsm/linux/issues/51
> Signed-off-by: Tingmao Wang <m at maowtm.org>
> ---
>
> There is an argument that there should only really be one audit request
> type for both sockets, since the only difference is whether path= is
> followed by a normal path, or by a hex string starting with 00.  But I'm
> not sure if we can change this at this point, so I have created a new
> request type.
>
>  security/landlock/audit.c |  4 +++
>  security/landlock/audit.h |  1 +
>  security/landlock/task.c  | 74 ++++++++++++++++++++++++++++++---------
>  3 files changed, 62 insertions(+), 17 deletions(-)
>
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> index e899995f1fd5..0626cc553ab0 100644
> --- a/security/landlock/audit.c
> +++ b/security/landlock/audit.c
> @@ -75,6 +75,10 @@ get_blocker(const enum landlock_request_type type,
>  		WARN_ON_ONCE(access_bit != -1);
>  		return "scope.abstract_unix_socket";
>  
> +	case LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET:
> +		WARN_ON_ONCE(access_bit != -1);
> +		return "scope.pathname_unix_socket";
> +
>  	case LANDLOCK_REQUEST_SCOPE_SIGNAL:
>  		WARN_ON_ONCE(access_bit != -1);
>  		return "scope.signal";
> diff --git a/security/landlock/audit.h b/security/landlock/audit.h
> index 92428b7fc4d8..1c9ce8588102 100644
> --- a/security/landlock/audit.h
> +++ b/security/landlock/audit.h
> @@ -21,6 +21,7 @@ enum landlock_request_type {
>  	LANDLOCK_REQUEST_NET_ACCESS,
>  	LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
>  	LANDLOCK_REQUEST_SCOPE_SIGNAL,
> +	LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET,
>  };
>  
>  /*
> diff --git a/security/landlock/task.c b/security/landlock/task.c
> index 6dfcc1860d6e..9fbb0ada440b 100644
> --- a/security/landlock/task.c
> +++ b/security/landlock/task.c
> @@ -233,57 +233,84 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client,
>  	return false;
>  }
>  
> +/**
> + * sock_is_scoped - Check if socket connect or send should be restricted
> + *    based on scope controls.
> + *
> + * @other: The server socket.
> + * @domain: The client domain.
> + * @scope: The relevant scope bit to check (i.e. pathname or abstract).
> + *
> + * Returns: True if connect should be restricted, false otherwise.
> + */
>  static bool sock_is_scoped(struct sock *const other,
> -			   const struct landlock_ruleset *const domain)
> +			   const struct landlock_ruleset *const domain,
> +			   access_mask_t scope)
>  {
>  	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);
> +	return domain_is_scoped(domain, dom_other, scope);
>  }
>  
> -static bool is_abstract_socket(struct sock *const sock)
> +static bool sock_addr_is_abstract(const struct unix_address *const addr)

Nit: From the name sock_addr_is_abstract, it's unclear without reading
the parameter that this function only works with unix sockets, when
socket is an overloaded term that can refer to other kinds of sockets
(e.g tcp/udp/raw).

Maybe is_unix_sock_addr_abstract? or unix_sock_addr_is_abstract?

>
>  {
> -	struct unix_address *addr = unix_sk(sock)->addr;
> -
> -	if (!addr)
> -		return false;
> -
> -	if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
> +	if (addr && addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 &&
>  	    addr->name->sun_path[0] == '\0')
>  		return true;
>  
>  	return false;
>  }
>  
> +/* Allow us to quickly test if the current domain scopes any form of socket */
>  static const struct access_masks unix_scope = {
> -	.scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
> +	.scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> +		 LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET,
>  };
>  
> +/*
> + * UNIX sockets can have three types of addresses: pathname (a filesystem path),
> + * unnamed (not bound to an address), and abstract (sun_path[0] is '\0').
> + * Unnamed sockets include those created with socketpair() and unbound sockets.
> + * We do not restrict unnamed sockets since they have no address to identify.
> + */
>  static int hook_unix_stream_connect(struct sock *const sock,
>  				    struct sock *const other,
>  				    struct sock *const newsk)
>  {
>  	size_t handle_layer;
> +	access_mask_t scope;
> +	enum landlock_request_type request_type;
>  	const struct landlock_cred_security *const subject =
>  		landlock_get_applicable_subject(current_cred(), unix_scope,
>  						&handle_layer);
> +	const struct unix_address *addr;
>  
>  	/* Quick return for non-landlocked tasks. */
>  	if (!subject)
>  		return 0;
>  
> -	if (!is_abstract_socket(other))
> +	addr = unix_sk(other)->addr;
> +	/* Unnamed sockets are not restricted. */
> +	if (!addr)
>  		return 0;
>  
> -	if (!sock_is_scoped(other, subject->domain))
> +	if (sock_addr_is_abstract(addr)) {
> +		scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
> +		request_type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET;
> +	} else {
> +		/* Pathname socket. */
> +		scope = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> +		request_type = LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET;
> +	}
> +
> +	if (!sock_is_scoped(other, subject->domain, scope))
>  		return 0;
>  
>  	landlock_log_denial(subject, &(struct landlock_request) {
> -		.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
> +		.type = request_type,
>  		.audit = {
>  			.type = LSM_AUDIT_DATA_NET,
>  			.u.net = &(struct lsm_network_audit) {
> @@ -299,9 +326,12 @@ static int hook_unix_may_send(struct socket *const sock,
>  			      struct socket *const other)
>  {
>  	size_t handle_layer;
> +	access_mask_t scope;
> +	enum landlock_request_type request_type;
>  	const struct landlock_cred_security *const subject =
>  		landlock_get_applicable_subject(current_cred(), unix_scope,
>  						&handle_layer);
> +	const struct unix_address *addr;
>  
>  	if (!subject)
>  		return 0;
> @@ -313,14 +343,24 @@ static int hook_unix_may_send(struct socket *const sock,
>  	if (unix_peer(sock->sk) == other->sk)
>  		return 0;
>  
> -	if (!is_abstract_socket(other->sk))
> +	addr = unix_sk(other->sk)->addr;
> +	/* Unnamed sockets are not restricted. */
> +	if (!addr)
>  		return 0;
>  
> -	if (!sock_is_scoped(other->sk, subject->domain))
> +	if (sock_addr_is_abstract(addr)) {
> +		scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
> +		request_type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET;
> +	} else {
> +		scope = LANDLOCK_SCOPE_PATHNAME_UNIX_SOCKET;
> +		request_type = LANDLOCK_REQUEST_SCOPE_PATHNAME_UNIX_SOCKET;
> +	}
> +
> +	if (!sock_is_scoped(other->sk, subject->domain, scope))
>  		return 0;
>  
>  	landlock_log_denial(subject, &(struct landlock_request) {
> -		.type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
> +		.type = request_type,
>  		.audit = {
>  			.type = LSM_AUDIT_DATA_NET,
>  			.u.net = &(struct lsm_network_audit) {



More information about the Linux-security-module-archive mailing list