linux-security-module.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v8 0/4] Landlock: Add abstract unix socket connect
@ 2024-08-02  4:02 Tahera Fahimi
  2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
                   ` (3 more replies)
  0 siblings, 4 replies; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-02  4:02 UTC (permalink / raw)
  To: outreachy
  Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi

This patch series adds scoping mechanism for abstract unix sockets.
Closes: https://github.com/landlock-lsm/linux/issues/7

Problem
=======

Abstract unix sockets are used for local inter-process communications
independent of the filesystem. Currently, a sandboxed process can
connect to a socket outside of the sandboxed environment, since Landlock
has no restriction for connecting to an abstract socket address(see more
details in [1,2]). Access to such sockets for a sandboxed process should
be scoped the same way ptrace is limited.

[1] https://lore.kernel.org/all/20231023.ahphah4Wii4v@digikod.net/
[2] https://lore.kernel.org/all/20231102.MaeWaepav8nu@digikod.net/

Solution
========

To solve this issue, we extend the user space interface by adding a new
"scoped" field to Landlock ruleset attribute structure. This field can
contains different rights to restrict different functionalities. For
abstract unix sockets, we introduce
"LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" field to specify that a ruleset
will deny any connection from within the sandbox domain to its parent
(i.e. any parent sandbox or non-sandbox processes).

Example
=======

Starting a listening socket with socat(1):
	socat abstract-listen:mysocket -

Starting a sandboxed shell from $HOME with samples/landlock/sandboxer:
	LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash

If we try to connect to the listening socket, the connection would be
refused.
	socat - abstract-connect:mysocket --> fails


Notes of Implementation
=======================

* Using the "scoped" field provides enough compatibility and flexibility
  to extend the scoping mechanism for other IPCs(e.g. signals).

* To access the domain of a socket, we use its credentials of the file's FD
  which point to the credentials of the process that created the socket.
  (see more details in [3]). Cases where the process using the socket has
  a different domain than the process created it are covered in the 
  unix_sock_special_cases test.

[3]https://lore.kernel.org/all/20240611.Pi8Iph7ootae@digikod.net/


Previous Versions
=================
v6: https://lore.kernel.org/all/Zn32CYZiu7pY+rdI@tahera-OptiPlex-5000/
and https://lore.kernel.org/all/Zn32KKIJrY7Zi51K@tahera-OptiPlex-5000/
v5: https://lore.kernel.org/all/ZnSZnhGBiprI6FRk@tahera-OptiPlex-5000/
v4: https://lore.kernel.org/all/ZnNcE3ph2SWi1qmd@tahera-OptiPlex-5000/
v3: https://lore.kernel.org/all/ZmJJ7lZdQuQop7e5@tahera-OptiPlex-5000/
v2: https://lore.kernel.org/all/ZgX5TRTrSDPrJFfF@tahera-OptiPlex-5000/
v1: https://lore.kernel.org/all/ZgXN5fi6A1YQKiAQ@tahera-OptiPlex-5000/

Tahera Fahimi (4):
  Landlock: Add abstract unix socket connect restriction
  selftests/landlock: Abstract unix socket restriction tests
  sample/Landlock: Support abstract unix socket restriction
  Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI
    versioning

 Documentation/userspace-api/landlock.rst      |   33 +-
 include/uapi/linux/landlock.h                 |   30 +
 samples/landlock/sandboxer.c                  |   56 +-
 security/landlock/limits.h                    |    3 +
 security/landlock/ruleset.c                   |    7 +-
 security/landlock/ruleset.h                   |   23 +-
 security/landlock/syscalls.c                  |   14 +-
 security/landlock/task.c                      |  155 +++
 tools/testing/selftests/landlock/base_test.c  |    2 +-
 tools/testing/selftests/landlock/common.h     |   72 ++
 tools/testing/selftests/landlock/fs_test.c    |   34 -
 tools/testing/selftests/landlock/net_test.c   |   31 +-
 .../landlock/scoped_abstract_unix_test.c      | 1136 +++++++++++++++++
 13 files changed, 1518 insertions(+), 78 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c

-- 
2.34.1


^ permalink raw reply	[flat|nested] 23+ messages in thread

* [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-02  4:02 [PATCH v8 0/4] Landlock: Add abstract unix socket connect Tahera Fahimi
@ 2024-08-02  4:02 ` Tahera Fahimi
  2024-08-02 16:47   ` Mickaël Salaün
                     ` (2 more replies)
  2024-08-02  4:02 ` [PATCH v8 2/4] selftests/landlock: Abstract unix socket restriction tests Tahera Fahimi
                   ` (2 subsequent siblings)
  3 siblings, 3 replies; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-02  4:02 UTC (permalink / raw)
  To: outreachy
  Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi

This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
abstract Unix sockets from connecting to a process outside of
the same landlock domain. It implements two hooks, unix_stream_connect
and unix_may_send to enforce this restriction.

Closes: https://github.com/landlock-lsm/linux/issues/7
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>

---
v8:
- Code refactoring (improve code readability, renaming variable, etc.) based
  on reviews by Mickaël Salaün on version 7.
- Adding warn_on_once to check (impossible) inconsistencies.
- Adding inline comments.
- Adding check_unix_address_format to check if the scoping socket is an abstract
  unix sockets.
v7:
 - Using socket's file credentials for both connected(STREAM) and
   non-connected(DGRAM) sockets.
 - Adding "domain_sock_scope" instead of the domain scoping mechanism used in
   ptrace ensures that if a server's domain is accessible from the client's
   domain (where the client is more privileged than the server), the client
   can connect to the server in all edge cases.
 - Removing debug codes.
v6:
 - Removing curr_ruleset from landlock_hierarchy, and switching back to use
   the same domain scoping as ptrace.
 - code clean up.
v5:
 - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
 - Adding curr_ruleset to hierarachy_ruleset structure to have access from
   landlock_hierarchy to its respective landlock_ruleset.
 - Using curr_ruleset to check if a domain is scoped while walking in the
   hierarchy of domains.
 - Modifying inline comments.
V4:
 - Rebased on Günther's Patch:
   https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
   so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
 - Adding get_scope_accesses function to check all scoped access masks in a ruleset.
 - Using socket's file credentials instead of credentials stored in peer_cred
   for datagram sockets. (see discussion in [1])
 - Modifying inline comments.
V3:
 - Improving commit description.
 - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
   purpose, and adding related functions.
 - Changing structure of ruleset based on "scoped".
 - Removing rcu lock and using unix_sk lock instead.
 - Introducing scoping for datagram sockets in unix_may_send.
V2:
 - Removing wrapper functions

[1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
----
---
 include/uapi/linux/landlock.h |  30 +++++++
 security/landlock/limits.h    |   3 +
 security/landlock/ruleset.c   |   7 +-
 security/landlock/ruleset.h   |  23 ++++-
 security/landlock/syscalls.c  |  14 ++-
 security/landlock/task.c      | 155 ++++++++++++++++++++++++++++++++++
 6 files changed, 225 insertions(+), 7 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 68625e728f43..ab31e9f53e55 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -37,6 +37,12 @@ struct landlock_ruleset_attr {
 	 * rule explicitly allow them.
 	 */
 	__u64 handled_access_net;
+	/**
+	 * @scoped: Bitmask of scopes (cf. `Scope flags`_)
+	 * restricting a Landlock domain from accessing outside
+	 * resources(e.g. IPCs).
+	 */
+	__u64 scoped;
 };
 
 /*
@@ -266,4 +272,28 @@ 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
+ *
+ * scoped attribute handles a set of restrictions on kernel IPCs through
+ * the following flags.
+ *
+ * Scope flags
+ * ~~~~~~~~~~~
+ *
+ * These flags enable to restrict 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.
+ *
+ * IPCs with scoped actions:
+ *
+ * - %LANDLOCK_SCOPED_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_SCOPED_ABSTRACT_UNIX_SOCKET		(1ULL << 0)
+/* clang-format on*/
 #endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 4eb643077a2a..eb01d0fb2165 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -26,6 +26,9 @@
 #define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
 #define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
 
+#define LANDLOCK_LAST_SCOPE		LANDLOCK_SCOPED_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 */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 6ff232f58618..a93bdbf52fff 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -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;
 }
 
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0f1b5b4c8f6b..c749fa0b3ecd 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -35,6 +35,8 @@ 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));
 
@@ -42,6 +44,7 @@ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
 struct access_masks {
 	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
 	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
+	access_mask_t scoped : LANDLOCK_NUM_SCOPE;
 };
 
 typedef u16 layer_mask_t;
@@ -233,7 +236,8 @@ struct landlock_ruleset {
 
 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);
@@ -280,6 +284,16 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
 	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 scoped_mask = scope_mask & LANDLOCK_MASK_SCOPE;
+
+	WARN_ON_ONCE(scope_mask != scoped_mask);
+	ruleset->access_masks[layer_level].scoped |= scoped_mask;
+}
+
 static inline access_mask_t
 landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
 				const u16 layer_level)
@@ -303,6 +317,13 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
 	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].scoped;
+}
+
 bool landlock_unmask_layers(const struct landlock_rule *const rule,
 			    const access_mask_t access_request,
 			    layer_mask_t (*const layer_masks)[],
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 03b470f5a85a..f51b29521d6b 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -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,7 +171,7 @@ 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;
+ * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;
  * - %E2BIG or %EFAULT: @attr or @size inconsistencies;
  * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
  */
@@ -213,9 +214,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);
 
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 849f5123610b..7e8579ebae83 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -13,6 +13,8 @@
 #include <linux/lsm_hooks.h>
 #include <linux/rcupdate.h>
 #include <linux/sched.h>
+#include <net/sock.h>
+#include <net/af_unix.h>
 
 #include "common.h"
 #include "cred.h"
@@ -108,9 +110,162 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
 	return task_ptrace(parent, current);
 }
 
+static bool walk_and_check(const struct landlock_ruleset *const child,
+			   struct landlock_hierarchy **walker,
+			   size_t base_layer, size_t deep_layer,
+			   access_mask_t check_scoping)
+{
+	if (!child || base_layer < 0 || !(*walker))
+		return false;
+
+	for (deep_layer; base_layer < deep_layer; deep_layer--) {
+		if (check_scoping & landlock_get_scope_mask(child, deep_layer))
+			return false;
+		*walker = (*walker)->parent;
+		if (WARN_ON_ONCE(!*walker))
+			/* there is an inconsistency between num_layers
+			 * and landlock_hierarchy in the ruleset
+			 */
+			return false;
+	}
+	return true;
+}
+
+/**
+ * domain_IPC_scope - Checks if the client domain is scoped in the same
+ *		      domain as the server.
+ *
+ * @client: IPC sender domain.
+ * @server: IPC receiver domain.
+ *
+ * Check if the @client domain is scoped to access the @server; the @server
+ * must be scoped in the same domain.
+ */
+static bool domain_IPC_scope(const struct landlock_ruleset *const client,
+			     const struct landlock_ruleset *const server,
+			     access_mask_t ipc_type)
+{
+	size_t client_layer, server_layer = 0;
+	int base_layer;
+	struct landlock_hierarchy *client_walker, *server_walker;
+	bool is_scoped;
+
+	/* Quick return if client has no domain */
+	if (!client)
+		return true;
+
+	client_layer = client->num_layers - 1;
+	client_walker = client->hierarchy;
+	if (server) {
+		server_layer = server->num_layers - 1;
+		server_walker = server->hierarchy;
+	}
+	base_layer = (client_layer > server_layer) ? server_layer :
+						     client_layer;
+
+	/* For client domain, walk_and_check ensures the client domain is
+	 * not scoped until gets to base_layer.
+	 * For server_domain, it only ensures that the server domain exist.
+	 */
+	if (client_layer != server_layer) {
+		if (client_layer > server_layer)
+			is_scoped = walk_and_check(client, &client_walker,
+						   server_layer, client_layer,
+						   ipc_type);
+		else
+			is_scoped = walk_and_check(server, &server_walker,
+						   client_layer, server_layer,
+						   ipc_type & 0);
+		if (!is_scoped)
+			return false;
+	}
+	/* client and server are at the same level in hierarchy. If client is
+	 * scoped, the server must be scoped in the same domain
+	 */
+	for (base_layer; base_layer >= 0; base_layer--) {
+		if (landlock_get_scope_mask(client, base_layer) & ipc_type) {
+			/* This check must be here since access would be denied only if
+			 * the client is scoped and the server has no domain, so
+			 * if the client has a domain but is not scoped and the server
+			 * has no domain, access is guaranteed.
+			 */
+			if (!server)
+				return false;
+
+			if (server_walker == client_walker)
+				return true;
+
+			return false;
+		}
+		client_walker = client_walker->parent;
+		server_walker = server_walker->parent;
+		/* Warn if there is an incosistenncy between num_layers and
+		 * landlock_hierarchy in each of rulesets
+		 */
+		if (WARN_ON_ONCE(base_layer > 0 &&
+				 (!server_walker || !client_walker)))
+			return false;
+	}
+	return true;
+}
+
+static bool sock_is_scoped(struct sock *const other)
+{
+	const struct landlock_ruleset *dom_other;
+	const struct landlock_ruleset *const dom =
+		landlock_get_current_domain();
+
+	/* 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_IPC_scope(dom, dom_other,
+				LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET);
+}
+
+static bool check_unix_address_format(struct sock *const sock)
+{
+	struct unix_address *addr = unix_sk(sock)->addr;
+
+	if (!addr)
+		return true;
+
+	if (addr->len > sizeof(AF_UNIX)) {
+		/* handling unspec sockets */
+		if (!addr->name[0].sun_path)
+			return true;
+
+		if (addr->name[0].sun_path[0] == '\0')
+			if (!sock_is_scoped(sock))
+				return false;
+	}
+
+	return true;
+}
+
+static int hook_unix_stream_connect(struct sock *const sock,
+				    struct sock *const other,
+				    struct sock *const newsk)
+{
+	if (check_unix_address_format(other))
+		return 0;
+
+	return -EPERM;
+}
+
+static int hook_unix_may_send(struct socket *const sock,
+			      struct socket *const other)
+{
+	if (check_unix_address_format(other->sk))
+		return 0;
+
+	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),
 };
 
 __init void landlock_add_task_hooks(void)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH v8 2/4] selftests/landlock: Abstract unix socket restriction tests
  2024-08-02  4:02 [PATCH v8 0/4] Landlock: Add abstract unix socket connect Tahera Fahimi
  2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
@ 2024-08-02  4:02 ` Tahera Fahimi
  2024-08-07 15:08   ` Mickaël Salaün
  2024-08-02  4:02 ` [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
  2024-08-02  4:02 ` [PATCH v8 4/4] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
  3 siblings, 1 reply; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-02  4:02 UTC (permalink / raw)
  To: outreachy
  Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi

The patch introduces Landlock ABI version 6 and has four types of tests:
1) unix_socket: base tests of the abstract socket scoping mechanism for a
   landlocked process, same as the ptrace test.
2) optional_scoping: generates three processes with different domains and
   tests if a process with a non-scoped domain can connect to other
   processes.
3) unix_sock_special_cases: since the socket's creator credentials are used
   for scoping sockets, this test examines the cases where the socket's
   credentials are different from the process using it.
4) pathname_address_sockets: ensures that Unix sockets bound to a
   null-terminated filesystem can still connect to a socket outside of
   their scoped domain. This means that even if the domain is scoped with
   LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, the socket can connect to a socket
   outside the scoped domain.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
Changes in versions:
V8:
- Move tests to scoped_abstract_unix_test.c file.
- To avoid potential conflicts among Unix socket names in different tests,
  set_unix_address is added to common.h to set different sun_path for Unix sockets.
- protocol_variant and service_fixture structures are also moved to common.h
- Adding pathname_address_sockets to cover all types of address formats
  for unix sockets, and moving remove_path() to common.h to reuse in this test.
V7:
- Introducing landlock ABI version 6.
- Adding some edge test cases to optional_scoping test.
- Using `enum` for different domains in optional_scoping tests.
- Extend unix_sock_special_cases test cases for connected(SOCK_STREAM) sockets.
- Modifying inline comments.
V6:
- Introducing optional_scoping test which ensures a sandboxed process with a
  non-scoped domain can still connect to another abstract unix socket(either
  sandboxed or non-sandboxed).
- Introducing unix_sock_special_cases test which tests examines scenarios where
  the connecting sockets have different domain than the process using them.
V4:
- Introducing unix_socket to evaluate the basic scoping mechanism for abstract
  unix sockets.
---
 tools/testing/selftests/landlock/base_test.c  |    2 +-
 tools/testing/selftests/landlock/common.h     |   72 ++
 tools/testing/selftests/landlock/fs_test.c    |   34 -
 tools/testing/selftests/landlock/net_test.c   |   31 +-
 .../landlock/scoped_abstract_unix_test.c      | 1136 +++++++++++++++++
 5 files changed, 1210 insertions(+), 65 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 3c1e9f35b531..52b00472a487 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -75,7 +75,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,
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 7e2b431b9f90..a0c8126d94b4 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -16,8 +16,11 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
 
 #include "../kselftest_harness.h"
+#define TMP_DIR "tmp"
 
 #ifndef __maybe_unused
 #define __maybe_unused __attribute__((__unused__))
@@ -226,3 +229,72 @@ 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';
+}
+
+static int __maybe_unused remove_path(const char *const path)
+{
+	char *walker;
+	int i, ret, err = 0;
+
+	walker = strdup(path);
+	if (!walker) {
+		err = ENOMEM;
+		goto out;
+	}
+	if (unlink(path) && rmdir(path)) {
+		if (errno != ENOENT && errno != ENOTDIR)
+			err = errno;
+		goto out;
+	}
+	for (i = strlen(walker); i > 0; i--) {
+		if (walker[i] != '/')
+			continue;
+		walker[i] = '\0';
+		ret = rmdir(walker);
+		if (ret) {
+			if (errno != ENOTEMPTY && errno != EBUSY)
+				err = errno;
+			goto out;
+		}
+		if (strcmp(walker, TMP_DIR) == 0)
+			goto out;
+	}
+
+out:
+	free(walker);
+	return err;
+}
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 7d063c652be1..fc74cab6dfb8 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -221,40 +221,6 @@ static void create_file(struct __test_metadata *const _metadata,
 	}
 }
 
-static int remove_path(const char *const path)
-{
-	char *walker;
-	int i, ret, err = 0;
-
-	walker = strdup(path);
-	if (!walker) {
-		err = ENOMEM;
-		goto out;
-	}
-	if (unlink(path) && rmdir(path)) {
-		if (errno != ENOENT && errno != ENOTDIR)
-			err = errno;
-		goto out;
-	}
-	for (i = strlen(walker); i > 0; i--) {
-		if (walker[i] != '/')
-			continue;
-		walker[i] = '\0';
-		ret = rmdir(walker);
-		if (ret) {
-			if (errno != ENOTEMPTY && errno != EBUSY)
-				err = errno;
-			goto out;
-		}
-		if (strcmp(walker, TMP_DIR) == 0)
-			goto out;
-	}
-
-out:
-	free(walker);
-	return err;
-}
-
 struct mnt_opt {
 	const char *const source;
 	const char *const type;
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index f21cfbbc3638..4e0aeb53b225 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -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;
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
new file mode 100644
index 000000000000..ffa1f01dbbce
--- /dev/null
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -0,0 +1,1136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - Abstract Unix Socket
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include "common.h"
+
+static void create_fs_domain(struct __test_metadata *const _metadata)
+{
+	int ruleset_fd;
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+	};
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	EXPECT_LE(0, ruleset_fd)
+	{
+		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
+	}
+	EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
+	EXPECT_EQ(0, close(ruleset_fd));
+}
+
+static void create_unix_domain(struct __test_metadata *const _metadata)
+{
+	int ruleset_fd;
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.scoped = LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET,
+	};
+
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	EXPECT_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));
+}
+
+/* clang-format off */
+FIXTURE(unix_socket)
+{
+	struct service_fixture stream_address, dgram_address;
+	int server, client, dgram_server, dgram_client;
+};
+
+/* clang-format on */
+FIXTURE_VARIANT(unix_socket)
+{
+	bool domain_both;
+	bool domain_parent;
+	bool domain_child;
+	bool connect_to_parent;
+};
+
+FIXTURE_SETUP(unix_socket)
+{
+	memset(&self->stream_address, 0, sizeof(self->stream_address));
+	memset(&self->dgram_address, 0, sizeof(self->dgram_address));
+
+	set_unix_address(&self->stream_address, 0);
+	set_unix_address(&self->dgram_address, 1);
+}
+
+FIXTURE_TEARDOWN(unix_socket)
+{
+	close(self->server);
+	close(self->client);
+	close(self->dgram_server);
+	close(self->dgram_client);
+}
+
+/*
+ *        No domain
+ *
+ *   P1-.               P1 -> P2 : allow
+ *       \              P2 -> P1 : allow
+ *        'P2
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_without_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = false,
+	.domain_child = false,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_without_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = false,
+	.domain_child = false,
+	.connect_to_parent = false,
+};
+
+/*
+ *        Child domain
+ *
+ *   P1--.              P1 -> P2 : allow
+ *        \             P2 -> P1 : deny
+ *        .'-----.
+ *        |  P2  |
+ *        '------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_one_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = false,
+	.domain_child = true,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_with_one_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = false,
+	.domain_child = true,
+	.connect_to_parent = false,
+};
+
+/*
+ *        Parent domain
+ * .------.
+ * |  P1  --.           P1 -> P2 : deny
+ * '------'  \          P2 -> P1 : allow
+ *            '
+ *            P2
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_with_parent_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_parent_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = false,
+	.connect_to_parent = false,
+};
+
+/*
+ *        Parent + child domain (siblings)
+ * .------.
+ * |  P1  ---.          P1 -> P2 : deny
+ * '------'   \         P2 -> P1 : deny
+ *         .---'--.
+ *         |  P2  |
+ *         '------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_sibling_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = true,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_sibling_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = false,
+	.domain_parent = true,
+	.domain_child = true,
+	.connect_to_parent = false,
+};
+
+/*
+ *         Same domain (inherited)
+ * .-------------.
+ * | P1----.     |      P1 -> P2 : allow
+ * |        \    |      P2 -> P1 : allow
+ * |         '   |
+ * |         P2  |
+ * '-------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_inherited_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = false,
+	.domain_child = false,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_inherited_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = false,
+	.domain_child = false,
+	.connect_to_parent = false,
+};
+
+/*
+ *         Inherited + child domain
+ * .-----------------.
+ * |  P1----.        |  P1 -> P2 : allow
+ * |         \       |  P2 -> P1 : deny
+ * |        .-'----. |
+ * |        |  P2  | |
+ * |        '------' |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_nested_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = false,
+	.domain_child = true,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_nested_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = false,
+	.domain_child = true,
+	.connect_to_parent = false,
+};
+
+/*
+ *         Inherited + parent domain
+ * .-----------------.
+ * |.------.         |  P1 -> P2 : deny
+ * ||  P1  ----.     |  P2 -> P1 : allow
+ * |'------'    \    |
+ * |             '   |
+ * |             P2  |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, allow_with_nested_and_parent_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = false,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_nested_and_parent_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = false,
+	.connect_to_parent = false,
+};
+
+/*
+ *         Inherited + parent and child domain (siblings)
+ * .-----------------.
+ * | .------.        |  P1 -> P2 : deny
+ * | |  P1  .        |  P2 -> P1 : deny
+ * | '------'\       |
+ * |          \      |
+ * |        .--'---. |
+ * |        |  P2  | |
+ * |        '------' |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_forked_domain_connect_to_parent) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = true,
+	.connect_to_parent = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_socket, deny_with_forked_domain_connect_to_child) {
+	/* clang-format on */
+	.domain_both = true,
+	.domain_parent = true,
+	.domain_child = true,
+	.connect_to_parent = false,
+};
+
+/* Test UNIX_STREAM_CONNECT and UNIX_MAY_SEND for parent and child,
+ * when they have scoped domain or no domain.
+ */
+TEST_F(unix_socket, abstract_unix_socket)
+{
+	int status;
+	pid_t child;
+	bool can_connect_to_parent, can_connect_to_child;
+	int err, err_dgram;
+	int pipe_child[2], pipe_parent[2];
+	char buf_parent;
+
+	/*
+	 * can_connect_to_child is true if a parent process can connect to its
+	 * child process. The parent process is not isolated from the child
+	 * with a dedicated Landlock domain.
+	 */
+	can_connect_to_child = !variant->domain_parent;
+	/*
+	 * can_connect_to_parent is true if a child process can connect to its
+	 * parent process. This depends on the child process is not isolated from
+	 * the parent with a dedicated Landlock domain.
+	 */
+	can_connect_to_parent = !variant->domain_child;
+
+	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+	if (variant->domain_both) {
+		create_unix_domain(_metadata);
+		if (!__test_passed(_metadata))
+			return;
+	}
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		char buf_child;
+
+		ASSERT_EQ(0, close(pipe_parent[1]));
+		ASSERT_EQ(0, close(pipe_child[0]));
+		if (variant->domain_child)
+			create_unix_domain(_metadata);
+
+		/* Waits for the parent to be in a domain, if any. */
+		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+		if (variant->connect_to_parent) {
+			self->client = socket(AF_UNIX, SOCK_STREAM, 0);
+			self->dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
+
+			ASSERT_NE(-1, self->client);
+			ASSERT_NE(-1, self->dgram_client);
+			ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+			err = connect(self->client,
+				      &self->stream_address.unix_addr,
+				      (self->stream_address).unix_addr_len);
+			err_dgram =
+				connect(self->dgram_client,
+					&self->dgram_address.unix_addr,
+					(self->dgram_address).unix_addr_len);
+
+			if (can_connect_to_parent) {
+				EXPECT_EQ(0, err);
+				EXPECT_EQ(0, err_dgram);
+			} else {
+				EXPECT_EQ(-1, err);
+				EXPECT_EQ(-1, err_dgram);
+				EXPECT_EQ(EPERM, errno);
+			}
+		} else {
+			self->server = socket(AF_UNIX, SOCK_STREAM, 0);
+			self->dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
+			ASSERT_NE(-1, self->server);
+			ASSERT_NE(-1, self->dgram_server);
+
+			ASSERT_EQ(0,
+				  bind(self->server,
+				       &self->stream_address.unix_addr,
+				       (self->stream_address).unix_addr_len));
+			ASSERT_EQ(0, bind(self->dgram_server,
+					  &self->dgram_address.unix_addr,
+					  (self->dgram_address).unix_addr_len));
+			ASSERT_EQ(0, listen(self->server, 32));
+
+			/* signal to parent that child is listening */
+			ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+			/* wait to connect */
+			ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+		}
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(0, close(pipe_child[1]));
+	ASSERT_EQ(0, close(pipe_parent[0]));
+
+	if (variant->domain_parent)
+		create_unix_domain(_metadata);
+
+	/* Signals that the parent is in a domain, if any. */
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	if (!variant->connect_to_parent) {
+		self->client = socket(AF_UNIX, SOCK_STREAM, 0);
+		self->dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
+
+		ASSERT_NE(-1, self->client);
+		ASSERT_NE(-1, self->dgram_client);
+
+		/* Waits for the child to listen */
+		ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+		err = connect(self->client, &self->stream_address.unix_addr,
+			      (self->stream_address).unix_addr_len);
+		err_dgram = connect(self->dgram_client,
+				    &self->dgram_address.unix_addr,
+				    (self->dgram_address).unix_addr_len);
+
+		if (can_connect_to_child) {
+			EXPECT_EQ(0, err);
+			EXPECT_EQ(0, err_dgram);
+		} else {
+			EXPECT_EQ(-1, err);
+			EXPECT_EQ(-1, err_dgram);
+			EXPECT_EQ(EPERM, errno);
+		}
+		ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+	} else {
+		self->server = socket(AF_UNIX, SOCK_STREAM, 0);
+		self->dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
+		ASSERT_NE(-1, self->server);
+		ASSERT_NE(-1, self->dgram_server);
+		ASSERT_EQ(0, bind(self->server, &self->stream_address.unix_addr,
+				  (self->stream_address).unix_addr_len));
+		ASSERT_EQ(0, bind(self->dgram_server,
+				  &self->dgram_address.unix_addr,
+				  (self->dgram_address).unix_addr_len));
+		ASSERT_EQ(0, listen(self->server, 32));
+
+		/* signal to child that parent is listening */
+		ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+	}
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+enum sandbox_type {
+	NO_SANDBOX,
+	SCOPE_SANDBOX,
+	/* Any other type of sandboxing domain */
+	OTHER_SANDBOX,
+};
+
+/* clang-format off */
+FIXTURE(optional_scoping)
+{
+	struct service_fixture parent_address, child_address;
+	int parent_server, child_server, client;
+};
+
+/* clang-format on */
+FIXTURE_VARIANT(optional_scoping)
+{
+	const int domain_all;
+	const int domain_parent;
+	const int domain_children;
+	const int domain_child;
+	const int domain_grand_child;
+	const int type;
+};
+
+FIXTURE_SETUP(optional_scoping)
+{
+	memset(&self->parent_address, 0, sizeof(self->parent_address));
+	memset(&self->child_address, 0, sizeof(self->child_address));
+
+	set_unix_address(&self->parent_address, 0);
+	set_unix_address(&self->child_address, 1);
+}
+
+FIXTURE_TEARDOWN(optional_scoping)
+{
+	close(self->parent_server);
+	close(self->child_server);
+	close(self->client);
+}
+
+/*
+ * .-----------------.
+ * |         ####### |  P3 -> P2 : allow
+ * |   P1----# P2  # |  P3 -> P1 : deny
+ * |         #  |  # |
+ * |         # P3  # |
+ * |         ####### |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, deny_scoped) {
+	.domain_all = OTHER_SANDBOX,
+	.domain_parent = NO_SANDBOX,
+	.domain_children = SCOPE_SANDBOX,
+	.domain_child = NO_SANDBOX,
+	.domain_grand_child = NO_SANDBOX,
+	.type = SOCK_DGRAM,
+	/* clang-format on */
+};
+
+/*
+ * .-----------------.
+ * |         .-----. |  P3 -> P2 : allow
+ * |   P1----| P2  | |  P3 -> P1 : allow
+ * |         |     | |
+ * |         | P3  | |
+ * |         '-----' |
+ * '-----------------'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, 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,
+	.type = SOCK_DGRAM,
+	/* clang-format on */
+};
+
+/*
+ *  .----.    ######   P3 -> P2 : allow
+ *  | P1 |----# P2 #   P3 -> P1 : allow
+ *  '----'    ######
+ *              |
+ *              P3
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, 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,
+	.type = SOCK_DGRAM,
+	/* clang-format on */
+};
+
+/*
+ *  ######    .-----.   P3 -> P2 : allow
+ *  # P1 #----| P2  |   P3 -> P1 : allow
+ *  ######    '-----'
+ *              |
+ *              P3
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, 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,
+	.type = SOCK_STREAM,
+	/* clang-format on */
+};
+
+/*
+ *  ######    ######   P3 -> P2 : allow
+ *  # P1 #----# P2 #   P3 -> P1 : allow
+ *  ######    ######
+ *               |
+ *             .----.
+ *             | P3 |
+ *             '----'
+ */
+/* clang-format off */
+FIXTURE_VARIANT_ADD(optional_scoping, 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,
+	.type = SOCK_STREAM,
+	/* clang-format on */
+};
+
+/* Test UNIX_STREAM_CONNECT and UNIX_MAY_SEND for parent, child
+ * and grand child processes when they can have scoped or non-scoped
+ * domains.
+ **/
+TEST_F(optional_scoping, unix_scoping)
+{
+	pid_t child;
+	int status;
+	bool can_connect_to_parent, can_connect_to_child;
+	int pipe_parent[2];
+
+	if (variant->domain_grand_child == SCOPE_SANDBOX)
+		can_connect_to_child = false;
+	else
+		can_connect_to_child = true;
+
+	if (!can_connect_to_child || variant->domain_children == SCOPE_SANDBOX)
+		can_connect_to_parent = false;
+	else
+		can_connect_to_parent = true;
+
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+	if (variant->domain_all == OTHER_SANDBOX)
+		create_fs_domain(_metadata);
+	else if (variant->domain_all == SCOPE_SANDBOX)
+		create_unix_domain(_metadata);
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		int pipe_child[2];
+
+		ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+		pid_t grand_child;
+
+		if (variant->domain_children == OTHER_SANDBOX)
+			create_fs_domain(_metadata);
+		else if (variant->domain_children == SCOPE_SANDBOX)
+			create_unix_domain(_metadata);
+
+		grand_child = fork();
+		ASSERT_LE(0, grand_child);
+		if (grand_child == 0) {
+			ASSERT_EQ(0, close(pipe_parent[1]));
+			ASSERT_EQ(0, close(pipe_child[1]));
+
+			char buf1, buf2;
+			int err;
+
+			if (variant->domain_grand_child == OTHER_SANDBOX)
+				create_fs_domain(_metadata);
+			else if (variant->domain_grand_child == SCOPE_SANDBOX)
+				create_unix_domain(_metadata);
+
+			self->client = socket(AF_UNIX, variant->type, 0);
+			ASSERT_NE(-1, self->client);
+
+			ASSERT_EQ(1, read(pipe_child[0], &buf2, 1));
+			err = connect(self->client,
+				      &self->child_address.unix_addr,
+				      (self->child_address).unix_addr_len);
+			if (can_connect_to_child) {
+				EXPECT_EQ(0, err);
+			} else {
+				EXPECT_EQ(-1, err);
+				EXPECT_EQ(EPERM, errno);
+			}
+
+			if (variant->type == SOCK_STREAM) {
+				EXPECT_EQ(0, close(self->client));
+				self->client =
+					socket(AF_UNIX, variant->type, 0);
+				ASSERT_NE(-1, self->client);
+			}
+
+			ASSERT_EQ(1, read(pipe_parent[0], &buf1, 1));
+			err = connect(self->client,
+				      &self->parent_address.unix_addr,
+				      (self->parent_address).unix_addr_len);
+			if (can_connect_to_parent) {
+				EXPECT_EQ(0, err);
+			} else {
+				EXPECT_EQ(-1, err);
+				EXPECT_EQ(EPERM, errno);
+			}
+			EXPECT_EQ(0, close(self->client));
+
+			_exit(_metadata->exit_code);
+			return;
+		}
+
+		ASSERT_EQ(0, close(pipe_child[0]));
+		if (variant->domain_child == OTHER_SANDBOX)
+			create_fs_domain(_metadata);
+		else if (variant->domain_child == SCOPE_SANDBOX)
+			create_unix_domain(_metadata);
+
+		self->child_server = socket(AF_UNIX, variant->type, 0);
+		ASSERT_NE(-1, self->child_server);
+		ASSERT_EQ(0, bind(self->child_server,
+				  &self->child_address.unix_addr,
+				  (self->child_address).unix_addr_len));
+		if (variant->type == SOCK_STREAM)
+			ASSERT_EQ(0, listen(self->child_server, 32));
+
+		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+		ASSERT_EQ(grand_child, waitpid(grand_child, &status, 0));
+		return;
+	}
+	ASSERT_EQ(0, close(pipe_parent[0]));
+
+	if (variant->domain_parent == OTHER_SANDBOX)
+		create_fs_domain(_metadata);
+	else if (variant->domain_parent == SCOPE_SANDBOX)
+		create_unix_domain(_metadata);
+
+	self->parent_server = socket(AF_UNIX, variant->type, 0);
+	ASSERT_NE(-1, self->parent_server);
+	ASSERT_EQ(0, bind(self->parent_server, &self->parent_address.unix_addr,
+			  (self->parent_address).unix_addr_len));
+
+	if (variant->type == SOCK_STREAM)
+		ASSERT_EQ(0, listen(self->parent_server, 32));
+
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+/*
+ * Since the special case of scoping only happens when the connecting socket
+ * is scoped, the client's domain is true for all the following test cases.
+ */
+/* clang-format off */
+FIXTURE(unix_sock_special_cases) {
+	int server_socket, client;
+	int stream_server, stream_client;
+	struct service_fixture address, transit_address;
+};
+
+/* clang-format on */
+FIXTURE_VARIANT(unix_sock_special_cases)
+{
+	const bool domain_server;
+	const bool domain_server_socket;
+	const int type;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_sock_special_cases, allow_dgram_server_sock_domain) {
+	/* clang-format on */
+	.domain_server = false,
+	.domain_server_socket = true,
+	.type = SOCK_DGRAM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_sock_special_cases, deny_dgram_server_domain) {
+	/* clang-format on */
+	.domain_server = true,
+	.domain_server_socket = false,
+	.type = SOCK_DGRAM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_sock_special_cases, allow_stream_server_sock_domain) {
+	/* clang-format on */
+	.domain_server = false,
+	.domain_server_socket = true,
+	.type = SOCK_STREAM,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(unix_sock_special_cases, deny_stream_server_domain) {
+	/* clang-format on */
+	.domain_server = true,
+	.domain_server_socket = false,
+	.type = SOCK_STREAM,
+};
+
+FIXTURE_SETUP(unix_sock_special_cases)
+{
+	memset(&self->transit_address, 0, sizeof(self->transit_address));
+	memset(&self->address, 0, sizeof(self->address));
+	set_unix_address(&self->transit_address, 0);
+	set_unix_address(&self->address, 1);
+}
+
+FIXTURE_TEARDOWN(unix_sock_special_cases)
+{
+	close(self->client);
+	close(self->server_socket);
+	close(self->stream_server);
+	close(self->stream_client);
+}
+
+/* Test UNIX_STREAM_CONNECT and UNIX_MAY_SEND for parent and
+ * child processes when connecting socket has different domain
+ * than the process using it.
+ **/
+TEST_F(unix_sock_special_cases, socket_with_different_domain)
+{
+	pid_t child;
+	int err, status;
+	int pipe_child[2], pipe_parent[2];
+	char buf_parent;
+
+	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		char buf_child;
+
+		ASSERT_EQ(0, close(pipe_parent[1]));
+		ASSERT_EQ(0, close(pipe_child[0]));
+
+		/* client always has domain */
+		create_unix_domain(_metadata);
+
+		if (variant->domain_server_socket) {
+			int data_socket;
+			int fd_sock = socket(AF_UNIX, variant->type, 0);
+
+			ASSERT_NE(-1, fd_sock);
+
+			self->stream_server = socket(AF_UNIX, SOCK_STREAM, 0);
+
+			ASSERT_NE(-1, self->stream_server);
+			ASSERT_EQ(0,
+				  bind(self->stream_server,
+				       &self->transit_address.unix_addr,
+				       (self->transit_address).unix_addr_len));
+			ASSERT_EQ(0, listen(self->stream_server, 32));
+
+			ASSERT_EQ(1, write(pipe_child[1], ".", 1));
+
+			data_socket = accept(self->stream_server, NULL, NULL);
+
+			ASSERT_EQ(0, send_fd(data_socket, fd_sock));
+			ASSERT_EQ(0, close(fd_sock));
+		}
+
+		self->client = socket(AF_UNIX, variant->type, 0);
+		ASSERT_NE(-1, self->client);
+		/* wait for parent signal for connection */
+		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+		err = connect(self->client, &self->address.unix_addr,
+			      (self->address).unix_addr_len);
+		if (!variant->domain_server_socket) {
+			EXPECT_EQ(-1, err);
+			EXPECT_EQ(EPERM, errno);
+		} else {
+			EXPECT_EQ(0, err);
+		}
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(0, close(pipe_child[1]));
+	ASSERT_EQ(0, close(pipe_parent[0]));
+
+	if (!variant->domain_server_socket) {
+		self->server_socket = socket(AF_UNIX, variant->type, 0);
+	} else {
+		int cli = socket(AF_UNIX, SOCK_STREAM, 0);
+
+		ASSERT_NE(-1, cli);
+		ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
+		ASSERT_EQ(0, connect(cli, &self->transit_address.unix_addr,
+				     (self->transit_address).unix_addr_len));
+
+		self->server_socket = recv_fd(cli);
+		ASSERT_LE(0, self->server_socket);
+		ASSERT_EQ(0, close(cli));
+	}
+
+	ASSERT_NE(-1, self->server_socket);
+
+	if (variant->domain_server)
+		create_unix_domain(_metadata);
+
+	ASSERT_EQ(0, bind(self->server_socket, &self->address.unix_addr,
+			  (self->address).unix_addr_len));
+	if (variant->type == SOCK_STREAM)
+		ASSERT_EQ(0, listen(self->server_socket, 32));
+	/* signal to child that parent is listening */
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+static const char path1_variant1[] = TMP_DIR "/s1_variant1";
+static const char path2_variant1[] = TMP_DIR "/s2_variant1";
+
+/* clang-format off */
+FIXTURE(pathname_address_sockets) {
+	struct service_fixture stream_address, dgram_address;
+};
+
+/* clang-format on */
+FIXTURE_VARIANT(pathname_address_sockets)
+{
+	const int domain;
+	const char *stream_path;
+	const char *dgram_path;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(pathname_address_sockets, pathname_socket_scoped_domain) {
+	/* clang-format on */
+	.domain = SCOPE_SANDBOX,
+	.stream_path = path1_variant1,
+	.dgram_path = path2_variant1,
+};
+
+FIXTURE_SETUP(pathname_address_sockets)
+{
+	/* setup abstract addresses */
+	memset(&self->stream_address, 0, sizeof(self->stream_address));
+	set_unix_address(&self->stream_address, 0);
+
+	memset(&self->dgram_address, 0, sizeof(self->dgram_address));
+	set_unix_address(&self->dgram_address, 0);
+
+	const char *s_path = variant->stream_path;
+	const char *dg_path = variant->dgram_path;
+
+	disable_caps(_metadata);
+	umask(0077);
+	ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
+
+	ASSERT_EQ(0, mknod(s_path, S_IFREG | 0700, 0))
+	{
+		TH_LOG("Failed to create file \"%s\": %s", s_path,
+		       strerror(errno));
+		remove_path(TMP_DIR);
+	}
+	ASSERT_EQ(0, mknod(dg_path, S_IFREG | 0700, 0))
+	{
+		TH_LOG("Failed to create file \"%s\": %s", dg_path,
+		       strerror(errno));
+		remove_path(TMP_DIR);
+	}
+}
+
+FIXTURE_TEARDOWN(pathname_address_sockets)
+{
+	const char *s_path = variant->stream_path;
+	const char *dg_path = variant->dgram_path;
+
+	ASSERT_EQ(0, remove_path(dg_path));
+	ASSERT_EQ(0, remove_path(s_path));
+	ASSERT_EQ(0, remove_path(TMP_DIR));
+}
+
+TEST_F(pathname_address_sockets, scoped_pathname_sockets)
+{
+	const char *const stream_path = variant->stream_path;
+	const char *const dgram_path = variant->dgram_path;
+	int srv_fd, srv_fd_dg;
+	socklen_t size, size_dg;
+	struct sockaddr_un srv_un, srv_un_dg;
+	int pipe_parent[2];
+	pid_t child;
+	int status;
+	char buf_child;
+	int socket_fds_stream[2];
+	int server, client, dgram_server, dgram_client;
+	int recv_data;
+
+	ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
+				socket_fds_stream));
+
+	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		struct sockaddr_un cli_un, cli_un_dg;
+		int cli_fd, cli_fd_dg;
+		socklen_t size, size_dg;
+		int sample = socket(AF_UNIX, SOCK_STREAM, 0);
+		int err, err_dg;
+
+		ASSERT_LE(0, sample);
+
+		ASSERT_EQ(0, close(pipe_parent[1]));
+
+		/* scope the domain */
+		if (variant->domain == SCOPE_SANDBOX)
+			create_unix_domain(_metadata);
+		else if (variant->domain == OTHER_SANDBOX)
+			create_fs_domain(_metadata);
+		ASSERT_EQ(0, close(socket_fds_stream[1]));
+		ASSERT_EQ(0, send_fd(socket_fds_stream[0], sample));
+		ASSERT_EQ(0, close(sample));
+		ASSERT_EQ(0, close(socket_fds_stream[0]));
+
+		/* wait for server to listen */
+		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
+
+		cli_un.sun_family = AF_UNIX;
+		cli_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+		ASSERT_LE(0, cli_fd);
+
+		size = offsetof(struct sockaddr_un, sun_path) +
+		       strlen(cli_un.sun_path);
+		ASSERT_EQ(0, bind(cli_fd, (struct sockaddr *)&cli_un, size));
+
+		bzero(&cli_un, sizeof(cli_un));
+		cli_un.sun_family = AF_UNIX;
+		snprintf(cli_un.sun_path, sizeof(cli_un.sun_path), "%s",
+			 stream_path);
+		size = offsetof(struct sockaddr_un, sun_path) +
+		       strlen(cli_un.sun_path);
+
+		ASSERT_EQ(0, connect(cli_fd, (struct sockaddr *)&cli_un, size));
+
+		ASSERT_EQ(0, close(cli_fd));
+
+		cli_un_dg.sun_family = AF_UNIX;
+		cli_fd_dg = socket(AF_UNIX, SOCK_DGRAM, 0);
+		ASSERT_LE(0, cli_fd_dg);
+
+		size_dg = offsetof(struct sockaddr_un, sun_path) +
+			  strlen(cli_un_dg.sun_path);
+		ASSERT_EQ(0, bind(cli_fd_dg, (struct sockaddr *)&cli_un_dg,
+				  size_dg));
+
+		bzero(&cli_un_dg, sizeof(cli_un_dg));
+		cli_un_dg.sun_family = AF_UNIX;
+		snprintf(cli_un_dg.sun_path, sizeof(cli_un_dg.sun_path), "%s",
+			 dgram_path);
+		size_dg = offsetof(struct sockaddr_un, sun_path) +
+			  strlen(cli_un_dg.sun_path);
+
+		ASSERT_EQ(0, connect(cli_fd_dg, (struct sockaddr *)&cli_un_dg,
+				     size_dg));
+
+		ASSERT_EQ(0, close(cli_fd_dg));
+
+		/* check connection with abstract sockets */
+
+		client = socket(AF_UNIX, SOCK_STREAM, 0);
+		dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
+
+		ASSERT_NE(-1, client);
+		ASSERT_NE(-1, dgram_client);
+
+		err = connect(client, &(self->stream_address).unix_addr,
+			      (self->stream_address).unix_addr_len);
+		err_dg = connect(dgram_client, &(self->dgram_address).unix_addr,
+				 (self->dgram_address).unix_addr_len);
+		if (variant->domain == SCOPE_SANDBOX) {
+			EXPECT_EQ(-1, err);
+			EXPECT_EQ(-1, err_dg);
+			EXPECT_EQ(EPERM, errno);
+		} else {
+			EXPECT_EQ(0, err);
+			EXPECT_EQ(0, err_dg);
+		}
+		ASSERT_EQ(0, close(client));
+		ASSERT_EQ(0, close(dgram_client));
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	ASSERT_EQ(0, close(pipe_parent[0]));
+
+	recv_data = recv_fd(socket_fds_stream[1]);
+	ASSERT_LE(0, recv_data);
+	ASSERT_LE(0, close(socket_fds_stream[1]));
+
+	/* Sets up a server */
+	srv_un.sun_family = AF_UNIX;
+	snprintf(srv_un.sun_path, sizeof(srv_un.sun_path), "%s", stream_path);
+
+	ASSERT_EQ(0, unlink(stream_path));
+	srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	ASSERT_LE(0, srv_fd);
+
+	size = offsetof(struct sockaddr_un, sun_path) + strlen(srv_un.sun_path);
+	ASSERT_EQ(0, bind(srv_fd, (struct sockaddr *)&srv_un, size));
+	ASSERT_EQ(0, listen(srv_fd, 10));
+
+	/* set up a datagram server */
+	srv_un_dg.sun_family = AF_UNIX;
+	snprintf(srv_un_dg.sun_path, sizeof(srv_un_dg.sun_path), "%s",
+		 dgram_path);
+
+	ASSERT_EQ(0, unlink(dgram_path));
+	srv_fd_dg = socket(AF_UNIX, SOCK_DGRAM, 0);
+	ASSERT_LE(0, srv_fd_dg);
+
+	size_dg = offsetof(struct sockaddr_un, sun_path) +
+		  strlen(srv_un_dg.sun_path);
+	ASSERT_EQ(0, bind(srv_fd_dg, (struct sockaddr *)&srv_un_dg, size_dg));
+
+	/*set up abstract servers */
+
+	server = socket(AF_UNIX, SOCK_STREAM, 0);
+	dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
+	ASSERT_NE(-1, server);
+	ASSERT_NE(-1, dgram_server);
+
+	ASSERT_EQ(0, bind(server, &(self->stream_address).unix_addr,
+			  self->stream_address.unix_addr_len));
+	ASSERT_EQ(0, bind(dgram_server, &(self->dgram_address).unix_addr,
+			  self->dgram_address.unix_addr_len));
+	ASSERT_EQ(0, listen(server, 32));
+
+	/* signal to child! */
+	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(0, close(srv_fd));
+	ASSERT_EQ(0, close(srv_fd_dg));
+	ASSERT_EQ(0, close(server));
+	ASSERT_EQ(0, close(dgram_server));
+
+	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) != EXIT_SUCCESS)
+		_metadata->exit_code = KSFT_FAIL;
+}
+
+TEST_HARNESS_MAIN
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction
  2024-08-02  4:02 [PATCH v8 0/4] Landlock: Add abstract unix socket connect Tahera Fahimi
  2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
  2024-08-02  4:02 ` [PATCH v8 2/4] selftests/landlock: Abstract unix socket restriction tests Tahera Fahimi
@ 2024-08-02  4:02 ` Tahera Fahimi
  2024-08-09 14:11   ` Mickaël Salaün
  2024-08-02  4:02 ` [PATCH v8 4/4] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
  3 siblings, 1 reply; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-02  4:02 UTC (permalink / raw)
  To: outreachy
  Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi

A sandboxer can receive the character "a" as input from the environment
variable LL_SCOPE to restrict the abstract unix sockets from connecting
to a process outside its scoped domain.

Example
=======
Create an abstract unix socket to listen with socat(1):
socat abstract-listen:mysocket -
Create a sandboxed shell and pass the character "a" to LL_SCOPED:
LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
If the sandboxed process tries to connect to the listening socket
with command "socat - abstract-connect:mysocket", the connection
will fail.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>

---
v8:
- Adding check_ruleset_scope function to parse the scope environment
  variable and update the landlock attribute based on the restriction
  provided by the user.
- Adding Mickaël Salaün reviews on version 7.

v7:
- Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED"
  environment variable. "LL_SCOPED" gets value "a" to restrict abstract
  unix sockets.
- Change LANDLOCK_ABI_LAST to 6.
---
 samples/landlock/sandboxer.c | 56 +++++++++++++++++++++++++++++++++---
 1 file changed, 52 insertions(+), 4 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e8223c3e781a..98132fd823ad 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -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 parse_path(char *env_path, const char ***const path_list)
@@ -184,6 +187,38 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
 	return ret;
 }
 
+static bool check_ruleset_scope(const char *const env_var,
+				struct landlock_ruleset_attr *ruleset_attr)
+{
+	bool ret = true;
+	char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
+
+	ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
+	env_type_scope = getenv(env_var);
+	/* scoping is not supported by the user */
+	if (!env_type_scope)
+		return true;
+	env_type_scope = strdup(env_type_scope);
+	unsetenv(env_var);
+
+	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) {
+			ruleset_attr->scoped |=
+				LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
+		} else {
+			fprintf(stderr, "Unsupported scoping \"%s\"\n",
+				ipc_scoping_name);
+			ret = false;
+			goto out_free_name;
+		}
+	}
+out_free_name:
+	free(env_type_scope);
+	return ret;
+}
+
 /* clang-format off */
 
 #define ACCESS_FS_ROUGHLY_READ ( \
@@ -208,7 +243,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
 
 /* 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)
 {
@@ -223,14 +258,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_SCOPED_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,
@@ -251,15 +287,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 restrictions on 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",
@@ -327,6 +366,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_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
+		ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
 			"to leverage Landlock features "
@@ -358,6 +401,11 @@ 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)) {
+		perror("Unsupported IPC scoping requested");
+		return 1;
+	}
+
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	if (ruleset_fd < 0) {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH v8 4/4] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning
  2024-08-02  4:02 [PATCH v8 0/4] Landlock: Add abstract unix socket connect Tahera Fahimi
                   ` (2 preceding siblings ...)
  2024-08-02  4:02 ` [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
@ 2024-08-02  4:02 ` Tahera Fahimi
  2024-08-07 15:14   ` Mickaël Salaün
  3 siblings, 1 reply; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-02  4:02 UTC (permalink / raw)
  To: outreachy
  Cc: mic, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev, Tahera Fahimi

Introducing LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET as an IPC scoping
mechanism in Landlock ABI version 6, and updating ruleset_attr,
Landlock ABI version, and access rights code blocks based on that.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
---
v8:
- Improving documentation by specifying differences between scoped and
  non-scoped domains.
- Adding review notes of version 7.
- Update date
v7:
- Add "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" explanation to IPC scoping
  section and updating ABI to version 6.
- Adding "scoped" attribute to the Access rights section.
- In current limitation, unnamed sockets are specified as sockets that
  are not restricted.
- Update date
---
 Documentation/userspace-api/landlock.rst | 33 ++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 07b63aec56fa..d602567b5139 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -8,7 +8,7 @@ Landlock: unprivileged access control
 =====================================
 
 :Author: Mickaël Salaün
-:Date: April 2024
+:Date: August 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_SCOPED_ABSTRACT_UNIX_SOCKET,
     };
 
 Because we may not know on which kernel version an application will be
@@ -119,6 +121,9 @@ 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;
+    case 5:
+        /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
+        ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
     }
 
 This enables to create an inclusive ruleset that will contain our rules.
@@ -306,6 +311,23 @@ 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_SCOPED_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 processes in
+the same scoped domain.
+
+IPC scoping does not support Landlock rules, so if a domain is scoped, no rules
+can be added to allow accessing to a resource outside of the scoped domain.
+
 Truncating files
 ----------------
 
@@ -404,7 +426,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 +563,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 sockets Restriction  (ABI < 6)
+--------------------------------------------
+
+With ABI version 6, it is possible to restrict connection to an abstract Unix socket
+through ``LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET``, thanks to the ``scoped`` ruleset
+attribute.
+
 .. _kernel_support:
 
 Kernel support
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
@ 2024-08-02 16:47   ` Mickaël Salaün
  2024-08-03 11:29   ` Mickaël Salaün
  2024-08-06 19:36   ` Jann Horn
  2 siblings, 0 replies; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-02 16:47 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> abstract Unix sockets from connecting to a process outside of
> the same landlock domain. It implements two hooks, unix_stream_connect
> and unix_may_send to enforce this restriction.
> 
> Closes: https://github.com/landlock-lsm/linux/issues/7
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> 
> ---
> v8:
> - Code refactoring (improve code readability, renaming variable, etc.) based
>   on reviews by Mickaël Salaün on version 7.
> - Adding warn_on_once to check (impossible) inconsistencies.
> - Adding inline comments.
> - Adding check_unix_address_format to check if the scoping socket is an abstract
>   unix sockets.
> v7:
>  - Using socket's file credentials for both connected(STREAM) and
>    non-connected(DGRAM) sockets.
>  - Adding "domain_sock_scope" instead of the domain scoping mechanism used in
>    ptrace ensures that if a server's domain is accessible from the client's
>    domain (where the client is more privileged than the server), the client
>    can connect to the server in all edge cases.
>  - Removing debug codes.
> v6:
>  - Removing curr_ruleset from landlock_hierarchy, and switching back to use
>    the same domain scoping as ptrace.
>  - code clean up.
> v5:
>  - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
>  - Adding curr_ruleset to hierarachy_ruleset structure to have access from
>    landlock_hierarchy to its respective landlock_ruleset.
>  - Using curr_ruleset to check if a domain is scoped while walking in the
>    hierarchy of domains.
>  - Modifying inline comments.
> V4:
>  - Rebased on Günther's Patch:
>    https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
>    so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
>  - Adding get_scope_accesses function to check all scoped access masks in a ruleset.
>  - Using socket's file credentials instead of credentials stored in peer_cred
>    for datagram sockets. (see discussion in [1])
>  - Modifying inline comments.
> V3:
>  - Improving commit description.
>  - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
>    purpose, and adding related functions.
>  - Changing structure of ruleset based on "scoped".
>  - Removing rcu lock and using unix_sk lock instead.
>  - Introducing scoping for datagram sockets in unix_may_send.
> V2:
>  - Removing wrapper functions
> 
> [1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
> ----
> ---
>  include/uapi/linux/landlock.h |  30 +++++++
>  security/landlock/limits.h    |   3 +
>  security/landlock/ruleset.c   |   7 +-
>  security/landlock/ruleset.h   |  23 ++++-
>  security/landlock/syscalls.c  |  14 ++-
>  security/landlock/task.c      | 155 ++++++++++++++++++++++++++++++++++
>  6 files changed, 225 insertions(+), 7 deletions(-)
> 
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index 68625e728f43..ab31e9f53e55 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -37,6 +37,12 @@ struct landlock_ruleset_attr {
>  	 * rule explicitly allow them.
>  	 */
>  	__u64 handled_access_net;
> +	/**
> +	 * @scoped: Bitmask of scopes (cf. `Scope flags`_)
> +	 * restricting a Landlock domain from accessing outside
> +	 * resources(e.g. IPCs).
> +	 */
> +	__u64 scoped;
>  };
>  
>  /*
> @@ -266,4 +272,28 @@ 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
> + *
> + * scoped attribute handles a set of restrictions on kernel IPCs through
> + * the following flags.

If present, this previous sentence should be after the "Scope flags"
section, otherwise it shows in the previous section.  I don't think this
sentence is useful because of the next one, so you can remove it.

> + *
> + * Scope flags
> + * ~~~~~~~~~~~
> + *
> + * These flags enable to restrict 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.
> + *
> + * IPCs with scoped actions:
> + *
> + * - %LANDLOCK_SCOPED_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_SCOPED_ABSTRACT_UNIX_SOCKET		(1ULL << 0)
> +/* clang-format on*/
>  #endif /* _UAPI_LINUX_LANDLOCK_H */
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index 4eb643077a2a..eb01d0fb2165 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -26,6 +26,9 @@
>  #define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
>  #define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>  
> +#define LANDLOCK_LAST_SCOPE		LANDLOCK_SCOPED_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 */
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 6ff232f58618..a93bdbf52fff 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -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;
>  }
>  
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 0f1b5b4c8f6b..c749fa0b3ecd 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -35,6 +35,8 @@ 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));
>  
> @@ -42,6 +44,7 @@ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>  struct access_masks {
>  	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
>  	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> +	access_mask_t scoped : LANDLOCK_NUM_SCOPE;
>  };
>  
>  typedef u16 layer_mask_t;
> @@ -233,7 +236,8 @@ struct landlock_ruleset {
>  
>  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);
> @@ -280,6 +284,16 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
>  	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 scoped_mask = scope_mask & LANDLOCK_MASK_SCOPE;
> +
> +	WARN_ON_ONCE(scope_mask != scoped_mask);
> +	ruleset->access_masks[layer_level].scoped |= scoped_mask;
> +}
> +
>  static inline access_mask_t
>  landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
>  				const u16 layer_level)
> @@ -303,6 +317,13 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
>  	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].scoped;
> +}
> +
>  bool landlock_unmask_layers(const struct landlock_rule *const rule,
>  			    const access_mask_t access_request,
>  			    layer_mask_t (*const layer_masks)[],
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 03b470f5a85a..f51b29521d6b 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -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,7 +171,7 @@ 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;
> + * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size;

I think you missed one of my previous review.

>   * - %E2BIG or %EFAULT: @attr or @size inconsistencies;
>   * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
>   */
> @@ -213,9 +214,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);
>  
> diff --git a/security/landlock/task.c b/security/landlock/task.c
> index 849f5123610b..7e8579ebae83 100644
> --- a/security/landlock/task.c
> +++ b/security/landlock/task.c
> @@ -13,6 +13,8 @@
>  #include <linux/lsm_hooks.h>
>  #include <linux/rcupdate.h>
>  #include <linux/sched.h>
> +#include <net/sock.h>
> +#include <net/af_unix.h>

Please sort the included files.

>  
>  #include "common.h"
>  #include "cred.h"
> @@ -108,9 +110,162 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
>  	return task_ptrace(parent, current);
>  }
>  
> +static bool walk_and_check(const struct landlock_ruleset *const child,
> +			   struct landlock_hierarchy **walker,
> +			   size_t base_layer, size_t deep_layer,
> +			   access_mask_t check_scoping)
> +{
> +	if (!child || base_layer < 0 || !(*walker))
> +		return false;
> +
> +	for (deep_layer; base_layer < deep_layer; deep_layer--) {
> +		if (check_scoping & landlock_get_scope_mask(child, deep_layer))
> +			return false;
> +		*walker = (*walker)->parent;
> +		if (WARN_ON_ONCE(!*walker))
> +			/* there is an inconsistency between num_layers
> +			 * and landlock_hierarchy in the ruleset
> +			 */
> +			return false;
> +	}
> +	return true;
> +}
> +
> +/**
> + * domain_IPC_scope - Checks if the client domain is scoped in the same

Please don't use uppercase (IPC) in function or variable's names, it is
reserved to "#define" statements only.

> + *		      domain as the server.
> + *
> + * @client: IPC sender domain.
> + * @server: IPC receiver domain.
> + *
> + * Check if the @client domain is scoped to access the @server; the @server
> + * must be scoped in the same domain.
> + */
> +static bool domain_IPC_scope(const struct landlock_ruleset *const client,
> +			     const struct landlock_ruleset *const server,
> +			     access_mask_t ipc_type)
> +{
> +	size_t client_layer, server_layer = 0;
> +	int base_layer;
> +	struct landlock_hierarchy *client_walker, *server_walker;
> +	bool is_scoped;
> +
> +	/* Quick return if client has no domain */
> +	if (!client)
> +		return true;
> +
> +	client_layer = client->num_layers - 1;
> +	client_walker = client->hierarchy;
> +	if (server) {
> +		server_layer = server->num_layers - 1;
> +		server_walker = server->hierarchy;
> +	}
> +	base_layer = (client_layer > server_layer) ? server_layer :
> +						     client_layer;
> +
> +	/* For client domain, walk_and_check ensures the client domain is
> +	 * not scoped until gets to base_layer.
> +	 * For server_domain, it only ensures that the server domain exist.
> +	 */
> +	if (client_layer != server_layer) {
> +		if (client_layer > server_layer)
> +			is_scoped = walk_and_check(client, &client_walker,
> +						   server_layer, client_layer,
> +						   ipc_type);
> +		else
> +			is_scoped = walk_and_check(server, &server_walker,
> +						   client_layer, server_layer,
> +						   ipc_type & 0);
> +		if (!is_scoped)
> +			return false;
> +	}
> +	/* client and server are at the same level in hierarchy. If client is
> +	 * scoped, the server must be scoped in the same domain
> +	 */
> +	for (base_layer; base_layer >= 0; base_layer--) {
> +		if (landlock_get_scope_mask(client, base_layer) & ipc_type) {
> +			/* This check must be here since access would be denied only if
> +			 * the client is scoped and the server has no domain, so
> +			 * if the client has a domain but is not scoped and the server
> +			 * has no domain, access is guaranteed.
> +			 */
> +			if (!server)
> +				return false;
> +
> +			if (server_walker == client_walker)
> +				return true;
> +
> +			return false;
> +		}
> +		client_walker = client_walker->parent;
> +		server_walker = server_walker->parent;
> +		/* Warn if there is an incosistenncy between num_layers and
> +		 * landlock_hierarchy in each of rulesets
> +		 */
> +		if (WARN_ON_ONCE(base_layer > 0 &&
> +				 (!server_walker || !client_walker)))
> +			return false;
> +	}
> +	return true;
> +}
> +
> +static bool sock_is_scoped(struct sock *const other)
> +{
> +	const struct landlock_ruleset *dom_other;
> +	const struct landlock_ruleset *const dom =
> +		landlock_get_current_domain();
> +
> +	/* 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_IPC_scope(dom, dom_other,
> +				LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET);
> +}
> +
> +static bool check_unix_address_format(struct sock *const sock)
> +{
> +	struct unix_address *addr = unix_sk(sock)->addr;
> +
> +	if (!addr)
> +		return true;
> +
> +	if (addr->len > sizeof(AF_UNIX)) {

AF_UNIX is the family, not a length.

I'm wondering if we cannot also check the "path" field or something
else.

> +		/* handling unspec sockets */
> +		if (!addr->name[0].sun_path)
> +			return true;
> +
> +		if (addr->name[0].sun_path[0] == '\0')
> +			if (!sock_is_scoped(sock))

This function should only check the address format.  sock_is_scoped()
can be called in each LSM hook.

> +				return false;
> +	}
> +
> +	return true;
> +}
> +
> +static int hook_unix_stream_connect(struct sock *const sock,
> +				    struct sock *const other,
> +				    struct sock *const newsk)
> +{

To not impact unsandboxed tasks, we need to first check if current is
sandboxed with Landlock, and then pass this dom to the following
functions.

> +	if (check_unix_address_format(other))
> +		return 0;
> +
> +	return -EPERM;
> +}
> +
> +static int hook_unix_may_send(struct socket *const sock,
> +			      struct socket *const other)
> +{
> +	if (check_unix_address_format(other->sk))
> +		return 0;
> +
> +	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),
>  };
>  
>  __init void landlock_add_task_hooks(void)
> -- 
> 2.34.1
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
  2024-08-02 16:47   ` Mickaël Salaün
@ 2024-08-03 11:29   ` Mickaël Salaün
  2024-08-06 19:35     ` Mickaël Salaün
  2024-08-07 15:37     ` Tahera Fahimi
  2024-08-06 19:36   ` Jann Horn
  2 siblings, 2 replies; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-03 11:29 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> abstract Unix sockets from connecting to a process outside of
> the same landlock domain. It implements two hooks, unix_stream_connect
> and unix_may_send to enforce this restriction.
> 
> Closes: https://github.com/landlock-lsm/linux/issues/7
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> 
> ---
> v8:
> - Code refactoring (improve code readability, renaming variable, etc.) based
>   on reviews by Mickaël Salaün on version 7.
> - Adding warn_on_once to check (impossible) inconsistencies.
> - Adding inline comments.
> - Adding check_unix_address_format to check if the scoping socket is an abstract
>   unix sockets.
> v7:
>  - Using socket's file credentials for both connected(STREAM) and
>    non-connected(DGRAM) sockets.
>  - Adding "domain_sock_scope" instead of the domain scoping mechanism used in
>    ptrace ensures that if a server's domain is accessible from the client's
>    domain (where the client is more privileged than the server), the client
>    can connect to the server in all edge cases.
>  - Removing debug codes.
> v6:
>  - Removing curr_ruleset from landlock_hierarchy, and switching back to use
>    the same domain scoping as ptrace.
>  - code clean up.
> v5:
>  - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
>  - Adding curr_ruleset to hierarachy_ruleset structure to have access from
>    landlock_hierarchy to its respective landlock_ruleset.
>  - Using curr_ruleset to check if a domain is scoped while walking in the
>    hierarchy of domains.
>  - Modifying inline comments.
> V4:
>  - Rebased on Günther's Patch:
>    https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
>    so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
>  - Adding get_scope_accesses function to check all scoped access masks in a ruleset.
>  - Using socket's file credentials instead of credentials stored in peer_cred
>    for datagram sockets. (see discussion in [1])
>  - Modifying inline comments.
> V3:
>  - Improving commit description.
>  - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
>    purpose, and adding related functions.
>  - Changing structure of ruleset based on "scoped".
>  - Removing rcu lock and using unix_sk lock instead.
>  - Introducing scoping for datagram sockets in unix_may_send.
> V2:
>  - Removing wrapper functions
> 
> [1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
> ----
> ---
>  include/uapi/linux/landlock.h |  30 +++++++
>  security/landlock/limits.h    |   3 +
>  security/landlock/ruleset.c   |   7 +-
>  security/landlock/ruleset.h   |  23 ++++-
>  security/landlock/syscalls.c  |  14 ++-
>  security/landlock/task.c      | 155 ++++++++++++++++++++++++++++++++++
>  6 files changed, 225 insertions(+), 7 deletions(-)

> diff --git a/security/landlock/task.c b/security/landlock/task.c
> index 849f5123610b..7e8579ebae83 100644
> --- a/security/landlock/task.c
> +++ b/security/landlock/task.c
> @@ -13,6 +13,8 @@
>  #include <linux/lsm_hooks.h>
>  #include <linux/rcupdate.h>
>  #include <linux/sched.h>
> +#include <net/sock.h>
> +#include <net/af_unix.h>
>  
>  #include "common.h"
>  #include "cred.h"
> @@ -108,9 +110,162 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
>  	return task_ptrace(parent, current);
>  }
>  
> +static bool walk_and_check(const struct landlock_ruleset *const child,
> +			   struct landlock_hierarchy **walker,
> +			   size_t base_layer, size_t deep_layer,
> +			   access_mask_t check_scoping)

s/check_scoping/scope/

> +{
> +	if (!child || base_layer < 0 || !(*walker))

I guess it should be:
WARN_ON_ONCE(!child || base_layer < 0 || !(*walker))

> +		return false;
> +
> +	for (deep_layer; base_layer < deep_layer; deep_layer--) {

No need to pass deep_layer as argument:
deep_layer = child->num_layers - 1

> +		if (check_scoping & landlock_get_scope_mask(child, deep_layer))
> +			return false;
> +		*walker = (*walker)->parent;
> +		if (WARN_ON_ONCE(!*walker))
> +			/* there is an inconsistency between num_layers

Please use full sentences starting with a capital letter and ending with
a dot, and in this case start with "/*"

> +			 * and landlock_hierarchy in the ruleset
> +			 */
> +			return false;
> +	}
> +	return true;
> +}
> +
> +/**
> + * domain_IPC_scope - Checks if the client domain is scoped in the same
> + *		      domain as the server.

Actually, you can remove IPC from the function name.

> + *
> + * @client: IPC sender domain.
> + * @server: IPC receiver domain.
> + *
> + * Check if the @client domain is scoped to access the @server; the @server
> + * must be scoped in the same domain.

Returns true if...

> + */
> +static bool domain_IPC_scope(const struct landlock_ruleset *const client,
> +			     const struct landlock_ruleset *const server,
> +			     access_mask_t ipc_type)
> +{
> +	size_t client_layer, server_layer = 0;
> +	int base_layer;
> +	struct landlock_hierarchy *client_walker, *server_walker;
> +	bool is_scoped;
> +
> +	/* Quick return if client has no domain */
> +	if (!client)
> +		return true;
> +
> +	client_layer = client->num_layers - 1;
> +	client_walker = client->hierarchy;
> +	if (server) {
> +		server_layer = server->num_layers - 1;
> +		server_walker = server->hierarchy;
> +	}

} else {
	server_layer = 0;
	server_walker = NULL;
}

> +	base_layer = (client_layer > server_layer) ? server_layer :
> +						     client_layer;
> +
> +	/* For client domain, walk_and_check ensures the client domain is
> +	 * not scoped until gets to base_layer.

until gets?

> +	 * For server_domain, it only ensures that the server domain exist.
> +	 */
> +	if (client_layer != server_layer) {

bool is_scoped;

> +		if (client_layer > server_layer)
> +			is_scoped = walk_and_check(client, &client_walker,
> +						   server_layer, client_layer,
> +						   ipc_type);
> +		else

server_walker may be uninitialized and still read here, and maybe later
in the for loop.  The whole code should maks sure this cannot happen,
and a test case should check this.

> +			is_scoped = walk_and_check(server, &server_walker,
> +						   client_layer, server_layer,
> +						   ipc_type & 0);

"ipc_type & 0" is the same as "0"

> +		if (!is_scoped)

The name doesn't reflect the semantic. walk_and_check() should return
the inverse.

> +			return false;
> +	}

This code would be simpler:

if (client_layer > server_layer) {
	base_layer = server_layer;
	// TODO: inverse boolean logic
	if (!walk_and_check(client, &client_walker,
				   base_layer, ipc_type))
		return false;
} else (client_layer < server_layer) {
	base_layer = client_layer;
	// TODO: inverse boolean logic
	if (!walk_and_check(server, &server_walker,
				   base_layer, 0))
		return false;
} else {
	base_layer = client_layer;
}


I think we can improve more to make sure there is no path/risk of
inconsistent pointers.


> +	/* client and server are at the same level in hierarchy. If client is
> +	 * scoped, the server must be scoped in the same domain
> +	 */
> +	for (base_layer; base_layer >= 0; base_layer--) {
> +		if (landlock_get_scope_mask(client, base_layer) & ipc_type) {

With each multi-line comment, the first line should be empty:
/*
 * This check must be here since access would be denied only if

> +			/* This check must be here since access would be denied only if
> +			 * the client is scoped and the server has no domain, so
> +			 * if the client has a domain but is not scoped and the server
> +			 * has no domain, access is guaranteed.
> +			 */
> +			if (!server)
> +				return false;
> +
> +			if (server_walker == client_walker)
> +				return true;
> +
> +			return false;
> +		}
> +		client_walker = client_walker->parent;
> +		server_walker = server_walker->parent;
> +		/* Warn if there is an incosistenncy between num_layers and

Makes sure there is no inconsistency between num_layers and


> +		 * landlock_hierarchy in each of rulesets
> +		 */
> +		if (WARN_ON_ONCE(base_layer > 0 &&
> +				 (!server_walker || !client_walker)))
> +			return false;
> +	}
> +	return true;
> +}

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-03 11:29   ` Mickaël Salaün
@ 2024-08-06 19:35     ` Mickaël Salaün
  2024-08-06 20:46       ` Jann Horn
  2024-08-07 15:37     ` Tahera Fahimi
  1 sibling, 1 reply; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-06 19:35 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Sat, Aug 03, 2024 at 01:29:09PM +0200, Mickaël Salaün wrote:
> On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> > This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> > that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> > abstract Unix sockets from connecting to a process outside of
> > the same landlock domain. It implements two hooks, unix_stream_connect
> > and unix_may_send to enforce this restriction.
> > 
> > Closes: https://github.com/landlock-lsm/linux/issues/7
> > Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> > 
> > ---
> > v8:
> > - Code refactoring (improve code readability, renaming variable, etc.) based
> >   on reviews by Mickaël Salaün on version 7.
> > - Adding warn_on_once to check (impossible) inconsistencies.
> > - Adding inline comments.
> > - Adding check_unix_address_format to check if the scoping socket is an abstract
> >   unix sockets.
> > v7:
> >  - Using socket's file credentials for both connected(STREAM) and
> >    non-connected(DGRAM) sockets.
> >  - Adding "domain_sock_scope" instead of the domain scoping mechanism used in
> >    ptrace ensures that if a server's domain is accessible from the client's
> >    domain (where the client is more privileged than the server), the client
> >    can connect to the server in all edge cases.
> >  - Removing debug codes.
> > v6:
> >  - Removing curr_ruleset from landlock_hierarchy, and switching back to use
> >    the same domain scoping as ptrace.
> >  - code clean up.
> > v5:
> >  - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
> >  - Adding curr_ruleset to hierarachy_ruleset structure to have access from
> >    landlock_hierarchy to its respective landlock_ruleset.
> >  - Using curr_ruleset to check if a domain is scoped while walking in the
> >    hierarchy of domains.
> >  - Modifying inline comments.
> > V4:
> >  - Rebased on Günther's Patch:
> >    https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
> >    so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
> >  - Adding get_scope_accesses function to check all scoped access masks in a ruleset.
> >  - Using socket's file credentials instead of credentials stored in peer_cred
> >    for datagram sockets. (see discussion in [1])
> >  - Modifying inline comments.
> > V3:
> >  - Improving commit description.
> >  - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
> >    purpose, and adding related functions.
> >  - Changing structure of ruleset based on "scoped".
> >  - Removing rcu lock and using unix_sk lock instead.
> >  - Introducing scoping for datagram sockets in unix_may_send.
> > V2:
> >  - Removing wrapper functions
> > 
> > [1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
> > ----
> > ---
> >  include/uapi/linux/landlock.h |  30 +++++++
> >  security/landlock/limits.h    |   3 +
> >  security/landlock/ruleset.c   |   7 +-
> >  security/landlock/ruleset.h   |  23 ++++-
> >  security/landlock/syscalls.c  |  14 ++-
> >  security/landlock/task.c      | 155 ++++++++++++++++++++++++++++++++++
> >  6 files changed, 225 insertions(+), 7 deletions(-)
> 
> > diff --git a/security/landlock/task.c b/security/landlock/task.c
> > index 849f5123610b..7e8579ebae83 100644
> > --- a/security/landlock/task.c
> > +++ b/security/landlock/task.c
> > @@ -13,6 +13,8 @@
> >  #include <linux/lsm_hooks.h>
> >  #include <linux/rcupdate.h>
> >  #include <linux/sched.h>
> > +#include <net/sock.h>
> > +#include <net/af_unix.h>
> >  
> >  #include "common.h"
> >  #include "cred.h"
> > @@ -108,9 +110,162 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
> >  	return task_ptrace(parent, current);
> >  }
> >  
> > +static bool walk_and_check(const struct landlock_ruleset *const child,
> > +			   struct landlock_hierarchy **walker,
> > +			   size_t base_layer, size_t deep_layer,
> > +			   access_mask_t check_scoping)
> 
> s/check_scoping/scope/
> 
> > +{
> > +	if (!child || base_layer < 0 || !(*walker))
> 
> I guess it should be:
> WARN_ON_ONCE(!child || base_layer < 0 || !(*walker))
> 
> > +		return false;
> > +
> > +	for (deep_layer; base_layer < deep_layer; deep_layer--) {
> 
> No need to pass deep_layer as argument:
> deep_layer = child->num_layers - 1
> 
> > +		if (check_scoping & landlock_get_scope_mask(child, deep_layer))
> > +			return false;
> > +		*walker = (*walker)->parent;
> > +		if (WARN_ON_ONCE(!*walker))
> > +			/* there is an inconsistency between num_layers
> 
> Please use full sentences starting with a capital letter and ending with
> a dot, and in this case start with "/*"
> 
> > +			 * and landlock_hierarchy in the ruleset
> > +			 */
> > +			return false;
> > +	}
> > +	return true;
> > +}
> > +
> > +/**
> > + * domain_IPC_scope - Checks if the client domain is scoped in the same
> > + *		      domain as the server.
> 
> Actually, you can remove IPC from the function name.
> 
> > + *
> > + * @client: IPC sender domain.
> > + * @server: IPC receiver domain.
> > + *
> > + * Check if the @client domain is scoped to access the @server; the @server
> > + * must be scoped in the same domain.
> 
> Returns true if...
> 
> > + */
> > +static bool domain_IPC_scope(const struct landlock_ruleset *const client,
> > +			     const struct landlock_ruleset *const server,
> > +			     access_mask_t ipc_type)
> > +{
> > +	size_t client_layer, server_layer = 0;
> > +	int base_layer;
> > +	struct landlock_hierarchy *client_walker, *server_walker;
> > +	bool is_scoped;
> > +
> > +	/* Quick return if client has no domain */
> > +	if (!client)
> > +		return true;
> > +
> > +	client_layer = client->num_layers - 1;
> > +	client_walker = client->hierarchy;
> > +	if (server) {
> > +		server_layer = server->num_layers - 1;
> > +		server_walker = server->hierarchy;
> > +	}
> 
> } else {
> 	server_layer = 0;
> 	server_walker = NULL;
> }
> 
> > +	base_layer = (client_layer > server_layer) ? server_layer :
> > +						     client_layer;
> > +
> > +	/* For client domain, walk_and_check ensures the client domain is
> > +	 * not scoped until gets to base_layer.
> 
> until gets?
> 
> > +	 * For server_domain, it only ensures that the server domain exist.
> > +	 */
> > +	if (client_layer != server_layer) {
> 
> bool is_scoped;
> 
> > +		if (client_layer > server_layer)
> > +			is_scoped = walk_and_check(client, &client_walker,
> > +						   server_layer, client_layer,
> > +						   ipc_type);
> > +		else
> 
> server_walker may be uninitialized and still read here, and maybe later
> in the for loop.  The whole code should maks sure this cannot happen,
> and a test case should check this.
> 
> > +			is_scoped = walk_and_check(server, &server_walker,
> > +						   client_layer, server_layer,
> > +						   ipc_type & 0);
> 
> "ipc_type & 0" is the same as "0"
> 
> > +		if (!is_scoped)
> 
> The name doesn't reflect the semantic. walk_and_check() should return
> the inverse.
> 
> > +			return false;
> > +	}
> 
> This code would be simpler:
> 
> if (client_layer > server_layer) {
> 	base_layer = server_layer;
> 	// TODO: inverse boolean logic
> 	if (!walk_and_check(client, &client_walker,
> 				   base_layer, ipc_type))
> 		return false;
> } else (client_layer < server_layer) {
> 	base_layer = client_layer;
> 	// TODO: inverse boolean logic
> 	if (!walk_and_check(server, &server_walker,
> 				   base_layer, 0))
> 		return false;
> } else {
> 	base_layer = client_layer;
> }
> 
> 
> I think we can improve more to make sure there is no path/risk of
> inconsistent pointers.
> 
> 
> > +	/* client and server are at the same level in hierarchy. If client is
> > +	 * scoped, the server must be scoped in the same domain
> > +	 */
> > +	for (base_layer; base_layer >= 0; base_layer--) {
> > +		if (landlock_get_scope_mask(client, base_layer) & ipc_type) {
> 
> With each multi-line comment, the first line should be empty:
> /*
>  * This check must be here since access would be denied only if
> 
> > +			/* This check must be here since access would be denied only if
> > +			 * the client is scoped and the server has no domain, so
> > +			 * if the client has a domain but is not scoped and the server
> > +			 * has no domain, access is guaranteed.
> > +			 */
> > +			if (!server)
> > +				return false;
> > +
> > +			if (server_walker == client_walker)
> > +				return true;
> > +
> > +			return false;
> > +		}
> > +		client_walker = client_walker->parent;
> > +		server_walker = server_walker->parent;
> > +		/* Warn if there is an incosistenncy between num_layers and
> 
> Makes sure there is no inconsistency between num_layers and
> 
> 
> > +		 * landlock_hierarchy in each of rulesets
> > +		 */
> > +		if (WARN_ON_ONCE(base_layer > 0 &&
> > +				 (!server_walker || !client_walker)))
> > +			return false;
> > +	}
> > +	return true;
> > +}

Here is a refactoring that is easier to read and avoid potential pointer
misuse:

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;

	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));

	if (!server) {
		/*
		 * Walks client's parent domains and checks that none of these
		 * domains are scoped.
		 */
		for (; client_layer >= 0; client_layer--) {
			if (landlock_get_scope_mask(client, client_layer) &
			    scope)
				return true;
		}
		return false;
	}

	server_layer = server->num_layers - 1;
	server_walker = server->hierarchy;

	/*
	 * 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.
			 */
			if (server_walker == client_walker)
				return false;

			return true;
		}
		client_walker = client_walker->parent;
		server_walker = server_walker->parent;
	}
	return false;
}

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
  2024-08-02 16:47   ` Mickaël Salaün
  2024-08-03 11:29   ` Mickaël Salaün
@ 2024-08-06 19:36   ` Jann Horn
  2 siblings, 0 replies; 23+ messages in thread
From: Jann Horn @ 2024-08-06 19:36 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, mic, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Fri, Aug 2, 2024 at 6:03 AM Tahera Fahimi <fahimitahera@gmail.com> wrote:
> This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> abstract Unix sockets from connecting to a process outside of
> the same landlock domain. It implements two hooks, unix_stream_connect
> and unix_may_send to enforce this restriction.
[...]
> +static bool check_unix_address_format(struct sock *const sock)
> +{
> +       struct unix_address *addr = unix_sk(sock)->addr;
> +
> +       if (!addr)
> +               return true;
> +
> +       if (addr->len > sizeof(AF_UNIX)) {
> +               /* handling unspec sockets */
> +               if (!addr->name[0].sun_path)
> +                       return true;

addr->name[0] is a "struct sockaddr_un", whose member "sun_path" is an
array member, not a pointer. If "addr" is a valid pointer,
"addr->name[0].sun_path" can't be NULL.


> +               if (addr->name[0].sun_path[0] == '\0')
> +                       if (!sock_is_scoped(sock))
> +                               return false;
> +       }
> +
> +       return true;
> +}

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-06 19:35     ` Mickaël Salaün
@ 2024-08-06 20:46       ` Jann Horn
  2024-08-07  7:21         ` Mickaël Salaün
  0 siblings, 1 reply; 23+ messages in thread
From: Jann Horn @ 2024-08-06 20:46 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tahera Fahimi, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Tue, Aug 6, 2024 at 9:36 PM Mickaël Salaün <mic@digikod.net> wrote:
> On Sat, Aug 03, 2024 at 01:29:09PM +0200, Mickaël Salaün wrote:
> > On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> > > This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> > > that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> > > abstract Unix sockets from connecting to a process outside of
> > > the same landlock domain. It implements two hooks, unix_stream_connect
> > > and unix_may_send to enforce this restriction.
[...]
> Here is a refactoring that is easier to read and avoid potential pointer
> misuse:
>
> 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;
>
>         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));
>
>         if (!server) {
>                 /*
>                  * Walks client's parent domains and checks that none of these
>                  * domains are scoped.
>                  */
>                 for (; client_layer >= 0; client_layer--) {
>                         if (landlock_get_scope_mask(client, client_layer) &
>                             scope)
>                                 return true;
>                 }
>                 return false;
>         }
>
>         server_layer = server->num_layers - 1;
>         server_walker = server->hierarchy;
>
>         /*
>          * 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.
>                          */
>                         if (server_walker == client_walker)
>                                 return false;
>
>                         return true;
>                 }
>                 client_walker = client_walker->parent;
>                 server_walker = server_walker->parent;
>         }
>         return false;
> }

I think adding something like this change on top of your code would
make it more concise (though this is entirely untested):

--- /tmp/a      2024-08-06 22:37:33.800158308 +0200
+++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
@@ -15,25 +15,12 @@
          * 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));

-        if (!server) {
-                /*
-                 * Walks client's parent domains and checks that none of these
-                 * domains are scoped.
-                 */
-                for (; client_layer >= 0; client_layer--) {
-                        if (landlock_get_scope_mask(client, client_layer) &
-                            scope)
-                                return true;
-                }
-                return false;
-        }
-
-        server_layer = server->num_layers - 1;
-        server_walker = server->hierarchy;
+        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.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-06 20:46       ` Jann Horn
@ 2024-08-07  7:21         ` Mickaël Salaün
  2024-08-07 13:45           ` Jann Horn
  0 siblings, 1 reply; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-07  7:21 UTC (permalink / raw)
  To: Jann Horn
  Cc: Tahera Fahimi, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Tue, Aug 06, 2024 at 10:46:43PM +0200, Jann Horn wrote:
> On Tue, Aug 6, 2024 at 9:36 PM Mickaël Salaün <mic@digikod.net> wrote:
> > On Sat, Aug 03, 2024 at 01:29:09PM +0200, Mickaël Salaün wrote:
> > > On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> > > > This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> > > > that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> > > > abstract Unix sockets from connecting to a process outside of
> > > > the same landlock domain. It implements two hooks, unix_stream_connect
> > > > and unix_may_send to enforce this restriction.
> [...]
> > Here is a refactoring that is easier to read and avoid potential pointer
> > misuse:
> >
> > 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;
> >
> >         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));
> >
> >         if (!server) {
> >                 /*
> >                  * Walks client's parent domains and checks that none of these
> >                  * domains are scoped.
> >                  */
> >                 for (; client_layer >= 0; client_layer--) {
> >                         if (landlock_get_scope_mask(client, client_layer) &
> >                             scope)
> >                                 return true;
> >                 }
> >                 return false;
> >         }
> >
> >         server_layer = server->num_layers - 1;
> >         server_walker = server->hierarchy;
> >
> >         /*
> >          * 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.
> >                          */

We can move this comment before the if and...

> >                         if (server_walker == client_walker)
> >                                 return false;
> >
> >                         return true;

...add this without the curly braces:

return server_walker != client_walker;

> >                 }
> >                 client_walker = client_walker->parent;
> >                 server_walker = server_walker->parent;
> >         }
> >         return false;
> > }
> 
> I think adding something like this change on top of your code would
> make it more concise (though this is entirely untested):
> 
> --- /tmp/a      2024-08-06 22:37:33.800158308 +0200
> +++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
> @@ -15,25 +15,12 @@
>           * 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));
> 
> -        if (!server) {
> -                /*
> -                 * Walks client's parent domains and checks that none of these
> -                 * domains are scoped.
> -                 */
> -                for (; client_layer >= 0; client_layer--) {
> -                        if (landlock_get_scope_mask(client, client_layer) &
> -                            scope)
> -                                return true;
> -                }
> -                return false;
> -        }

This loop is redundant with the following one, but it makes sure there
is no issue nor inconsistencies with the server or server_walker
pointers.  That's the only approach I found to make sure we don't go
through a path that could use an incorrect pointer, and makes the code
easy to review.

> -
> -        server_layer = server->num_layers - 1;
> -        server_walker = server->hierarchy;
> +        server_layer = server ? (server->num_layers - 1) : -1;
> +        server_walker = server ? server->hierarchy : NULL;

We would need to change the last loop to avoid a null pointer deref.

> 
>          /*
>           * 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.
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-07  7:21         ` Mickaël Salaün
@ 2024-08-07 13:45           ` Jann Horn
  2024-08-07 14:44             ` Mickaël Salaün
  0 siblings, 1 reply; 23+ messages in thread
From: Jann Horn @ 2024-08-07 13:45 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Tahera Fahimi, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Wed, Aug 7, 2024 at 9:21 AM Mickaël Salaün <mic@digikod.net> wrote:
> On Tue, Aug 06, 2024 at 10:46:43PM +0200, Jann Horn wrote:
> > I think adding something like this change on top of your code would
> > make it more concise (though this is entirely untested):
> >
> > --- /tmp/a      2024-08-06 22:37:33.800158308 +0200
> > +++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
> > @@ -15,25 +15,12 @@
> >           * 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));
> >
> > -        if (!server) {
> > -                /*
> > -                 * Walks client's parent domains and checks that none of these
> > -                 * domains are scoped.
> > -                 */
> > -                for (; client_layer >= 0; client_layer--) {
> > -                        if (landlock_get_scope_mask(client, client_layer) &
> > -                            scope)
> > -                                return true;
> > -                }
> > -                return false;
> > -        }
>
> This loop is redundant with the following one, but it makes sure there
> is no issue nor inconsistencies with the server or server_walker
> pointers.  That's the only approach I found to make sure we don't go
> through a path that could use an incorrect pointer, and makes the code
> easy to review.

My view is that this is a duplication of logic for one particular
special case - after all, you can also end up walking up to the same
state (client_layer==-1, server_layer==-1, client_walker==NULL,
server_walker==NULL) with the loop at the bottom.

But I guess my preference for more concise code is kinda subjective -
if you prefer the more verbose version, I'm fine with that too.

> > -
> > -        server_layer = server->num_layers - 1;
> > -        server_walker = server->hierarchy;
> > +        server_layer = server ? (server->num_layers - 1) : -1;
> > +        server_walker = server ? server->hierarchy : NULL;
>
> We would need to change the last loop to avoid a null pointer deref.

Why? The first loop would either exit or walk the client_walker up
until client_layer is -1 and client_walker is NULL; the second loop
wouldn't do anything because the walkers are at the same layer; the
third loop's body wouldn't be executed because client_layer is -1.

The case where the server is not in any Landlock domain is just one
subcase of the more general case "client and server do not have a
common ancestor domain".

> >
> >          /*
> >           * 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.
> >

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-07 13:45           ` Jann Horn
@ 2024-08-07 14:44             ` Mickaël Salaün
  2024-08-08 23:17               ` Tahera Fahimi
  0 siblings, 1 reply; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-07 14:44 UTC (permalink / raw)
  To: Jann Horn
  Cc: Tahera Fahimi, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Wed, Aug 07, 2024 at 03:45:18PM +0200, Jann Horn wrote:
> On Wed, Aug 7, 2024 at 9:21 AM Mickaël Salaün <mic@digikod.net> wrote:
> > On Tue, Aug 06, 2024 at 10:46:43PM +0200, Jann Horn wrote:
> > > I think adding something like this change on top of your code would
> > > make it more concise (though this is entirely untested):
> > >
> > > --- /tmp/a      2024-08-06 22:37:33.800158308 +0200
> > > +++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
> > > @@ -15,25 +15,12 @@
> > >           * 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));
> > >
> > > -        if (!server) {
> > > -                /*
> > > -                 * Walks client's parent domains and checks that none of these
> > > -                 * domains are scoped.
> > > -                 */
> > > -                for (; client_layer >= 0; client_layer--) {
> > > -                        if (landlock_get_scope_mask(client, client_layer) &
> > > -                            scope)
> > > -                                return true;
> > > -                }
> > > -                return false;
> > > -        }
> >
> > This loop is redundant with the following one, but it makes sure there
> > is no issue nor inconsistencies with the server or server_walker
> > pointers.  That's the only approach I found to make sure we don't go
> > through a path that could use an incorrect pointer, and makes the code
> > easy to review.
> 
> My view is that this is a duplication of logic for one particular
> special case - after all, you can also end up walking up to the same
> state (client_layer==-1, server_layer==-1, client_walker==NULL,
> server_walker==NULL) with the loop at the bottom.

Indeed

> 
> But I guess my preference for more concise code is kinda subjective -
> if you prefer the more verbose version, I'm fine with that too.
> 
> > > -
> > > -        server_layer = server->num_layers - 1;
> > > -        server_walker = server->hierarchy;
> > > +        server_layer = server ? (server->num_layers - 1) : -1;
> > > +        server_walker = server ? server->hierarchy : NULL;
> >
> > We would need to change the last loop to avoid a null pointer deref.
> 
> Why? The first loop would either exit or walk the client_walker up
> until client_layer is -1 and client_walker is NULL; the second loop
> wouldn't do anything because the walkers are at the same layer; the
> third loop's body wouldn't be executed because client_layer is -1.

Correct, I missed that client_layer would always be greater than
server_layer (-1).

Tahera, could you please take Jann's proposal?


> 
> The case where the server is not in any Landlock domain is just one
> subcase of the more general case "client and server do not have a
> common ancestor domain".
> 
> > >
> > >          /*
> > >           * 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.
> > >
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 2/4] selftests/landlock: Abstract unix socket restriction tests
  2024-08-02  4:02 ` [PATCH v8 2/4] selftests/landlock: Abstract unix socket restriction tests Tahera Fahimi
@ 2024-08-07 15:08   ` Mickaël Salaün
  0 siblings, 0 replies; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-07 15:08 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Thu, Aug 01, 2024 at 10:02:34PM -0600, Tahera Fahimi wrote:
> The patch introduces Landlock ABI version 6 and has four types of tests:
> 1) unix_socket: base tests of the abstract socket scoping mechanism for a
>    landlocked process, same as the ptrace test.
> 2) optional_scoping: generates three processes with different domains and
>    tests if a process with a non-scoped domain can connect to other
>    processes.
> 3) unix_sock_special_cases: since the socket's creator credentials are used
>    for scoping sockets, this test examines the cases where the socket's
>    credentials are different from the process using it.
> 4) pathname_address_sockets: ensures that Unix sockets bound to a
>    null-terminated filesystem can still connect to a socket outside of
>    their scoped domain. This means that even if the domain is scoped with
>    LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, the socket can connect to a socket
>    outside the scoped domain.
> 
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> ---
> Changes in versions:
> V8:
> - Move tests to scoped_abstract_unix_test.c file.
> - To avoid potential conflicts among Unix socket names in different tests,
>   set_unix_address is added to common.h to set different sun_path for Unix sockets.
> - protocol_variant and service_fixture structures are also moved to common.h
> - Adding pathname_address_sockets to cover all types of address formats
>   for unix sockets, and moving remove_path() to common.h to reuse in this test.
> V7:
> - Introducing landlock ABI version 6.
> - Adding some edge test cases to optional_scoping test.
> - Using `enum` for different domains in optional_scoping tests.
> - Extend unix_sock_special_cases test cases for connected(SOCK_STREAM) sockets.
> - Modifying inline comments.
> V6:
> - Introducing optional_scoping test which ensures a sandboxed process with a
>   non-scoped domain can still connect to another abstract unix socket(either
>   sandboxed or non-sandboxed).
> - Introducing unix_sock_special_cases test which tests examines scenarios where
>   the connecting sockets have different domain than the process using them.
> V4:
> - Introducing unix_socket to evaluate the basic scoping mechanism for abstract
>   unix sockets.
> ---
>  tools/testing/selftests/landlock/base_test.c  |    2 +-
>  tools/testing/selftests/landlock/common.h     |   72 ++
>  tools/testing/selftests/landlock/fs_test.c    |   34 -
>  tools/testing/selftests/landlock/net_test.c   |   31 +-
>  .../landlock/scoped_abstract_unix_test.c      | 1136 +++++++++++++++++
>  5 files changed, 1210 insertions(+), 65 deletions(-)
>  create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c

> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index 3c1e9f35b531..52b00472a487 100644
> --- a/tools/testing/selftests/landlock/base_test.c
> +++ b/tools/testing/selftests/landlock/base_test.c
> @@ -75,7 +75,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,
> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
> index 7e2b431b9f90..a0c8126d94b4 100644
> --- a/tools/testing/selftests/landlock/common.h
> +++ b/tools/testing/selftests/landlock/common.h
> @@ -16,8 +16,11 @@
>  #include <sys/types.h>
>  #include <sys/wait.h>
>  #include <unistd.h>
> +#include <sys/un.h>
> +#include <arpa/inet.h>
>  
>  #include "../kselftest_harness.h"
> +#define TMP_DIR "tmp"
>  
>  #ifndef __maybe_unused
>  #define __maybe_unused __attribute__((__unused__))
> @@ -226,3 +229,72 @@ 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';
> +}
> +
> +static int __maybe_unused remove_path(const char *const path)

We don't need to import remove_path() for these simple filesystem
actions.

> +{
> +	char *walker;
> +	int i, ret, err = 0;
> +
> +	walker = strdup(path);
> +	if (!walker) {
> +		err = ENOMEM;
> +		goto out;
> +	}
> +	if (unlink(path) && rmdir(path)) {
> +		if (errno != ENOENT && errno != ENOTDIR)
> +			err = errno;
> +		goto out;
> +	}
> +	for (i = strlen(walker); i > 0; i--) {
> +		if (walker[i] != '/')
> +			continue;
> +		walker[i] = '\0';
> +		ret = rmdir(walker);
> +		if (ret) {
> +			if (errno != ENOTEMPTY && errno != EBUSY)
> +				err = errno;
> +			goto out;
> +		}
> +		if (strcmp(walker, TMP_DIR) == 0)
> +			goto out;
> +	}
> +
> +out:
> +	free(walker);
> +	return err;
> +}
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 7d063c652be1..fc74cab6dfb8 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -221,40 +221,6 @@ static void create_file(struct __test_metadata *const _metadata,
>  	}
>  }
>  
> -static int remove_path(const char *const path)
> -{
> -	char *walker;
> -	int i, ret, err = 0;
> -
> -	walker = strdup(path);
> -	if (!walker) {
> -		err = ENOMEM;
> -		goto out;
> -	}
> -	if (unlink(path) && rmdir(path)) {
> -		if (errno != ENOENT && errno != ENOTDIR)
> -			err = errno;
> -		goto out;
> -	}
> -	for (i = strlen(walker); i > 0; i--) {
> -		if (walker[i] != '/')
> -			continue;
> -		walker[i] = '\0';
> -		ret = rmdir(walker);
> -		if (ret) {
> -			if (errno != ENOTEMPTY && errno != EBUSY)
> -				err = errno;
> -			goto out;
> -		}
> -		if (strcmp(walker, TMP_DIR) == 0)
> -			goto out;
> -	}
> -
> -out:
> -	free(walker);
> -	return err;
> -}
> -
>  struct mnt_opt {
>  	const char *const source;
>  	const char *const type;
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index f21cfbbc3638..4e0aeb53b225 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -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);

Please create a standalone patch for existing code refactoring, and then
another patch for the new tests.

>  		return 0;
>  	}
>  	return 1;
> diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
> new file mode 100644
> index 000000000000..ffa1f01dbbce
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c

> +TEST_F(unix_socket, abstract_unix_socket)
> +{
> +	int status;
> +	pid_t child;
> +	bool can_connect_to_parent, can_connect_to_child;
> +	int err, err_dgram;
> +	int pipe_child[2], pipe_parent[2];
> +	char buf_parent;
> +
> +	/*
> +	 * can_connect_to_child is true if a parent process can connect to its
> +	 * child process. The parent process is not isolated from the child
> +	 * with a dedicated Landlock domain.
> +	 */
> +	can_connect_to_child = !variant->domain_parent;
> +	/*
> +	 * can_connect_to_parent is true if a child process can connect to its
> +	 * parent process. This depends on the child process is not isolated from
> +	 * the parent with a dedicated Landlock domain.
> +	 */
> +	can_connect_to_parent = !variant->domain_child;
> +
> +	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> +	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> +	if (variant->domain_both) {
> +		create_unix_domain(_metadata);
> +		if (!__test_passed(_metadata))
> +			return;
> +	}
> +
> +	child = fork();
> +	ASSERT_LE(0, child);
> +	if (child == 0) {
> +		char buf_child;
> +
> +		ASSERT_EQ(0, close(pipe_parent[1]));
> +		ASSERT_EQ(0, close(pipe_child[0]));
> +		if (variant->domain_child)
> +			create_unix_domain(_metadata);
> +
> +		/* Waits for the parent to be in a domain, if any. */
> +		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> +
> +		if (variant->connect_to_parent) {
> +			self->client = socket(AF_UNIX, SOCK_STREAM, 0);
> +			self->dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
> +
> +			ASSERT_NE(-1, self->client);
> +			ASSERT_NE(-1, self->dgram_client);
> +			ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> +
> +			err = connect(self->client,
> +				      &self->stream_address.unix_addr,
> +				      (self->stream_address).unix_addr_len);
> +			err_dgram =
> +				connect(self->dgram_client,
> +					&self->dgram_address.unix_addr,
> +					(self->dgram_address).unix_addr_len);
> +
> +			if (can_connect_to_parent) {
> +				EXPECT_EQ(0, err);
> +				EXPECT_EQ(0, err_dgram);
> +			} else {
> +				EXPECT_EQ(-1, err);
> +				EXPECT_EQ(-1, err_dgram);
> +				EXPECT_EQ(EPERM, errno);
> +			}
> +		} else {
> +			self->server = socket(AF_UNIX, SOCK_STREAM, 0);
> +			self->dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
> +			ASSERT_NE(-1, self->server);
> +			ASSERT_NE(-1, self->dgram_server);
> +
> +			ASSERT_EQ(0,
> +				  bind(self->server,
> +				       &self->stream_address.unix_addr,
> +				       (self->stream_address).unix_addr_len));
> +			ASSERT_EQ(0, bind(self->dgram_server,
> +					  &self->dgram_address.unix_addr,
> +					  (self->dgram_address).unix_addr_len));
> +			ASSERT_EQ(0, listen(self->server, 32));

Please, avoid the use of hardcoded values (e.g. in all listen calls).
You can create a global backlog variable like in net_test.c (just copy
it, no need to include it in common.h).

> +
> +			/* signal to parent that child is listening */
> +			ASSERT_EQ(1, write(pipe_child[1], ".", 1));
> +			/* wait to connect */
> +			ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
> +		}
> +		_exit(_metadata->exit_code);
> +		return;
> +	}
> +
> +	ASSERT_EQ(0, close(pipe_child[1]));
> +	ASSERT_EQ(0, close(pipe_parent[0]));
> +
> +	if (variant->domain_parent)
> +		create_unix_domain(_metadata);
> +
> +	/* Signals that the parent is in a domain, if any. */
> +	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> +
> +	if (!variant->connect_to_parent) {
> +		self->client = socket(AF_UNIX, SOCK_STREAM, 0);
> +		self->dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0);
> +
> +		ASSERT_NE(-1, self->client);
> +		ASSERT_NE(-1, self->dgram_client);
> +
> +		/* Waits for the child to listen */
> +		ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
> +		err = connect(self->client, &self->stream_address.unix_addr,
> +			      (self->stream_address).unix_addr_len);
> +		err_dgram = connect(self->dgram_client,
> +				    &self->dgram_address.unix_addr,
> +				    (self->dgram_address).unix_addr_len);
> +
> +		if (can_connect_to_child) {
> +			EXPECT_EQ(0, err);
> +			EXPECT_EQ(0, err_dgram);
> +		} else {
> +			EXPECT_EQ(-1, err);
> +			EXPECT_EQ(-1, err_dgram);
> +			EXPECT_EQ(EPERM, errno);
> +		}
> +		ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> +	} else {
> +		self->server = socket(AF_UNIX, SOCK_STREAM, 0);
> +		self->dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0);
> +		ASSERT_NE(-1, self->server);
> +		ASSERT_NE(-1, self->dgram_server);
> +		ASSERT_EQ(0, bind(self->server, &self->stream_address.unix_addr,
> +				  (self->stream_address).unix_addr_len));
> +		ASSERT_EQ(0, bind(self->dgram_server,
> +				  &self->dgram_address.unix_addr,
> +				  (self->dgram_address).unix_addr_len));
> +		ASSERT_EQ(0, listen(self->server, 32));
> +
> +		/* signal to child that parent is listening */
> +		ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> +	}
> +
> +	ASSERT_EQ(child, waitpid(child, &status, 0));
> +
> +	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
> +	    WEXITSTATUS(status) != EXIT_SUCCESS)
> +		_metadata->exit_code = KSFT_FAIL;
> +}
> +
> +enum sandbox_type {
> +	NO_SANDBOX,
> +	SCOPE_SANDBOX,
> +	/* Any other type of sandboxing domain */
> +	OTHER_SANDBOX,
> +};
> +
> +/* clang-format off */
> +FIXTURE(optional_scoping)
> +{
> +	struct service_fixture parent_address, child_address;

> +	int parent_server, child_server, client;

The variables should be local ones.

> +};
> +
> +/* clang-format on */
> +FIXTURE_VARIANT(optional_scoping)
> +{
> +	const int domain_all;
> +	const int domain_parent;
> +	const int domain_children;
> +	const int domain_child;

> +	const int domain_grand_child;

domain_grand_child is always NO_SANDBOX in the variants.


> +	const int type;
> +};
> +
> +FIXTURE_SETUP(optional_scoping)

s/optional_scoping/scoping/ ?

> +{
> +	memset(&self->parent_address, 0, sizeof(self->parent_address));
> +	memset(&self->child_address, 0, sizeof(self->child_address));
> +
> +	set_unix_address(&self->parent_address, 0);
> +	set_unix_address(&self->child_address, 1);
> +}

> +TEST_F(optional_scoping, unix_scoping)
> +{
> +	pid_t child;
> +	int status;
> +	bool can_connect_to_parent, can_connect_to_child;
> +	int pipe_parent[2];
> +
> +	if (variant->domain_grand_child == SCOPE_SANDBOX)
> +		can_connect_to_child = false;

We never go through this case.

> +	else
> +		can_connect_to_child = true;
> +
> +	if (!can_connect_to_child || variant->domain_children == SCOPE_SANDBOX)
> +		can_connect_to_parent = false;
> +	else
> +		can_connect_to_parent = true;

We never go through this case.

> +
> +	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
> +
> +	if (variant->domain_all == OTHER_SANDBOX)
> +		create_fs_domain(_metadata);
> +	else if (variant->domain_all == SCOPE_SANDBOX)
> +		create_unix_domain(_metadata);
> +
> +	child = fork();
> +	ASSERT_LE(0, child);
> +	if (child == 0) {
> +		int pipe_child[2];
> +
> +		ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
> +		pid_t grand_child;
> +
> +		if (variant->domain_children == OTHER_SANDBOX)
> +			create_fs_domain(_metadata);
> +		else if (variant->domain_children == SCOPE_SANDBOX)
> +			create_unix_domain(_metadata);
> +
> +		grand_child = fork();
> +		ASSERT_LE(0, grand_child);
> +		if (grand_child == 0) {
> +			ASSERT_EQ(0, close(pipe_parent[1]));
> +			ASSERT_EQ(0, close(pipe_child[1]));
> +
> +			char buf1, buf2;
> +			int err;
> +
> +			if (variant->domain_grand_child == OTHER_SANDBOX)
> +				create_fs_domain(_metadata);
> +			else if (variant->domain_grand_child == SCOPE_SANDBOX)
> +				create_unix_domain(_metadata);
> +
> +			self->client = socket(AF_UNIX, variant->type, 0);
> +			ASSERT_NE(-1, self->client);
> +
> +			ASSERT_EQ(1, read(pipe_child[0], &buf2, 1));
> +			err = connect(self->client,
> +				      &self->child_address.unix_addr,

> +				      (self->child_address).unix_addr_len);

No need for these extra parenthesis.

> +			if (can_connect_to_child) {
> +				EXPECT_EQ(0, err);
> +			} else {
> +				EXPECT_EQ(-1, err);
> +				EXPECT_EQ(EPERM, errno);
> +			}
> +
> +			if (variant->type == SOCK_STREAM) {
> +				EXPECT_EQ(0, close(self->client));
> +				self->client =
> +					socket(AF_UNIX, variant->type, 0);
> +				ASSERT_NE(-1, self->client);
> +			}
> +
> +			ASSERT_EQ(1, read(pipe_parent[0], &buf1, 1));
> +			err = connect(self->client,
> +				      &self->parent_address.unix_addr,
> +				      (self->parent_address).unix_addr_len);
> +			if (can_connect_to_parent) {
> +				EXPECT_EQ(0, err);
> +			} else {
> +				EXPECT_EQ(-1, err);
> +				EXPECT_EQ(EPERM, errno);
> +			}
> +			EXPECT_EQ(0, close(self->client));
> +
> +			_exit(_metadata->exit_code);
> +			return;
> +		}
> +
> +		ASSERT_EQ(0, close(pipe_child[0]));
> +		if (variant->domain_child == OTHER_SANDBOX)
> +			create_fs_domain(_metadata);
> +		else if (variant->domain_child == SCOPE_SANDBOX)
> +			create_unix_domain(_metadata);
> +
> +		self->child_server = socket(AF_UNIX, variant->type, 0);
> +		ASSERT_NE(-1, self->child_server);
> +		ASSERT_EQ(0, bind(self->child_server,
> +				  &self->child_address.unix_addr,
> +				  (self->child_address).unix_addr_len));
> +		if (variant->type == SOCK_STREAM)
> +			ASSERT_EQ(0, listen(self->child_server, 32));
> +
> +		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
> +		ASSERT_EQ(grand_child, waitpid(grand_child, &status, 0));
> +		return;
> +	}
> +	ASSERT_EQ(0, close(pipe_parent[0]));
> +
> +	if (variant->domain_parent == OTHER_SANDBOX)
> +		create_fs_domain(_metadata);
> +	else if (variant->domain_parent == SCOPE_SANDBOX)
> +		create_unix_domain(_metadata);
> +
> +	self->parent_server = socket(AF_UNIX, variant->type, 0);
> +	ASSERT_NE(-1, self->parent_server);
> +	ASSERT_EQ(0, bind(self->parent_server, &self->parent_address.unix_addr,
> +			  (self->parent_address).unix_addr_len));
> +
> +	if (variant->type == SOCK_STREAM)
> +		ASSERT_EQ(0, listen(self->parent_server, 32));
> +
> +	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
> +	ASSERT_EQ(child, waitpid(child, &status, 0));
> +	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
> +	    WEXITSTATUS(status) != EXIT_SUCCESS)
> +		_metadata->exit_code = KSFT_FAIL;
> +}

> +FIXTURE_SETUP(pathname_address_sockets)
> +{
> +	/* setup abstract addresses */
> +	memset(&self->stream_address, 0, sizeof(self->stream_address));
> +	set_unix_address(&self->stream_address, 0);
> +
> +	memset(&self->dgram_address, 0, sizeof(self->dgram_address));
> +	set_unix_address(&self->dgram_address, 0);
> +
> +	const char *s_path = variant->stream_path;
> +	const char *dg_path = variant->dgram_path;
> +
> +	disable_caps(_metadata);
> +	umask(0077);
> +	ASSERT_EQ(0, mkdir(TMP_DIR, 0700));
> +
> +	ASSERT_EQ(0, mknod(s_path, S_IFREG | 0700, 0))
> +	{
> +		TH_LOG("Failed to create file \"%s\": %s", s_path,
> +		       strerror(errno));
> +		remove_path(TMP_DIR);

Just use rmdir() instead of remove_path() everywhere.

> +	}
> +	ASSERT_EQ(0, mknod(dg_path, S_IFREG | 0700, 0))
> +	{
> +		TH_LOG("Failed to create file \"%s\": %s", dg_path,
> +		       strerror(errno));
> +		remove_path(TMP_DIR);
> +	}
> +}
> +
> +FIXTURE_TEARDOWN(pathname_address_sockets)
> +{
> +	const char *s_path = variant->stream_path;
> +	const char *dg_path = variant->dgram_path;
> +
> +	ASSERT_EQ(0, remove_path(dg_path));

Just use unlink() and rmdir().

> +	ASSERT_EQ(0, remove_path(s_path));
> +	ASSERT_EQ(0, remove_path(TMP_DIR));
> +}


^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 4/4] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning
  2024-08-02  4:02 ` [PATCH v8 4/4] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
@ 2024-08-07 15:14   ` Mickaël Salaün
  0 siblings, 0 replies; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-07 15:14 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Thu, Aug 01, 2024 at 10:02:36PM -0600, Tahera Fahimi wrote:
> Introducing LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET as an IPC scoping
> mechanism in Landlock ABI version 6, and updating ruleset_attr,
> Landlock ABI version, and access rights code blocks based on that.
> 
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> ---
> v8:
> - Improving documentation by specifying differences between scoped and
>   non-scoped domains.
> - Adding review notes of version 7.
> - Update date
> v7:
> - Add "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" explanation to IPC scoping
>   section and updating ABI to version 6.
> - Adding "scoped" attribute to the Access rights section.
> - In current limitation, unnamed sockets are specified as sockets that
>   are not restricted.
> - Update date
> ---
>  Documentation/userspace-api/landlock.rst | 33 ++++++++++++++++++++++--
>  1 file changed, 31 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index 07b63aec56fa..d602567b5139 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -8,7 +8,7 @@ Landlock: unprivileged access control
>  =====================================
>  
>  :Author: Mickaël Salaün
> -:Date: April 2024
> +:Date: August 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_SCOPED_ABSTRACT_UNIX_SOCKET,
>      };
>  
>  Because we may not know on which kernel version an application will be
> @@ -119,6 +121,9 @@ 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;
> +    case 5:
> +        /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
> +        ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
>      }
>  
>  This enables to create an inclusive ruleset that will contain our rules.
> @@ -306,6 +311,23 @@ 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_SCOPED_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 processes in

...it can only connect to sockets created by proccesses in the same
scoped domain.

> +the same scoped domain.
> +
> +IPC scoping does not support Landlock rules, so if a domain is scoped, no rules
> +can be added to allow accessing to a resource outside of the scoped domain.
> +
>  Truncating files
>  ----------------
>  
> @@ -404,7 +426,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 +563,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 sockets Restriction  (ABI < 6)

Let's follow the capitalization used by man pages: "UNIX" instead of
"Unix".

> +--------------------------------------------
> +
> +With ABI version 6, it is possible to restrict connection to an abstract Unix socket
> +through ``LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET``, thanks to the ``scoped`` ruleset
> +attribute.
> +
>  .. _kernel_support:
>  
>  Kernel support
> -- 
> 2.34.1
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-03 11:29   ` Mickaël Salaün
  2024-08-06 19:35     ` Mickaël Salaün
@ 2024-08-07 15:37     ` Tahera Fahimi
  2024-08-09 14:13       ` Mickaël Salaün
  1 sibling, 1 reply; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-07 15:37 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Sat, Aug 03, 2024 at 01:29:04PM +0200, Mickaël Salaün wrote:
> On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> > This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> > that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> > abstract Unix sockets from connecting to a process outside of
> > the same landlock domain. It implements two hooks, unix_stream_connect
> > and unix_may_send to enforce this restriction.
> > 
> > Closes: https://github.com/landlock-lsm/linux/issues/7
> > Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> > 
> > ---
> > v8:
> > - Code refactoring (improve code readability, renaming variable, etc.) based
> >   on reviews by Mickaël Salaün on version 7.
> > - Adding warn_on_once to check (impossible) inconsistencies.
> > - Adding inline comments.
> > - Adding check_unix_address_format to check if the scoping socket is an abstract
> >   unix sockets.
> > v7:
> >  - Using socket's file credentials for both connected(STREAM) and
> >    non-connected(DGRAM) sockets.
> >  - Adding "domain_sock_scope" instead of the domain scoping mechanism used in
> >    ptrace ensures that if a server's domain is accessible from the client's
> >    domain (where the client is more privileged than the server), the client
> >    can connect to the server in all edge cases.
> >  - Removing debug codes.
> > v6:
> >  - Removing curr_ruleset from landlock_hierarchy, and switching back to use
> >    the same domain scoping as ptrace.
> >  - code clean up.
> > v5:
> >  - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
> >  - Adding curr_ruleset to hierarachy_ruleset structure to have access from
> >    landlock_hierarchy to its respective landlock_ruleset.
> >  - Using curr_ruleset to check if a domain is scoped while walking in the
> >    hierarchy of domains.
> >  - Modifying inline comments.
> > V4:
> >  - Rebased on Günther's Patch:
> >    https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
> >    so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
> >  - Adding get_scope_accesses function to check all scoped access masks in a ruleset.
> >  - Using socket's file credentials instead of credentials stored in peer_cred
> >    for datagram sockets. (see discussion in [1])
> >  - Modifying inline comments.
> > V3:
> >  - Improving commit description.
> >  - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
> >    purpose, and adding related functions.
> >  - Changing structure of ruleset based on "scoped".
> >  - Removing rcu lock and using unix_sk lock instead.
> >  - Introducing scoping for datagram sockets in unix_may_send.
> > V2:
> >  - Removing wrapper functions
> > 
> > [1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
> > ----
> > ---
> >  include/uapi/linux/landlock.h |  30 +++++++
> >  security/landlock/limits.h    |   3 +
> >  security/landlock/ruleset.c   |   7 +-
> >  security/landlock/ruleset.h   |  23 ++++-
> >  security/landlock/syscalls.c  |  14 ++-
> >  security/landlock/task.c      | 155 ++++++++++++++++++++++++++++++++++
> >  6 files changed, 225 insertions(+), 7 deletions(-)
> 
> > diff --git a/security/landlock/task.c b/security/landlock/task.c
> > index 849f5123610b..7e8579ebae83 100644
> > --- a/security/landlock/task.c
> > +++ b/security/landlock/task.c
> > @@ -13,6 +13,8 @@
> >  #include <linux/lsm_hooks.h>
> >  #include <linux/rcupdate.h>
> >  #include <linux/sched.h>
> > +#include <net/sock.h>
> > +#include <net/af_unix.h>
> >  
> >  #include "common.h"
> >  #include "cred.h"
> > @@ -108,9 +110,162 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
> >  	return task_ptrace(parent, current);
> >  }
> >  
> > +static bool walk_and_check(const struct landlock_ruleset *const child,
> > +			   struct landlock_hierarchy **walker,
> > +			   size_t base_layer, size_t deep_layer,
> > +			   access_mask_t check_scoping)
> 
> s/check_scoping/scope/
> 
> > +{
> > +	if (!child || base_layer < 0 || !(*walker))
> 
> I guess it should be:
> WARN_ON_ONCE(!child || base_layer < 0 || !(*walker))
> 
> > +		return false;
> > +
> > +	for (deep_layer; base_layer < deep_layer; deep_layer--) {
> 
> No need to pass deep_layer as argument:
> deep_layer = child->num_layers - 1
> 
> > +		if (check_scoping & landlock_get_scope_mask(child, deep_layer))
> > +			return false;
> > +		*walker = (*walker)->parent;
> > +		if (WARN_ON_ONCE(!*walker))
> > +			/* there is an inconsistency between num_layers
> 
> Please use full sentences starting with a capital letter and ending with
> a dot, and in this case start with "/*"
> 
> > +			 * and landlock_hierarchy in the ruleset
> > +			 */
> > +			return false;
> > +	}
> > +	return true;
> > +}
> > +
> > +/**
> > + * domain_IPC_scope - Checks if the client domain is scoped in the same
> > + *		      domain as the server.
> 
> Actually, you can remove IPC from the function name.
> 
> > + *
> > + * @client: IPC sender domain.
> > + * @server: IPC receiver domain.
> > + *
> > + * Check if the @client domain is scoped to access the @server; the @server
> > + * must be scoped in the same domain.
> 
> Returns true if...
> 
> > + */
> > +static bool domain_IPC_scope(const struct landlock_ruleset *const client,
> > +			     const struct landlock_ruleset *const server,
> > +			     access_mask_t ipc_type)
> > +{
> > +	size_t client_layer, server_layer = 0;
> > +	int base_layer;
> > +	struct landlock_hierarchy *client_walker, *server_walker;
> > +	bool is_scoped;
> > +
> > +	/* Quick return if client has no domain */
> > +	if (!client)
> > +		return true;
> > +
> > +	client_layer = client->num_layers - 1;
> > +	client_walker = client->hierarchy;
> > +	if (server) {
> > +		server_layer = server->num_layers - 1;
> > +		server_walker = server->hierarchy;
> > +	}
> 
> } else {
> 	server_layer = 0;
> 	server_walker = NULL;
> }
> 
> > +	base_layer = (client_layer > server_layer) ? server_layer :
> > +						     client_layer;
> > +
> > +	/* For client domain, walk_and_check ensures the client domain is
> > +	 * not scoped until gets to base_layer.
> 
> until gets?
> 
> > +	 * For server_domain, it only ensures that the server domain exist.
> > +	 */
> > +	if (client_layer != server_layer) {
> 
> bool is_scoped;
It is defined above. 
> > +		if (client_layer > server_layer)
> > +			is_scoped = walk_and_check(client, &client_walker,
> > +						   server_layer, client_layer,
> > +						   ipc_type);
> > +		else
> 
> server_walker may be uninitialized and still read here, and maybe later
> in the for loop.  The whole code should maks sure this cannot happen,
> and a test case should check this.
I think this case never happens, since the server_walker can be read
here if there are more than one layers in server domain which means that
the server_walker is not unintialized.

> > +			is_scoped = walk_and_check(server, &server_walker,
> > +						   client_layer, server_layer,
> > +						   ipc_type & 0);
> 
> "ipc_type & 0" is the same as "0"
> 
> > +		if (!is_scoped)
> 
> The name doesn't reflect the semantic. walk_and_check() should return
> the inverse.
> 
> > +			return false;
> > +	}
> 
> This code would be simpler:
> 
> if (client_layer > server_layer) {
> 	base_layer = server_layer;
> 	// TODO: inverse boolean logic
> 	if (!walk_and_check(client, &client_walker,
> 				   base_layer, ipc_type))
> 		return false;
> } else (client_layer < server_layer) {
> 	base_layer = client_layer;
> 	// TODO: inverse boolean logic
> 	if (!walk_and_check(server, &server_walker,
> 				   base_layer, 0))
> 		return false;
> } else {
> 	base_layer = client_layer;
> }
> 
> 
> I think we can improve more to make sure there is no path/risk of
> inconsistent pointers.
> 
> 
> > +	/* client and server are at the same level in hierarchy. If client is
> > +	 * scoped, the server must be scoped in the same domain
> > +	 */
> > +	for (base_layer; base_layer >= 0; base_layer--) {
> > +		if (landlock_get_scope_mask(client, base_layer) & ipc_type) {
> 
> With each multi-line comment, the first line should be empty:
> /*
>  * This check must be here since access would be denied only if
> 
> > +			/* This check must be here since access would be denied only if
> > +			 * the client is scoped and the server has no domain, so
> > +			 * if the client has a domain but is not scoped and the server
> > +			 * has no domain, access is guaranteed.
> > +			 */
> > +			if (!server)
> > +				return false;
> > +
> > +			if (server_walker == client_walker)
> > +				return true;
> > +
> > +			return false;
> > +		}
> > +		client_walker = client_walker->parent;
> > +		server_walker = server_walker->parent;
> > +		/* Warn if there is an incosistenncy between num_layers and
> 
> Makes sure there is no inconsistency between num_layers and
> 
> 
> > +		 * landlock_hierarchy in each of rulesets
> > +		 */
> > +		if (WARN_ON_ONCE(base_layer > 0 &&
> > +				 (!server_walker || !client_walker)))
> > +			return false;
> > +	}
> > +	return true;
> > +}

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-07 14:44             ` Mickaël Salaün
@ 2024-08-08 23:17               ` Tahera Fahimi
  2024-08-09  8:49                 ` Mickaël Salaün
  0 siblings, 1 reply; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-08 23:17 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Jann Horn, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Wed, Aug 07, 2024 at 04:44:36PM +0200, Mickaël Salaün wrote:
> On Wed, Aug 07, 2024 at 03:45:18PM +0200, Jann Horn wrote:
> > On Wed, Aug 7, 2024 at 9:21 AM Mickaël Salaün <mic@digikod.net> wrote:
> > > On Tue, Aug 06, 2024 at 10:46:43PM +0200, Jann Horn wrote:
> > > > I think adding something like this change on top of your code would
> > > > make it more concise (though this is entirely untested):
> > > >
> > > > --- /tmp/a      2024-08-06 22:37:33.800158308 +0200
> > > > +++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
> > > > @@ -15,25 +15,12 @@
> > > >           * 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));
> > > >
> > > > -        if (!server) {
> > > > -                /*
> > > > -                 * Walks client's parent domains and checks that none of these
> > > > -                 * domains are scoped.
> > > > -                 */
> > > > -                for (; client_layer >= 0; client_layer--) {
> > > > -                        if (landlock_get_scope_mask(client, client_layer) &
> > > > -                            scope)
> > > > -                                return true;
> > > > -                }
> > > > -                return false;
> > > > -        }
> > >
> > > This loop is redundant with the following one, but it makes sure there
> > > is no issue nor inconsistencies with the server or server_walker
> > > pointers.  That's the only approach I found to make sure we don't go
> > > through a path that could use an incorrect pointer, and makes the code
> > > easy to review.
> > 
> > My view is that this is a duplication of logic for one particular
> > special case - after all, you can also end up walking up to the same
> > state (client_layer==-1, server_layer==-1, client_walker==NULL,
> > server_walker==NULL) with the loop at the bottom.
> 
> Indeed
> 
> > 
> > But I guess my preference for more concise code is kinda subjective -
> > if you prefer the more verbose version, I'm fine with that too.
> > 
> > > > -
> > > > -        server_layer = server->num_layers - 1;
> > > > -        server_walker = server->hierarchy;
> > > > +        server_layer = server ? (server->num_layers - 1) : -1;
> > > > +        server_walker = server ? server->hierarchy : NULL;
> > >
> > > We would need to change the last loop to avoid a null pointer deref.
> > 
> > Why? The first loop would either exit or walk the client_walker up
> > until client_layer is -1 and client_walker is NULL; the second loop
> > wouldn't do anything because the walkers are at the same layer; the
> > third loop's body wouldn't be executed because client_layer is -1.
> 
> Correct, I missed that client_layer would always be greater than
> server_layer (-1).
> 
> Tahera, could you please take Jann's proposal?
Done.
We will have duplicate logic, but it would be easier to read and review.
> 
> > 
> > The case where the server is not in any Landlock domain is just one
> > subcase of the more general case "client and server do not have a
> > common ancestor domain".
> > 
> > > >
> > > >          /*
> > > >           * 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.
> > > >
> > 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-08 23:17               ` Tahera Fahimi
@ 2024-08-09  8:49                 ` Mickaël Salaün
  2024-08-09 17:54                   ` Tahera Fahimi
  0 siblings, 1 reply; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-09  8:49 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: Jann Horn, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Thu, Aug 08, 2024 at 05:17:10PM -0600, Tahera Fahimi wrote:
> On Wed, Aug 07, 2024 at 04:44:36PM +0200, Mickaël Salaün wrote:
> > On Wed, Aug 07, 2024 at 03:45:18PM +0200, Jann Horn wrote:
> > > On Wed, Aug 7, 2024 at 9:21 AM Mickaël Salaün <mic@digikod.net> wrote:
> > > > On Tue, Aug 06, 2024 at 10:46:43PM +0200, Jann Horn wrote:
> > > > > I think adding something like this change on top of your code would
> > > > > make it more concise (though this is entirely untested):
> > > > >
> > > > > --- /tmp/a      2024-08-06 22:37:33.800158308 +0200
> > > > > +++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
> > > > > @@ -15,25 +15,12 @@
> > > > >           * 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));
> > > > >
> > > > > -        if (!server) {
> > > > > -                /*
> > > > > -                 * Walks client's parent domains and checks that none of these
> > > > > -                 * domains are scoped.
> > > > > -                 */
> > > > > -                for (; client_layer >= 0; client_layer--) {
> > > > > -                        if (landlock_get_scope_mask(client, client_layer) &
> > > > > -                            scope)
> > > > > -                                return true;
> > > > > -                }
> > > > > -                return false;
> > > > > -        }
> > > >
> > > > This loop is redundant with the following one, but it makes sure there
> > > > is no issue nor inconsistencies with the server or server_walker
> > > > pointers.  That's the only approach I found to make sure we don't go
> > > > through a path that could use an incorrect pointer, and makes the code
> > > > easy to review.
> > > 
> > > My view is that this is a duplication of logic for one particular
> > > special case - after all, you can also end up walking up to the same
> > > state (client_layer==-1, server_layer==-1, client_walker==NULL,
> > > server_walker==NULL) with the loop at the bottom.
> > 
> > Indeed
> > 
> > > 
> > > But I guess my preference for more concise code is kinda subjective -
> > > if you prefer the more verbose version, I'm fine with that too.
> > > 
> > > > > -
> > > > > -        server_layer = server->num_layers - 1;
> > > > > -        server_walker = server->hierarchy;
> > > > > +        server_layer = server ? (server->num_layers - 1) : -1;
> > > > > +        server_walker = server ? server->hierarchy : NULL;
> > > >
> > > > We would need to change the last loop to avoid a null pointer deref.
> > > 
> > > Why? The first loop would either exit or walk the client_walker up
> > > until client_layer is -1 and client_walker is NULL; the second loop
> > > wouldn't do anything because the walkers are at the same layer; the
> > > third loop's body wouldn't be executed because client_layer is -1.
> > 
> > Correct, I missed that client_layer would always be greater than
> > server_layer (-1).
> > 
> > Tahera, could you please take Jann's proposal?
> Done.
> We will have duplicate logic, but it would be easier to read and review.

With Jann's proposal we don't have duplicate logic.

> > 
> > > 
> > > The case where the server is not in any Landlock domain is just one
> > > subcase of the more general case "client and server do not have a
> > > common ancestor domain".
> > > 
> > > > >
> > > > >          /*
> > > > >           * 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.
> > > > >
> > > 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction
  2024-08-02  4:02 ` [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
@ 2024-08-09 14:11   ` Mickaël Salaün
  2024-08-09 18:16     ` Tahera Fahimi
  0 siblings, 1 reply; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-09 14:11 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Thu, Aug 01, 2024 at 10:02:35PM -0600, Tahera Fahimi wrote:
> A sandboxer can receive the character "a" as input from the environment
> variable LL_SCOPE to restrict the abstract unix sockets from connecting
> to a process outside its scoped domain.
> 
> Example
> =======
> Create an abstract unix socket to listen with socat(1):
> socat abstract-listen:mysocket -
> Create a sandboxed shell and pass the character "a" to LL_SCOPED:
> LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
> If the sandboxed process tries to connect to the listening socket
> with command "socat - abstract-connect:mysocket", the connection
> will fail.
> 
> Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> 
> ---
> v8:
> - Adding check_ruleset_scope function to parse the scope environment
>   variable and update the landlock attribute based on the restriction
>   provided by the user.
> - Adding Mickaël Salaün reviews on version 7.
> 
> v7:
> - Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED"
>   environment variable. "LL_SCOPED" gets value "a" to restrict abstract
>   unix sockets.
> - Change LANDLOCK_ABI_LAST to 6.
> ---
>  samples/landlock/sandboxer.c | 56 +++++++++++++++++++++++++++++++++---
>  1 file changed, 52 insertions(+), 4 deletions(-)
> 
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index e8223c3e781a..98132fd823ad 100644
> --- a/samples/landlock/sandboxer.c
> +++ b/samples/landlock/sandboxer.c
> @@ -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 parse_path(char *env_path, const char ***const path_list)
> @@ -184,6 +187,38 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>  	return ret;
>  }
>  
> +static bool check_ruleset_scope(const char *const env_var,
> +				struct landlock_ruleset_attr *ruleset_attr)
> +{
> +	bool ret = true;
> +	char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
> +
> +	ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;

Why always removing the suported scope?
What happen if ABI < 6 ?

> +	env_type_scope = getenv(env_var);
> +	/* scoping is not supported by the user */
> +	if (!env_type_scope)
> +		return true;
> +	env_type_scope = strdup(env_type_scope);
> +	unsetenv(env_var);
> +
> +	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) {
> +			ruleset_attr->scoped |=
> +				LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;

There are two issues here:
1. this would not work for ABI < 6
2. "a" can be repeated several times, which should probably not be
   allowed because we don't want to support this
   unspecified/undocumented behavior.


> +		} else {
> +			fprintf(stderr, "Unsupported scoping \"%s\"\n",
> +				ipc_scoping_name);
> +			ret = false;
> +			goto out_free_name;
> +		}
> +	}
> +out_free_name:
> +	free(env_type_scope);
> +	return ret;
> +}
> +
>  /* clang-format off */
>  
>  #define ACCESS_FS_ROUGHLY_READ ( \
> @@ -208,7 +243,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>  
>  /* 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)
>  {
> @@ -223,14 +258,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_SCOPED_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,
> @@ -251,15 +287,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 restrictions on 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",
> @@ -327,6 +366,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_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
> +		ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
>  		fprintf(stderr,
>  			"Hint: You should update the running kernel "
>  			"to leverage Landlock features "
> @@ -358,6 +401,11 @@ 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)) {

You should use the same pattern as for TCP access rigths: if the
environment variable is not set then remove the ruleset's scopes.

> +		perror("Unsupported IPC scoping requested");
> +		return 1;
> +	}
> +
>  	ruleset_fd =
>  		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>  	if (ruleset_fd < 0) {
> -- 
> 2.34.1
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-07 15:37     ` Tahera Fahimi
@ 2024-08-09 14:13       ` Mickaël Salaün
  0 siblings, 0 replies; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-09 14:13 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Wed, Aug 07, 2024 at 09:37:15AM -0600, Tahera Fahimi wrote:
> On Sat, Aug 03, 2024 at 01:29:04PM +0200, Mickaël Salaün wrote:
> > On Thu, Aug 01, 2024 at 10:02:33PM -0600, Tahera Fahimi wrote:
> > > This patch introduces a new "scoped" attribute to the landlock_ruleset_attr
> > > that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope
> > > abstract Unix sockets from connecting to a process outside of
> > > the same landlock domain. It implements two hooks, unix_stream_connect
> > > and unix_may_send to enforce this restriction.
> > > 
> > > Closes: https://github.com/landlock-lsm/linux/issues/7
> > > Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> > > 
> > > ---
> > > v8:
> > > - Code refactoring (improve code readability, renaming variable, etc.) based
> > >   on reviews by Mickaël Salaün on version 7.
> > > - Adding warn_on_once to check (impossible) inconsistencies.
> > > - Adding inline comments.
> > > - Adding check_unix_address_format to check if the scoping socket is an abstract
> > >   unix sockets.
> > > v7:
> > >  - Using socket's file credentials for both connected(STREAM) and
> > >    non-connected(DGRAM) sockets.
> > >  - Adding "domain_sock_scope" instead of the domain scoping mechanism used in
> > >    ptrace ensures that if a server's domain is accessible from the client's
> > >    domain (where the client is more privileged than the server), the client
> > >    can connect to the server in all edge cases.
> > >  - Removing debug codes.
> > > v6:
> > >  - Removing curr_ruleset from landlock_hierarchy, and switching back to use
> > >    the same domain scoping as ptrace.
> > >  - code clean up.
> > > v5:
> > >  - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE"
> > >  - Adding curr_ruleset to hierarachy_ruleset structure to have access from
> > >    landlock_hierarchy to its respective landlock_ruleset.
> > >  - Using curr_ruleset to check if a domain is scoped while walking in the
> > >    hierarchy of domains.
> > >  - Modifying inline comments.
> > > V4:
> > >  - Rebased on Günther's Patch:
> > >    https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/
> > >    so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed.
> > >  - Adding get_scope_accesses function to check all scoped access masks in a ruleset.
> > >  - Using socket's file credentials instead of credentials stored in peer_cred
> > >    for datagram sockets. (see discussion in [1])
> > >  - Modifying inline comments.
> > > V3:
> > >  - Improving commit description.
> > >  - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping
> > >    purpose, and adding related functions.
> > >  - Changing structure of ruleset based on "scoped".
> > >  - Removing rcu lock and using unix_sk lock instead.
> > >  - Introducing scoping for datagram sockets in unix_may_send.
> > > V2:
> > >  - Removing wrapper functions
> > > 
> > > [1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/
> > > ----
> > > ---
> > >  include/uapi/linux/landlock.h |  30 +++++++
> > >  security/landlock/limits.h    |   3 +
> > >  security/landlock/ruleset.c   |   7 +-
> > >  security/landlock/ruleset.h   |  23 ++++-
> > >  security/landlock/syscalls.c  |  14 ++-
> > >  security/landlock/task.c      | 155 ++++++++++++++++++++++++++++++++++
> > >  6 files changed, 225 insertions(+), 7 deletions(-)
> > 
> > > diff --git a/security/landlock/task.c b/security/landlock/task.c
> > > index 849f5123610b..7e8579ebae83 100644
> > > --- a/security/landlock/task.c
> > > +++ b/security/landlock/task.c
> > > @@ -13,6 +13,8 @@
> > >  #include <linux/lsm_hooks.h>
> > >  #include <linux/rcupdate.h>
> > >  #include <linux/sched.h>
> > > +#include <net/sock.h>
> > > +#include <net/af_unix.h>
> > >  
> > >  #include "common.h"
> > >  #include "cred.h"
> > > @@ -108,9 +110,162 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
> > >  	return task_ptrace(parent, current);
> > >  }
> > >  
> > > +static bool walk_and_check(const struct landlock_ruleset *const child,
> > > +			   struct landlock_hierarchy **walker,
> > > +			   size_t base_layer, size_t deep_layer,
> > > +			   access_mask_t check_scoping)
> > 
> > s/check_scoping/scope/
> > 
> > > +{
> > > +	if (!child || base_layer < 0 || !(*walker))
> > 
> > I guess it should be:
> > WARN_ON_ONCE(!child || base_layer < 0 || !(*walker))
> > 
> > > +		return false;
> > > +
> > > +	for (deep_layer; base_layer < deep_layer; deep_layer--) {
> > 
> > No need to pass deep_layer as argument:
> > deep_layer = child->num_layers - 1
> > 
> > > +		if (check_scoping & landlock_get_scope_mask(child, deep_layer))
> > > +			return false;
> > > +		*walker = (*walker)->parent;
> > > +		if (WARN_ON_ONCE(!*walker))
> > > +			/* there is an inconsistency between num_layers
> > 
> > Please use full sentences starting with a capital letter and ending with
> > a dot, and in this case start with "/*"
> > 
> > > +			 * and landlock_hierarchy in the ruleset
> > > +			 */
> > > +			return false;
> > > +	}
> > > +	return true;
> > > +}
> > > +
> > > +/**
> > > + * domain_IPC_scope - Checks if the client domain is scoped in the same
> > > + *		      domain as the server.
> > 
> > Actually, you can remove IPC from the function name.
> > 
> > > + *
> > > + * @client: IPC sender domain.
> > > + * @server: IPC receiver domain.
> > > + *
> > > + * Check if the @client domain is scoped to access the @server; the @server
> > > + * must be scoped in the same domain.
> > 
> > Returns true if...
> > 
> > > + */
> > > +static bool domain_IPC_scope(const struct landlock_ruleset *const client,
> > > +			     const struct landlock_ruleset *const server,
> > > +			     access_mask_t ipc_type)
> > > +{
> > > +	size_t client_layer, server_layer = 0;
> > > +	int base_layer;
> > > +	struct landlock_hierarchy *client_walker, *server_walker;
> > > +	bool is_scoped;
> > > +
> > > +	/* Quick return if client has no domain */
> > > +	if (!client)
> > > +		return true;
> > > +
> > > +	client_layer = client->num_layers - 1;
> > > +	client_walker = client->hierarchy;
> > > +	if (server) {
> > > +		server_layer = server->num_layers - 1;
> > > +		server_walker = server->hierarchy;
> > > +	}
> > 
> > } else {
> > 	server_layer = 0;
> > 	server_walker = NULL;
> > }
> > 
> > > +	base_layer = (client_layer > server_layer) ? server_layer :
> > > +						     client_layer;
> > > +
> > > +	/* For client domain, walk_and_check ensures the client domain is
> > > +	 * not scoped until gets to base_layer.
> > 
> > until gets?
> > 
> > > +	 * For server_domain, it only ensures that the server domain exist.
> > > +	 */
> > > +	if (client_layer != server_layer) {
> > 
> > bool is_scoped;
> It is defined above. 

Yes, but it should be defined here because it is not used outside of
this block.

> > > +		if (client_layer > server_layer)
> > > +			is_scoped = walk_and_check(client, &client_walker,
> > > +						   server_layer, client_layer,
> > > +						   ipc_type);
> > > +		else
> > 
> > server_walker may be uninitialized and still read here, and maybe later
> > in the for loop.  The whole code should maks sure this cannot happen,
> > and a test case should check this.
> I think this case never happens, since the server_walker can be read
> here if there are more than one layers in server domain which means that
> the server_walker is not unintialized.

Yes, but this code makes it more difficult to convince yourself.  The
proposed refactoring should help.

> 
> > > +			is_scoped = walk_and_check(server, &server_walker,
> > > +						   client_layer, server_layer,
> > > +						   ipc_type & 0);
> > 
> > "ipc_type & 0" is the same as "0"
> > 
> > > +		if (!is_scoped)
> > 
> > The name doesn't reflect the semantic. walk_and_check() should return
> > the inverse.
> > 
> > > +			return false;
> > > +	}
> > 
> > This code would be simpler:
> > 
> > if (client_layer > server_layer) {
> > 	base_layer = server_layer;
> > 	// TODO: inverse boolean logic
> > 	if (!walk_and_check(client, &client_walker,
> > 				   base_layer, ipc_type))
> > 		return false;
> > } else (client_layer < server_layer) {
> > 	base_layer = client_layer;
> > 	// TODO: inverse boolean logic
> > 	if (!walk_and_check(server, &server_walker,
> > 				   base_layer, 0))
> > 		return false;
> > } else {
> > 	base_layer = client_layer;
> > }
> > 
> > 
> > I think we can improve more to make sure there is no path/risk of
> > inconsistent pointers.
> > 
> > 
> > > +	/* client and server are at the same level in hierarchy. If client is
> > > +	 * scoped, the server must be scoped in the same domain
> > > +	 */
> > > +	for (base_layer; base_layer >= 0; base_layer--) {
> > > +		if (landlock_get_scope_mask(client, base_layer) & ipc_type) {
> > 
> > With each multi-line comment, the first line should be empty:
> > /*
> >  * This check must be here since access would be denied only if
> > 
> > > +			/* This check must be here since access would be denied only if
> > > +			 * the client is scoped and the server has no domain, so
> > > +			 * if the client has a domain but is not scoped and the server
> > > +			 * has no domain, access is guaranteed.
> > > +			 */
> > > +			if (!server)
> > > +				return false;
> > > +
> > > +			if (server_walker == client_walker)
> > > +				return true;
> > > +
> > > +			return false;
> > > +		}
> > > +		client_walker = client_walker->parent;
> > > +		server_walker = server_walker->parent;
> > > +		/* Warn if there is an incosistenncy between num_layers and
> > 
> > Makes sure there is no inconsistency between num_layers and
> > 
> > 
> > > +		 * landlock_hierarchy in each of rulesets
> > > +		 */
> > > +		if (WARN_ON_ONCE(base_layer > 0 &&
> > > +				 (!server_walker || !client_walker)))
> > > +			return false;
> > > +	}
> > > +	return true;
> > > +}
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction
  2024-08-09  8:49                 ` Mickaël Salaün
@ 2024-08-09 17:54                   ` Tahera Fahimi
  0 siblings, 0 replies; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-09 17:54 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Jann Horn, outreachy, gnoack, paul, jmorris, serge,
	linux-security-module, linux-kernel, bjorn3_gh, netdev

On Fri, Aug 09, 2024 at 10:49:17AM +0200, Mickaël Salaün wrote:
> On Thu, Aug 08, 2024 at 05:17:10PM -0600, Tahera Fahimi wrote:
> > On Wed, Aug 07, 2024 at 04:44:36PM +0200, Mickaël Salaün wrote:
> > > On Wed, Aug 07, 2024 at 03:45:18PM +0200, Jann Horn wrote:
> > > > On Wed, Aug 7, 2024 at 9:21 AM Mickaël Salaün <mic@digikod.net> wrote:
> > > > > On Tue, Aug 06, 2024 at 10:46:43PM +0200, Jann Horn wrote:
> > > > > > I think adding something like this change on top of your code would
> > > > > > make it more concise (though this is entirely untested):
> > > > > >
> > > > > > --- /tmp/a      2024-08-06 22:37:33.800158308 +0200
> > > > > > +++ /tmp/b      2024-08-06 22:44:49.539314039 +0200
> > > > > > @@ -15,25 +15,12 @@
> > > > > >           * 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));
> > > > > >
> > > > > > -        if (!server) {
> > > > > > -                /*
> > > > > > -                 * Walks client's parent domains and checks that none of these
> > > > > > -                 * domains are scoped.
> > > > > > -                 */
> > > > > > -                for (; client_layer >= 0; client_layer--) {
> > > > > > -                        if (landlock_get_scope_mask(client, client_layer) &
> > > > > > -                            scope)
> > > > > > -                                return true;
> > > > > > -                }
> > > > > > -                return false;
> > > > > > -        }
> > > > >
> > > > > This loop is redundant with the following one, but it makes sure there
> > > > > is no issue nor inconsistencies with the server or server_walker
> > > > > pointers.  That's the only approach I found to make sure we don't go
> > > > > through a path that could use an incorrect pointer, and makes the code
> > > > > easy to review.
> > > > 
> > > > My view is that this is a duplication of logic for one particular
> > > > special case - after all, you can also end up walking up to the same
> > > > state (client_layer==-1, server_layer==-1, client_walker==NULL,
> > > > server_walker==NULL) with the loop at the bottom.
> > > 
> > > Indeed
> > > 
> > > > 
> > > > But I guess my preference for more concise code is kinda subjective -
> > > > if you prefer the more verbose version, I'm fine with that too.
> > > > 
> > > > > > -
> > > > > > -        server_layer = server->num_layers - 1;
> > > > > > -        server_walker = server->hierarchy;
> > > > > > +        server_layer = server ? (server->num_layers - 1) : -1;
> > > > > > +        server_walker = server ? server->hierarchy : NULL;
> > > > >
> > > > > We would need to change the last loop to avoid a null pointer deref.
> > > > 
> > > > Why? The first loop would either exit or walk the client_walker up
> > > > until client_layer is -1 and client_walker is NULL; the second loop
> > > > wouldn't do anything because the walkers are at the same layer; the
> > > > third loop's body wouldn't be executed because client_layer is -1.
> > > 
> > > Correct, I missed that client_layer would always be greater than
> > > server_layer (-1).
> > > 
> > > Tahera, could you please take Jann's proposal?
> > Done.
> > We will have duplicate logic, but it would be easier to read and review.
> 
> With Jann's proposal we don't have duplicate logic.
Still the first two for loops apply the same logic for client and server
domains, but I totally understand that it is much easier to review and
understand.
> > > 
> > > > 
> > > > The case where the server is not in any Landlock domain is just one
> > > > subcase of the more general case "client and server do not have a
> > > > common ancestor domain".
> > > > 
> > > > > >
> > > > > >          /*
> > > > > >           * 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.
> > > > > >
> > > > 
> > 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction
  2024-08-09 14:11   ` Mickaël Salaün
@ 2024-08-09 18:16     ` Tahera Fahimi
  2024-08-12 17:06       ` Mickaël Salaün
  0 siblings, 1 reply; 23+ messages in thread
From: Tahera Fahimi @ 2024-08-09 18:16 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Fri, Aug 09, 2024 at 04:11:47PM +0200, Mickaël Salaün wrote:
> On Thu, Aug 01, 2024 at 10:02:35PM -0600, Tahera Fahimi wrote:
> > A sandboxer can receive the character "a" as input from the environment
> > variable LL_SCOPE to restrict the abstract unix sockets from connecting
> > to a process outside its scoped domain.
> > 
> > Example
> > =======
> > Create an abstract unix socket to listen with socat(1):
> > socat abstract-listen:mysocket -
> > Create a sandboxed shell and pass the character "a" to LL_SCOPED:
> > LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
> > If the sandboxed process tries to connect to the listening socket
> > with command "socat - abstract-connect:mysocket", the connection
> > will fail.
> > 
> > Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> > 
> > ---
> > v8:
> > - Adding check_ruleset_scope function to parse the scope environment
> >   variable and update the landlock attribute based on the restriction
> >   provided by the user.
> > - Adding Mickaël Salaün reviews on version 7.
> > 
> > v7:
> > - Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED"
> >   environment variable. "LL_SCOPED" gets value "a" to restrict abstract
> >   unix sockets.
> > - Change LANDLOCK_ABI_LAST to 6.
> > ---
> >  samples/landlock/sandboxer.c | 56 +++++++++++++++++++++++++++++++++---
> >  1 file changed, 52 insertions(+), 4 deletions(-)
> > 
> > diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> > index e8223c3e781a..98132fd823ad 100644
> > --- a/samples/landlock/sandboxer.c
> > +++ b/samples/landlock/sandboxer.c
> > @@ -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 parse_path(char *env_path, const char ***const path_list)
> > @@ -184,6 +187,38 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
> >  	return ret;
> >  }
> >  
> > +static bool check_ruleset_scope(const char *const env_var,
> > +				struct landlock_ruleset_attr *ruleset_attr)
> > +{
> > +	bool ret = true;
> > +	char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
> > +
> > +	ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> 
> Why always removing the suported scope?
> What happen if ABI < 6 ?
Right, I will add this check before calling chek_ruleset_scope function.

> > +	env_type_scope = getenv(env_var);
> > +	/* scoping is not supported by the user */
> > +	if (!env_type_scope)
> > +		return true;
> > +	env_type_scope = strdup(env_type_scope);
> > +	unsetenv(env_var);
> > +
> > +	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) {
> > +			ruleset_attr->scoped |=
> > +				LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> 
> There are two issues here:
> 1. this would not work for ABI < 6
> 2. "a" can be repeated several times, which should probably not be
>    allowed because we don't want to support this
>    unspecified/undocumented behavior.
For the second note, I think even if the user provides multiple "a"
(something like "a:a"), It would not have a different effect (for now).
Do you suggest that I change this way of handeling this environment
variable or add documents that mention this note?
> 
> > +		} else {
> > +			fprintf(stderr, "Unsupported scoping \"%s\"\n",
> > +				ipc_scoping_name);
> > +			ret = false;
> > +			goto out_free_name;
> > +		}
> > +	}
> > +out_free_name:
> > +	free(env_type_scope);
> > +	return ret;
> > +}
> > +
> >  /* clang-format off */
> >  
> >  #define ACCESS_FS_ROUGHLY_READ ( \
> > @@ -208,7 +243,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
> >  
> >  /* 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)
> >  {
> > @@ -223,14 +258,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_SCOPED_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,
> > @@ -251,15 +287,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 restrictions on 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",
> > @@ -327,6 +366,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_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
> > +		ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> >  		fprintf(stderr,
> >  			"Hint: You should update the running kernel "
> >  			"to leverage Landlock features "
> > @@ -358,6 +401,11 @@ 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)) {
> 
> You should use the same pattern as for TCP access rigths: if the
> environment variable is not set then remove the ruleset's scopes.
I think this happens in check_ruleset_scope function. However, I will
add a condition (abi >=6) to this "if" statement.

> > +		perror("Unsupported IPC scoping requested");
> > +		return 1;
> > +	}
> > +
> >  	ruleset_fd =
> >  		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> >  	if (ruleset_fd < 0) {
> > -- 
> > 2.34.1
> > 
> > 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction
  2024-08-09 18:16     ` Tahera Fahimi
@ 2024-08-12 17:06       ` Mickaël Salaün
  0 siblings, 0 replies; 23+ messages in thread
From: Mickaël Salaün @ 2024-08-12 17:06 UTC (permalink / raw)
  To: Tahera Fahimi
  Cc: outreachy, gnoack, paul, jmorris, serge, linux-security-module,
	linux-kernel, bjorn3_gh, jannh, netdev

On Fri, Aug 09, 2024 at 12:16:37PM -0600, Tahera Fahimi wrote:
> On Fri, Aug 09, 2024 at 04:11:47PM +0200, Mickaël Salaün wrote:
> > On Thu, Aug 01, 2024 at 10:02:35PM -0600, Tahera Fahimi wrote:
> > > A sandboxer can receive the character "a" as input from the environment
> > > variable LL_SCOPE to restrict the abstract unix sockets from connecting
> > > to a process outside its scoped domain.
> > > 
> > > Example
> > > =======
> > > Create an abstract unix socket to listen with socat(1):
> > > socat abstract-listen:mysocket -
> > > Create a sandboxed shell and pass the character "a" to LL_SCOPED:
> > > LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash
> > > If the sandboxed process tries to connect to the listening socket
> > > with command "socat - abstract-connect:mysocket", the connection
> > > will fail.
> > > 
> > > Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
> > > 
> > > ---
> > > v8:
> > > - Adding check_ruleset_scope function to parse the scope environment
> > >   variable and update the landlock attribute based on the restriction
> > >   provided by the user.
> > > - Adding Mickaël Salaün reviews on version 7.
> > > 
> > > v7:
> > > - Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED"
> > >   environment variable. "LL_SCOPED" gets value "a" to restrict abstract
> > >   unix sockets.
> > > - Change LANDLOCK_ABI_LAST to 6.
> > > ---
> > >  samples/landlock/sandboxer.c | 56 +++++++++++++++++++++++++++++++++---
> > >  1 file changed, 52 insertions(+), 4 deletions(-)
> > > 
> > > diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> > > index e8223c3e781a..98132fd823ad 100644
> > > --- a/samples/landlock/sandboxer.c
> > > +++ b/samples/landlock/sandboxer.c
> > > @@ -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 parse_path(char *env_path, const char ***const path_list)
> > > @@ -184,6 +187,38 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
> > >  	return ret;
> > >  }
> > >  
> > > +static bool check_ruleset_scope(const char *const env_var,
> > > +				struct landlock_ruleset_attr *ruleset_attr)
> > > +{
> > > +	bool ret = true;
> > > +	char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;
> > > +
> > > +	ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> > 
> > Why always removing the suported scope?
> > What happen if ABI < 6 ?
> Right, I will add this check before calling chek_ruleset_scope function.
> 
> > > +	env_type_scope = getenv(env_var);
> > > +	/* scoping is not supported by the user */
> > > +	if (!env_type_scope)
> > > +		return true;
> > > +	env_type_scope = strdup(env_type_scope);
> > > +	unsetenv(env_var);
> > > +
> > > +	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) {
> > > +			ruleset_attr->scoped |=
> > > +				LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> > 
> > There are two issues here:
> > 1. this would not work for ABI < 6
> > 2. "a" can be repeated several times, which should probably not be
> >    allowed because we don't want to support this
> >    unspecified/undocumented behavior.
> For the second note, I think even if the user provides multiple "a"
> (something like "a:a"), It would not have a different effect (for now).
> Do you suggest that I change this way of handeling this environment
> variable or add documents that mention this note?

We should have a stricter approach to only allow zero or one "a" letter.


> > 
> > > +		} else {
> > > +			fprintf(stderr, "Unsupported scoping \"%s\"\n",
> > > +				ipc_scoping_name);
> > > +			ret = false;
> > > +			goto out_free_name;
> > > +		}
> > > +	}
> > > +out_free_name:
> > > +	free(env_type_scope);
> > > +	return ret;
> > > +}
> > > +
> > >  /* clang-format off */
> > >  
> > >  #define ACCESS_FS_ROUGHLY_READ ( \
> > > @@ -208,7 +243,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
> > >  
> > >  /* 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)
> > >  {
> > > @@ -223,14 +258,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_SCOPED_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,
> > > @@ -251,15 +287,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 restrictions on 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",
> > > @@ -327,6 +366,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_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */
> > > +		ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET;
> > >  		fprintf(stderr,
> > >  			"Hint: You should update the running kernel "
> > >  			"to leverage Landlock features "
> > > @@ -358,6 +401,11 @@ 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)) {
> > 
> > You should use the same pattern as for TCP access rigths: if the
> > environment variable is not set then remove the ruleset's scopes.
> I think this happens in check_ruleset_scope function. However, I will
> add a condition (abi >=6) to this "if" statement.
> 
> > > +		perror("Unsupported IPC scoping requested");
> > > +		return 1;
> > > +	}
> > > +
> > >  	ruleset_fd =
> > >  		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> > >  	if (ruleset_fd < 0) {
> > > -- 
> > > 2.34.1
> > > 
> > > 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2024-08-12 17:06 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-08-02  4:02 [PATCH v8 0/4] Landlock: Add abstract unix socket connect Tahera Fahimi
2024-08-02  4:02 ` [PATCH v8 1/4] Landlock: Add abstract unix socket connect restriction Tahera Fahimi
2024-08-02 16:47   ` Mickaël Salaün
2024-08-03 11:29   ` Mickaël Salaün
2024-08-06 19:35     ` Mickaël Salaün
2024-08-06 20:46       ` Jann Horn
2024-08-07  7:21         ` Mickaël Salaün
2024-08-07 13:45           ` Jann Horn
2024-08-07 14:44             ` Mickaël Salaün
2024-08-08 23:17               ` Tahera Fahimi
2024-08-09  8:49                 ` Mickaël Salaün
2024-08-09 17:54                   ` Tahera Fahimi
2024-08-07 15:37     ` Tahera Fahimi
2024-08-09 14:13       ` Mickaël Salaün
2024-08-06 19:36   ` Jann Horn
2024-08-02  4:02 ` [PATCH v8 2/4] selftests/landlock: Abstract unix socket restriction tests Tahera Fahimi
2024-08-07 15:08   ` Mickaël Salaün
2024-08-02  4:02 ` [PATCH v8 3/4] sample/Landlock: Support abstract unix socket restriction Tahera Fahimi
2024-08-09 14:11   ` Mickaël Salaün
2024-08-09 18:16     ` Tahera Fahimi
2024-08-12 17:06       ` Mickaël Salaün
2024-08-02  4:02 ` [PATCH v8 4/4] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI versioning Tahera Fahimi
2024-08-07 15:14   ` Mickaël Salaün

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).