netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v8 00/12] Network support for Landlock
@ 2022-10-21 15:26 Konstantin Meskhidze
  2022-10-21 15:26 ` [PATCH v8 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
                   ` (11 more replies)
  0 siblings, 12 replies; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

Hi,
This is a new V8 patch related to Landlock LSM network confinement.
It is based on the landlock's -next branch on top of v6.1-rc1 kernel version:
https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next

It brings refactoring of previous patch version V7.
Mostly there are minor fixes of logic and typos.

All test were run in QEMU evironment and compiled with
 -static flag.
 1. network_test: 18/18 tests passed.
 2. base_test: 7/7 tests passed.
 3. fs_test: 78/78 tests passed.
 4. ptrace_test: 8/8 tests passed.

Previous versions:
v7: https://lore.kernel.org/linux-security-module/20220829170401.834298-1-konstantin.meskhidze@huawei.com/
v6: https://lore.kernel.org/linux-security-module/20220621082313.3330667-1-konstantin.meskhidze@huawei.com/
v5: https://lore.kernel.org/linux-security-module/20220516152038.39594-1-konstantin.meskhidze@huawei.com
v4: https://lore.kernel.org/linux-security-module/20220309134459.6448-1-konstantin.meskhidze@huawei.com/
v3: https://lore.kernel.org/linux-security-module/20220124080215.265538-1-konstantin.meskhidze@huawei.com/
v2: https://lore.kernel.org/linux-security-module/20211228115212.703084-1-konstantin.meskhidze@huawei.com/
v1: https://lore.kernel.org/linux-security-module/20211210072123.386713-1-konstantin.meskhidze@huawei.com/

Konstantin Meskhidze (12):
  landlock: Make ruleset's access masks more generic
  landlock: Refactor landlock_find_rule/insert_rule
  landlock: Refactor merge/inherit_ruleset functions
  landlock: Move unmask_layers() and init_layer_masks()
  landlock: Refactor unmask_layers() and init_layer_masks()
  landlock: Refactor landlock_add_rule() syscall
  landlock: Add network rules support
  landlock: Implement TCP network hooks
  selftests/landlock: Share enforce_ruleset()
  selftests/landlock: Add 10 new test suites dedicated to network
  samples/landlock: Add network demo
  landlock: Document Landlock's network support

 Documentation/userspace-api/landlock.rst     |  72 +-
 include/uapi/linux/landlock.h                |  49 ++
 samples/landlock/sandboxer.c                 | 129 ++-
 security/landlock/Kconfig                    |   1 +
 security/landlock/Makefile                   |   2 +
 security/landlock/fs.c                       | 168 +---
 security/landlock/limits.h                   |   7 +-
 security/landlock/net.c                      | 164 ++++
 security/landlock/net.h                      |  26 +
 security/landlock/ruleset.c                  | 412 ++++++++--
 security/landlock/ruleset.h                  | 176 +++-
 security/landlock/setup.c                    |   2 +
 security/landlock/syscalls.c                 | 163 ++--
 tools/testing/selftests/landlock/base_test.c |   2 +-
 tools/testing/selftests/landlock/common.h    |  10 +
 tools/testing/selftests/landlock/config      |   4 +
 tools/testing/selftests/landlock/fs_test.c   |  75 +-
 tools/testing/selftests/landlock/net_test.c  | 823 +++++++++++++++++++
 18 files changed, 1972 insertions(+), 313 deletions(-)
 create mode 100644 security/landlock/net.c
 create mode 100644 security/landlock/net.h
 create mode 100644 tools/testing/selftests/landlock/net_test.c

--
2.25.1


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

* [PATCH v8 01/12] landlock: Make ruleset's access masks more generic
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:41   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

To support network type rules, this modification renames ruleset's
access masks and modifies it's type to access_masks_t. This patch
adds filesystem helper functions to add and get filesystem mask.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Refactors commit message.

Changes since v6:
* Adds a new access_masks_t for struct ruleset.
* Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
  because it OR values.
* Makes landlock_add_fs_access_mask() more resilient incorrect values.
* Refactors landlock_get_fs_access_mask().

Changes since v6:
* Adds a new access_masks_t for struct ruleset.
* Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
  because it OR values.
* Makes landlock_add_fs_access_mask() more resilient incorrect values.
* Refactors landlock_get_fs_access_mask().

Changes since v5:
* Changes access_mask_t to u32.
* Formats code with clang-format-14.

Changes since v4:
* Deletes struct landlock_access_mask.

Changes since v3:
* Splits commit.
* Adds get_mask, set_mask helpers for filesystem.
* Adds new struct landlock_access_mask.

---
 security/landlock/fs.c       | 10 +++++-----
 security/landlock/limits.h   |  1 +
 security/landlock/ruleset.c  | 17 +++++++++--------
 security/landlock/ruleset.h  | 35 +++++++++++++++++++++++++++++++----
 security/landlock/syscalls.c |  7 ++++---
 5 files changed, 50 insertions(+), 20 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index adcea0fe7e68..0d57c6479d29 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -178,9 +178,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 		return -EINVAL;

 	/* Transforms relative access rights to absolute ones. */
-	access_rights |=
-		LANDLOCK_MASK_ACCESS_FS &
-		~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
+	access_rights |= LANDLOCK_MASK_ACCESS_FS &
+			 ~(landlock_get_fs_access_mask(ruleset, 0) |
+			   ACCESS_INITIALLY_DENIED);
 	object = get_inode_object(d_backing_inode(path->dentry));
 	if (IS_ERR(object))
 		return PTR_ERR(object);
@@ -294,7 +294,7 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
 	size_t layer_level;

 	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
-		access_dom |= domain->fs_access_masks[layer_level];
+		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
 	return access_dom & LANDLOCK_MASK_ACCESS_FS;
 }

@@ -336,7 +336,7 @@ init_layer_masks(const struct landlock_ruleset *const domain,
 			 * access rights.
 			 */
 			if (BIT_ULL(access_bit) &
-			    (domain->fs_access_masks[layer_level] |
+			    (landlock_get_fs_access_mask(domain, layer_level) |
 			     ACCESS_INITIALLY_DENIED)) {
 				(*layer_masks)[access_bit] |=
 					BIT_ULL(layer_level);
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 82288f0e9e5e..bafb3b8dc677 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -21,6 +21,7 @@
 #define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_TRUNCATE
 #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
 #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
+#define LANDLOCK_SHIFT_ACCESS_FS	0

 /* clang-format on */

diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 996484f98bfd..1f3188b4e313 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -29,7 +29,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 	struct landlock_ruleset *new_ruleset;

 	new_ruleset =
-		kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers),
+		kzalloc(struct_size(new_ruleset, access_masks, num_layers),
 			GFP_KERNEL_ACCOUNT);
 	if (!new_ruleset)
 		return ERR_PTR(-ENOMEM);
@@ -40,7 +40,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 	/*
 	 * hierarchy = NULL
 	 * num_rules = 0
-	 * fs_access_masks[] = 0
+	 * access_masks[] = 0
 	 */
 	return new_ruleset;
 }
@@ -55,7 +55,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask)
 		return ERR_PTR(-ENOMSG);
 	new_ruleset = create_ruleset(1);
 	if (!IS_ERR(new_ruleset))
-		new_ruleset->fs_access_masks[0] = fs_access_mask;
+		landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
 	return new_ruleset;
 }

@@ -117,11 +117,12 @@ static void build_check_ruleset(void)
 		.num_rules = ~0,
 		.num_layers = ~0,
 	};
-	typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0;
+	typeof(ruleset.access_masks[0]) access_masks = ~0;

 	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
 	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
-	BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS);
+	BUILD_BUG_ON(access_masks <
+		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
 }

 /**
@@ -281,7 +282,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 		err = -EINVAL;
 		goto out_unlock;
 	}
-	dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
+	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];

 	/* Merges the @src tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
@@ -340,8 +341,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 		goto out_unlock;
 	}
 	/* Copies the parent layer stack and leaves a space for the new layer. */
-	memcpy(child->fs_access_masks, parent->fs_access_masks,
-	       flex_array_size(parent, fs_access_masks, parent->num_layers));
+	memcpy(child->access_masks, parent->access_masks,
+	       flex_array_size(parent, access_masks, parent->num_layers));

 	if (WARN_ON_ONCE(!parent->hierarchy)) {
 		err = -EINVAL;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index d43231b783e4..f2ad932d396c 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -25,6 +25,11 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
 /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
 static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));

+/* Ruleset access masks. */
+typedef u16 access_masks_t;
+/* Makes sure all ruleset access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
+
 typedef u16 layer_mask_t;
 /* Makes sure all layers can be checked. */
 static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
@@ -110,7 +115,7 @@ struct landlock_ruleset {
 		 * section.  This is only used by
 		 * landlock_put_ruleset_deferred() when @usage reaches zero.
 		 * The fields @lock, @usage, @num_rules, @num_layers and
-		 * @fs_access_masks are then unused.
+		 * @access_masks are then unused.
 		 */
 		struct work_struct work_free;
 		struct {
@@ -137,7 +142,7 @@ struct landlock_ruleset {
 			 */
 			u32 num_layers;
 			/**
-			 * @fs_access_masks: Contains the subset of filesystem
+			 * @access_masks: Contains the subset of filesystem
 			 * actions that are restricted by a ruleset.  A domain
 			 * saves all layers of merged rulesets in a stack
 			 * (FAM), starting from the first layer to the last
@@ -148,13 +153,13 @@ struct landlock_ruleset {
 			 * layers are set once and never changed for the
 			 * lifetime of the ruleset.
 			 */
-			access_mask_t fs_access_masks[];
+			access_masks_t access_masks[];
 		};
 	};
 };

 struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask);
+landlock_create_ruleset(const access_mask_t access_mask);

 void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -177,4 +182,26 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
 		refcount_inc(&ruleset->usage);
 }

+static inline void
+landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
+			    const access_mask_t fs_access_mask,
+			    const u16 layer_level)
+{
+	access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
+
+	/* Should already be checked in sys_landlock_create_ruleset(). */
+	WARN_ON_ONCE(fs_access_mask != fs_mask);
+	// TODO: Add tests to check "|=" and not "="
+	ruleset->access_masks[layer_level] |=
+		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
+}
+
+static inline access_mask_t
+landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
+			    const u16 layer_level)
+{
+	return (ruleset->access_masks[layer_level] >>
+		LANDLOCK_SHIFT_ACCESS_FS) &
+	       LANDLOCK_MASK_ACCESS_FS;
+}
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 245cc650a4dc..71aca7f990bc 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -346,10 +346,11 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	}
 	/*
 	 * Checks that allowed_access matches the @ruleset constraints
-	 * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits).
+	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
 	 */
-	if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) !=
-	    ruleset->fs_access_masks[0]) {
+	if ((path_beneath_attr.allowed_access |
+	     landlock_get_fs_access_mask(ruleset, 0)) !=
+	    landlock_get_fs_access_mask(ruleset, 0)) {
 		err = -EINVAL;
 		goto out_put_ruleset;
 	}
--
2.25.1


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

* [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
  2022-10-21 15:26 ` [PATCH v8 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:41   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

Adds a new landlock_key union and landlock_id structure to support
a socket port rule type. A struct landlock_id identifies a unique entry
in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
port). There is one red-black tree per key type.

This patch also adds is_object_pointer() and get_root() helpers.
is_object_pointer() one checks whether key type is LANDLOCK_KEY_INODE.
get_root() helper chooses red_black tree root depending on a key type.

Refactors landlock_insert_rule() and landlock_find_rule() to support coming
network modifications. Now adding or searching a rule in a ruleset depends
on a landlock id argument provided in refactored functions.

Co-developed-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Completes all the new field descriptions landlock_key,
  landlock_key_type, landlock_id.
* Refactors commit message, adds a co-developer.

Changes since v6:
* Adds union landlock_key, enum landlock_key_type, and struct
  landlock_id.
* Refactors ruleset functions and improves switch/cases: create_rule(),
  insert_rule(), get_root(), is_object_pointer(), free_rule(),
  landlock_find_rule().
* Refactors landlock_append_fs_rule() functions to support new
  landlock_id type.

Changes since v5:
* Formats code with clang-format-14.

Changes since v4:
* Refactors insert_rule() and create_rule() functions by deleting
rule_type from their arguments list, it helps to reduce useless code.

Changes since v3:
* Splits commit.
* Refactors landlock_insert_rule and landlock_find_rule functions.
* Rename new_ruleset->root_inode.

---
 security/landlock/fs.c      |  21 ++++--
 security/landlock/ruleset.c | 146 +++++++++++++++++++++++++-----------
 security/landlock/ruleset.h |  64 +++++++++++++---
 3 files changed, 169 insertions(+), 62 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 0d57c6479d29..710cfa1306de 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -168,7 +168,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 			    access_mask_t access_rights)
 {
 	int err;
-	struct landlock_object *object;
+	struct landlock_id id = {
+		.type = LANDLOCK_KEY_INODE,
+	};

 	/* Files only get access rights that make sense. */
 	if (!d_is_dir(path->dentry) &&
@@ -181,17 +183,17 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 	access_rights |= LANDLOCK_MASK_ACCESS_FS &
 			 ~(landlock_get_fs_access_mask(ruleset, 0) |
 			   ACCESS_INITIALLY_DENIED);
-	object = get_inode_object(d_backing_inode(path->dentry));
-	if (IS_ERR(object))
-		return PTR_ERR(object);
+	id.key.object = get_inode_object(d_backing_inode(path->dentry));
+	if (IS_ERR(id.key.object))
+		return PTR_ERR(id.key.object);
 	mutex_lock(&ruleset->lock);
-	err = landlock_insert_rule(ruleset, object, access_rights);
+	err = landlock_insert_rule(ruleset, id, access_rights);
 	mutex_unlock(&ruleset->lock);
 	/*
 	 * No need to check for an error because landlock_insert_rule()
 	 * increments the refcount for the new object if needed.
 	 */
-	landlock_put_object(object);
+	landlock_put_object(id.key.object);
 	return err;
 }

@@ -208,6 +210,9 @@ find_rule(const struct landlock_ruleset *const domain,
 {
 	const struct landlock_rule *rule;
 	const struct inode *inode;
+	struct landlock_id id = {
+		.type = LANDLOCK_KEY_INODE,
+	};

 	/* Ignores nonexistent leafs. */
 	if (d_is_negative(dentry))
@@ -215,8 +220,8 @@ find_rule(const struct landlock_ruleset *const domain,

 	inode = d_backing_inode(dentry);
 	rcu_read_lock();
-	rule = landlock_find_rule(
-		domain, rcu_dereference(landlock_inode(inode)->object));
+	id.key.object = rcu_dereference(landlock_inode(inode)->object);
+	rule = landlock_find_rule(domain, id);
 	rcu_read_unlock();
 	return rule;
 }
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 1f3188b4e313..41de17d1869e 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -35,7 +35,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 		return ERR_PTR(-ENOMEM);
 	refcount_set(&new_ruleset->usage, 1);
 	mutex_init(&new_ruleset->lock);
-	new_ruleset->root = RB_ROOT;
+	new_ruleset->root_inode = RB_ROOT;
 	new_ruleset->num_layers = num_layers;
 	/*
 	 * hierarchy = NULL
@@ -68,8 +68,18 @@ static void build_check_rule(void)
 	BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
 }

+static inline bool is_object_pointer(const enum landlock_key_type key_type)
+{
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		return true;
+	}
+	WARN_ON_ONCE(1);
+	return false;
+}
+
 static struct landlock_rule *
-create_rule(struct landlock_object *const object,
+create_rule(const struct landlock_id id,
 	    const struct landlock_layer (*const layers)[], const u32 num_layers,
 	    const struct landlock_layer *const new_layer)
 {
@@ -90,8 +100,13 @@ create_rule(struct landlock_object *const object,
 	if (!new_rule)
 		return ERR_PTR(-ENOMEM);
 	RB_CLEAR_NODE(&new_rule->node);
-	landlock_get_object(object);
-	new_rule->object = object;
+	if (is_object_pointer(id.type)) {
+		/* This should be catched by insert_rule(). */
+		WARN_ON_ONCE(!id.key.object);
+		landlock_get_object(id.key.object);
+	}
+
+	new_rule->key = id.key;
 	new_rule->num_layers = new_num_layers;
 	/* Copies the original layer stack. */
 	memcpy(new_rule->layers, layers,
@@ -102,12 +117,29 @@ create_rule(struct landlock_object *const object,
 	return new_rule;
 }

-static void free_rule(struct landlock_rule *const rule)
+static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
+				       const enum landlock_key_type key_type)
+{
+	struct rb_root *root = NULL;
+
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		root = &ruleset->root_inode;
+		break;
+	}
+	if (WARN_ON_ONCE(!root))
+		return ERR_PTR(-EINVAL);
+	return root;
+}
+
+static void free_rule(struct landlock_rule *const rule,
+		      const enum landlock_key_type key_type)
 {
 	might_sleep();
 	if (!rule)
 		return;
-	landlock_put_object(rule->object);
+	if (is_object_pointer(key_type))
+		landlock_put_object(rule->key.object);
 	kfree(rule);
 }

@@ -129,8 +161,8 @@ static void build_check_ruleset(void)
  * insert_rule - Create and insert a rule in a ruleset
  *
  * @ruleset: The ruleset to be updated.
- * @object: The object to build the new rule with.  The underlying kernel
- *          object must be held by the caller.
+ * @id: The ID to build the new rule with.  The underlying kernel object, if
+ *      any, must be held by the caller.
  * @layers: One or multiple layers to be copied into the new rule.
  * @num_layers: The number of @layers entries.
  *
@@ -144,26 +176,37 @@ static void build_check_ruleset(void)
  * access rights.
  */
 static int insert_rule(struct landlock_ruleset *const ruleset,
-		       struct landlock_object *const object,
+		       const struct landlock_id id,
 		       const struct landlock_layer (*const layers)[],
-		       size_t num_layers)
+		       const size_t num_layers)
 {
 	struct rb_node **walker_node;
 	struct rb_node *parent_node = NULL;
 	struct landlock_rule *new_rule;
+	struct rb_root *root;

 	might_sleep();
 	lockdep_assert_held(&ruleset->lock);
-	if (WARN_ON_ONCE(!object || !layers))
+	if (WARN_ON_ONCE(!layers))
 		return -ENOENT;
-	walker_node = &(ruleset->root.rb_node);
+
+	if (is_object_pointer(id.type)) {
+		if (WARN_ON_ONCE(!id.key.object))
+			return -ENOENT;
+	}
+
+	root = get_root(ruleset, id.type);
+	if (IS_ERR(root))
+		return PTR_ERR(root);
+
+	walker_node = &root->rb_node;
 	while (*walker_node) {
 		struct landlock_rule *const this =
 			rb_entry(*walker_node, struct landlock_rule, node);

-		if (this->object != object) {
+		if (this->key.data != id.key.data) {
 			parent_node = *walker_node;
-			if (this->object < object)
+			if (this->key.data < id.key.data)
 				walker_node = &((*walker_node)->rb_right);
 			else
 				walker_node = &((*walker_node)->rb_left);
@@ -195,24 +238,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 		 * Intersects access rights when it is a merge between a
 		 * ruleset and a domain.
 		 */
-		new_rule = create_rule(object, &this->layers, this->num_layers,
+		new_rule = create_rule(id, &this->layers, this->num_layers,
 				       &(*layers)[0]);
 		if (IS_ERR(new_rule))
 			return PTR_ERR(new_rule);
-		rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
-		free_rule(this);
+		rb_replace_node(&this->node, &new_rule->node, root);
+		free_rule(this, id.type);
 		return 0;
 	}

-	/* There is no match for @object. */
+	/* There is no match for @id. */
 	build_check_ruleset();
 	if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
 		return -E2BIG;
-	new_rule = create_rule(object, layers, num_layers, NULL);
+	new_rule = create_rule(id, layers, num_layers, NULL);
 	if (IS_ERR(new_rule))
 		return PTR_ERR(new_rule);
 	rb_link_node(&new_rule->node, parent_node, walker_node);
-	rb_insert_color(&new_rule->node, &ruleset->root);
+	rb_insert_color(&new_rule->node, root);
 	ruleset->num_rules++;
 	return 0;
 }
@@ -230,7 +273,7 @@ static void build_check_layer(void)

 /* @ruleset must be locked by the caller. */
 int landlock_insert_rule(struct landlock_ruleset *const ruleset,
-			 struct landlock_object *const object,
+			 const struct landlock_id id,
 			 const access_mask_t access)
 {
 	struct landlock_layer layers[] = { {
@@ -240,7 +283,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
 	} };

 	build_check_layer();
-	return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers));
+	return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
 }

 static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -263,6 +306,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 			 struct landlock_ruleset *const src)
 {
 	struct landlock_rule *walker_rule, *next_rule;
+	struct rb_root *src_root;
 	int err = 0;

 	might_sleep();
@@ -273,6 +317,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
 		return -EINVAL;

+	src_root = get_root(src, LANDLOCK_KEY_INODE);
+	if (IS_ERR(src_root))
+		return PTR_ERR(src_root);
+
 	/* Locks @dst first because we are its only owner. */
 	mutex_lock(&dst->lock);
 	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
@@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];

 	/* Merges the @src tree. */
-	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
+	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
 					     node) {
 		struct landlock_layer layers[] = { {
 			.level = dst->num_layers,
 		} };
+		const struct landlock_id id = {
+			.key = walker_rule->key,
+			.type = LANDLOCK_KEY_INODE,
+		};

-		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
-			err = -EINVAL;
-			goto out_unlock;
-		}
-		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
-			err = -EINVAL;
-			goto out_unlock;
-		}
+		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
+			return -EINVAL;
+		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
+			return -EINVAL;
 		layers[0].access = walker_rule->layers[0].access;
-		err = insert_rule(dst, walker_rule->object, &layers,
-				  ARRAY_SIZE(layers));
+
+		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
 		if (err)
 			goto out_unlock;
 	}
@@ -316,21 +364,29 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 			   struct landlock_ruleset *const child)
 {
 	struct landlock_rule *walker_rule, *next_rule;
+	struct rb_root *parent_root;
 	int err = 0;

 	might_sleep();
 	if (!parent)
 		return 0;

+	parent_root = get_root(parent, LANDLOCK_KEY_INODE);
+	if (IS_ERR(parent_root))
+		return PTR_ERR(parent_root);
+
 	/* Locks @child first because we are its only owner. */
 	mutex_lock(&child->lock);
 	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);

 	/* Copies the @parent tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
-					     &parent->root, node) {
-		err = insert_rule(child, walker_rule->object,
-				  &walker_rule->layers,
+					     parent_root, node) {
+		const struct landlock_id id = {
+			.key = walker_rule->key,
+			.type = LANDLOCK_KEY_INODE,
+		};
+		err = insert_rule(child, id, &walker_rule->layers,
 				  walker_rule->num_layers);
 		if (err)
 			goto out_unlock;
@@ -362,8 +418,9 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
 	struct landlock_rule *freeme, *next;

 	might_sleep();
-	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node)
-		free_rule(freeme);
+	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+					     node)
+		free_rule(freeme, LANDLOCK_KEY_INODE);
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
@@ -454,20 +511,23 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
  */
 const struct landlock_rule *
 landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_object *const object)
+		   const struct landlock_id id)
 {
+	const struct rb_root *root;
 	const struct rb_node *node;

-	if (!object)
+	root = get_root((struct landlock_ruleset *)ruleset, id.type);
+	if (IS_ERR(root))
 		return NULL;
-	node = ruleset->root.rb_node;
+	node = root->rb_node;
+
 	while (node) {
 		struct landlock_rule *this =
 			rb_entry(node, struct landlock_rule, node);

-		if (this->object == object)
+		if (this->key.data == id.key.data)
 			return this;
-		if (this->object < object)
+		if (this->key.data < id.key.data)
 			node = node->rb_right;
 		else
 			node = node->rb_left;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f2ad932d396c..608ab356bc3e 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -49,6 +49,46 @@ struct landlock_layer {
 	access_mask_t access;
 };

+/**
+ * union landlock_key - Key of a ruleset's red-black tree
+ */
+union landlock_key {
+	/**
+	 * @object: Pointer to identify a kernel object (e.g. an inode).
+	 */
+	struct landlock_object *object;
+	/**
+	 * @data: A raw data value to identify a network socket port.
+	 */
+	uintptr_t data;
+};
+
+/**
+ * enum landlock_key_type - Type of &union landlock_key
+ */
+enum landlock_key_type {
+	/**
+	 * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
+	 * keys.
+	 */
+	LANDLOCK_KEY_INODE = 1,
+};
+
+/**
+ * struct landlock_id - Unique rule identifier for a ruleset
+ */
+struct landlock_id {
+	/**
+	 * @key: A union to identify either a kernel object (e.g. an inode) or
+	 * a raw data value (e.g. a network socket port).
+	 */
+	union landlock_key key;
+	/**
+	 * @type: A enumerator to identify the type of landlock_ruleset's root tree.
+	 */
+	const enum landlock_key_type type;
+};
+
 /**
  * struct landlock_rule - Access rights tied to an object
  */
@@ -58,12 +98,13 @@ struct landlock_rule {
 	 */
 	struct rb_node node;
 	/**
-	 * @object: Pointer to identify a kernel object (e.g. an inode).  This
-	 * is used as a key for this ruleset element.  This pointer is set once
-	 * and never modified.  It always points to an allocated object because
-	 * each rule increments the refcount of its object.
+	 * @key: A union to identify either a kernel object (e.g. an inode) or
+	 * a raw data value (e.g. a network socket port). This is used as a key
+	 * for this ruleset element.  The pointer is set once and never
+	 * modified.  It always points to an allocated object because each rule
+	 * increments the refcount of its object.
 	 */
-	struct landlock_object *object;
+	union landlock_key key;
 	/**
 	 * @num_layers: Number of entries in @layers.
 	 */
@@ -99,11 +140,12 @@ struct landlock_hierarchy {
  */
 struct landlock_ruleset {
 	/**
-	 * @root: Root of a red-black tree containing &struct landlock_rule
-	 * nodes.  Once a ruleset is tied to a process (i.e. as a domain), this
-	 * tree is immutable until @usage reaches zero.
+	 * @root_inode: Root of a red-black tree containing &struct
+	 * landlock_rule nodes with inode object.  Once a ruleset is tied to a
+	 * process (i.e. as a domain), this tree is immutable until @usage
+	 * reaches zero.
 	 */
-	struct rb_root root;
+	struct rb_root root_inode;
 	/**
 	 * @hierarchy: Enables hierarchy identification even when a parent
 	 * domain vanishes.  This is needed for the ptrace protection.
@@ -165,7 +207,7 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);

 int landlock_insert_rule(struct landlock_ruleset *const ruleset,
-			 struct landlock_object *const object,
+			 const struct landlock_id id,
 			 const access_mask_t access);

 struct landlock_ruleset *
@@ -174,7 +216,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,

 const struct landlock_rule *
 landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_object *const object);
+		   const struct landlock_id id);

 static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
 {
--
2.25.1


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

* [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
  2022-10-21 15:26 ` [PATCH v8 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
  2022-10-21 15:26 ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:41   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks() Konstantin Meskhidze
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

Refactors merge_ruleset() and inherit_ruleset() functions to support
new rule types. This patch adds merge_tree() and inherit_tree()
helpers. Each has key_type argument to choose a particular rb_tree
structure in a ruleset.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Adds missed lockdep_assert_held it inherit_tree() and merge_tree().
* Fixes comment.

Changes since v6:
* Refactors merge_ruleset() and inherit_ruleset() functions to support
  new rule types.
* Renames tree_merge() to merge_tree() (and reorder arguments), and
  tree_copy() to inherit_tree().

Changes since v5:
* Refactors some logic errors.
* Formats code with clang-format-14.

Changes since v4:
* None

---
 security/landlock/ruleset.c | 108 ++++++++++++++++++++++++------------
 1 file changed, 73 insertions(+), 35 deletions(-)

diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 41de17d1869e..961ffe0c709e 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -302,36 +302,22 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy)
 	}
 }

-static int merge_ruleset(struct landlock_ruleset *const dst,
-			 struct landlock_ruleset *const src)
+static int merge_tree(struct landlock_ruleset *const dst,
+		      struct landlock_ruleset *const src,
+		      const enum landlock_key_type key_type)
 {
 	struct landlock_rule *walker_rule, *next_rule;
 	struct rb_root *src_root;
 	int err = 0;

 	might_sleep();
-	/* Should already be checked by landlock_merge_ruleset() */
-	if (WARN_ON_ONCE(!src))
-		return 0;
-	/* Only merge into a domain. */
-	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
-		return -EINVAL;
+	lockdep_assert_held(&dst->lock);
+	lockdep_assert_held(&src->lock);

-	src_root = get_root(src, LANDLOCK_KEY_INODE);
+	src_root = get_root(src, key_type);
 	if (IS_ERR(src_root))
 		return PTR_ERR(src_root);

-	/* Locks @dst first because we are its only owner. */
-	mutex_lock(&dst->lock);
-	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
-
-	/* Stacks the new layer. */
-	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
-		err = -EINVAL;
-		goto out_unlock;
-	}
-	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
-
 	/* Merges the @src tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
 					     node) {
@@ -340,7 +326,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 		} };
 		const struct landlock_id id = {
 			.key = walker_rule->key,
-			.type = LANDLOCK_KEY_INODE,
+			.type = key_type,
 		};

 		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
@@ -351,8 +337,39 @@ static int merge_ruleset(struct landlock_ruleset *const dst,

 		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
 		if (err)
-			goto out_unlock;
+			return err;
+	}
+	return err;
+}
+
+static int merge_ruleset(struct landlock_ruleset *const dst,
+			 struct landlock_ruleset *const src)
+{
+	int err = 0;
+
+	might_sleep();
+	/* Should already be checked by landlock_merge_ruleset() */
+	if (WARN_ON_ONCE(!src))
+		return 0;
+	/* Only merge into a domain. */
+	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
+		return -EINVAL;
+
+	/* Locks @dst first because we are its only owner. */
+	mutex_lock(&dst->lock);
+	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
+
+	/* Stacks the new layer. */
+	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
+		err = -EINVAL;
+		goto out_unlock;
 	}
+	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
+
+	/* Merges the @src inode tree. */
+	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
+	if (err)
+		goto out_unlock;

 out_unlock:
 	mutex_unlock(&src->lock);
@@ -360,43 +377,64 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	return err;
 }

-static int inherit_ruleset(struct landlock_ruleset *const parent,
-			   struct landlock_ruleset *const child)
+static int inherit_tree(struct landlock_ruleset *const parent,
+			struct landlock_ruleset *const child,
+			const enum landlock_key_type key_type)
 {
 	struct landlock_rule *walker_rule, *next_rule;
 	struct rb_root *parent_root;
 	int err = 0;

 	might_sleep();
-	if (!parent)
-		return 0;
+	lockdep_assert_held(&parent->lock);
+	lockdep_assert_held(&parent->lock);

-	parent_root = get_root(parent, LANDLOCK_KEY_INODE);
+	parent_root = get_root(parent, key_type);
 	if (IS_ERR(parent_root))
 		return PTR_ERR(parent_root);

-	/* Locks @child first because we are its only owner. */
-	mutex_lock(&child->lock);
-	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
-
-	/* Copies the @parent tree. */
+	/* Copies the @parent inode or network tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
 					     parent_root, node) {
 		const struct landlock_id id = {
 			.key = walker_rule->key,
-			.type = LANDLOCK_KEY_INODE,
+			.type = key_type,
 		};
+
 		err = insert_rule(child, id, &walker_rule->layers,
 				  walker_rule->num_layers);
 		if (err)
-			goto out_unlock;
+			return err;
 	}
+	return err;
+}
+
+static int inherit_ruleset(struct landlock_ruleset *const parent,
+			   struct landlock_ruleset *const child)
+{
+	int err = 0;
+
+	might_sleep();
+	if (!parent)
+		return 0;
+
+	/* Locks @child first because we are its only owner. */
+	mutex_lock(&child->lock);
+	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
+
+	/* Copies the @parent inode tree. */
+	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
+	if (err)
+		goto out_unlock;

 	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
 		err = -EINVAL;
 		goto out_unlock;
 	}
-	/* Copies the parent layer stack and leaves a space for the new layer. */
+	/*
+	 * Copies the parent layer stack and leaves a space
+	 * for the new layer.
+	 */
 	memcpy(child->access_masks, parent->access_masks,
 	       flex_array_size(parent, access_masks, parent->num_layers));

--
2.25.1


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

* [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks()
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (2 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:42   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 05/12] landlock: Refactor " Konstantin Meskhidze
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

This patch moves unmask_layers() and init_layer_masks() helpers
to ruleset.c to share with landlock network implementation in
following commits.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Refactors commit message.

Changes since v6:
* Moves get_handled_accesses() helper from ruleset.c back to fs.c,
  cause it's not used in coming network commits.

Changes since v5:
* Splits commit.
* Moves init_layer_masks() and get_handled_accesses() helpers
to ruleset.c and makes then non-static.
* Formats code with clang-format-14.

---
 security/landlock/fs.c      | 103 ------------------------------------
 security/landlock/ruleset.c | 102 +++++++++++++++++++++++++++++++++++
 security/landlock/ruleset.h |  20 +++++++
 3 files changed, 122 insertions(+), 103 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 710cfa1306de..240e42a8f788 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -226,60 +226,6 @@ find_rule(const struct landlock_ruleset *const domain,
 	return rule;
 }

-/*
- * @layer_masks is read and may be updated according to the access request and
- * the matching rule.
- *
- * Returns true if the request is allowed (i.e. relevant layer masks for the
- * request are empty).
- */
-static inline bool
-unmask_layers(const struct landlock_rule *const rule,
-	      const access_mask_t access_request,
-	      layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
-{
-	size_t layer_level;
-
-	if (!access_request || !layer_masks)
-		return true;
-	if (!rule)
-		return false;
-
-	/*
-	 * An access is granted if, for each policy layer, at least one rule
-	 * encountered on the pathwalk grants the requested access,
-	 * regardless of its position in the layer stack.  We must then check
-	 * the remaining layers for each inode, from the first added layer to
-	 * the last one.  When there is multiple requested accesses, for each
-	 * policy layer, the full set of requested accesses may not be granted
-	 * by only one rule, but by the union (binary OR) of multiple rules.
-	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
-	 */
-	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
-		const struct landlock_layer *const layer =
-			&rule->layers[layer_level];
-		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
-		const unsigned long access_req = access_request;
-		unsigned long access_bit;
-		bool is_empty;
-
-		/*
-		 * Records in @layer_masks which layer grants access to each
-		 * requested access.
-		 */
-		is_empty = true;
-		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
-			if (layer->access & BIT_ULL(access_bit))
-				(*layer_masks)[access_bit] &= ~layer_bit;
-			is_empty = is_empty && !(*layer_masks)[access_bit];
-		}
-		if (is_empty)
-			return true;
-	}
-	return false;
-}
-
 /*
  * Allows access to pseudo filesystems that will never be mountable (e.g.
  * sockfs, pipefs), but can still be reachable through
@@ -303,55 +249,6 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
 	return access_dom & LANDLOCK_MASK_ACCESS_FS;
 }

-/**
- * init_layer_masks - Initialize layer masks from an access request
- *
- * Populates @layer_masks such that for each access right in @access_request,
- * the bits for all the layers are set where this access right is handled.
- *
- * @domain: The domain that defines the current restrictions.
- * @access_request: The requested access rights to check.
- * @layer_masks: The layer masks to populate.
- *
- * Returns: An access mask where each access right bit is set which is handled
- * in any of the active layers in @domain.
- */
-static inline access_mask_t
-init_layer_masks(const struct landlock_ruleset *const domain,
-		 const access_mask_t access_request,
-		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
-{
-	access_mask_t handled_accesses = 0;
-	size_t layer_level;
-
-	memset(layer_masks, 0, sizeof(*layer_masks));
-	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
-	if (!access_request)
-		return 0;
-
-	/* Saves all handled accesses per layer. */
-	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
-		const unsigned long access_req = access_request;
-		unsigned long access_bit;
-
-		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
-			/*
-			 * Artificially handles all initially denied by default
-			 * access rights.
-			 */
-			if (BIT_ULL(access_bit) &
-			    (landlock_get_fs_access_mask(domain, layer_level) |
-			     ACCESS_INITIALLY_DENIED)) {
-				(*layer_masks)[access_bit] |=
-					BIT_ULL(layer_level);
-				handled_accesses |= BIT_ULL(access_bit);
-			}
-		}
-	}
-	return handled_accesses;
-}
-
 /*
  * Check that a destination file hierarchy has more restrictions than a source
  * file hierarchy.  This is only used for link and rename actions.
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 961ffe0c709e..02ab14439c43 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -572,3 +572,105 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
 	}
 	return NULL;
 }
+
+/*
+ * @layer_masks is read and may be updated according to the access request and
+ * the matching rule.
+ *
+ * Returns true if the request is allowed (i.e. relevant layer masks for the
+ * request are empty).
+ */
+bool unmask_layers(const struct landlock_rule *const rule,
+		   const access_mask_t access_request,
+		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+	size_t layer_level;
+
+	if (!access_request || !layer_masks)
+		return true;
+	if (!rule)
+		return false;
+
+	/*
+	 * An access is granted if, for each policy layer, at least one rule
+	 * encountered on the pathwalk grants the requested access,
+	 * regardless of its position in the layer stack.  We must then check
+	 * the remaining layers for each inode, from the first added layer to
+	 * the last one.  When there is multiple requested accesses, for each
+	 * policy layer, the full set of requested accesses may not be granted
+	 * by only one rule, but by the union (binary OR) of multiple rules.
+	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
+	 */
+	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
+		const struct landlock_layer *const layer =
+			&rule->layers[layer_level];
+		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
+		const unsigned long access_req = access_request;
+		unsigned long access_bit;
+		bool is_empty;
+
+		/*
+		 * Records in @layer_masks which layer grants access to each
+		 * requested access.
+		 */
+		is_empty = true;
+		for_each_set_bit(access_bit, &access_req,
+				 ARRAY_SIZE(*layer_masks)) {
+			if (layer->access & BIT_ULL(access_bit))
+				(*layer_masks)[access_bit] &= ~layer_bit;
+			is_empty = is_empty && !(*layer_masks)[access_bit];
+		}
+		if (is_empty)
+			return true;
+	}
+	return false;
+}
+
+/**
+ * init_layer_masks - Initialize layer masks from an access request
+ *
+ * Populates @layer_masks such that for each access right in @access_request,
+ * the bits for all the layers are set where this access right is handled.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @layer_masks: The layer masks to populate.
+ *
+ * Returns: An access mask where each access right bit is set which is handled
+ * in any of the active layers in @domain.
+ */
+access_mask_t
+init_layer_masks(const struct landlock_ruleset *const domain,
+		 const access_mask_t access_request,
+		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+	access_mask_t handled_accesses = 0;
+	size_t layer_level;
+
+	memset(layer_masks, 0, sizeof(*layer_masks));
+	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
+	if (!access_request)
+		return 0;
+
+	/* Saves all handled accesses per layer. */
+	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+		const unsigned long access_req = access_request;
+		unsigned long access_bit;
+
+		for_each_set_bit(access_bit, &access_req,
+				 ARRAY_SIZE(*layer_masks)) {
+			/*
+			 * Artificially handles all initially denied by default
+			 * access rights.
+			 */
+			if (BIT_ULL(access_bit) &
+			    (landlock_get_fs_access_mask(domain, layer_level) |
+			     ACCESS_INITIALLY_DENIED)) {
+				(*layer_masks)[access_bit] |=
+					BIT_ULL(layer_level);
+				handled_accesses |= BIT_ULL(access_bit);
+			}
+		}
+	}
+	return handled_accesses;
+}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 608ab356bc3e..50baff4fcbb4 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -34,6 +34,16 @@ typedef u16 layer_mask_t;
 /* Makes sure all layers can be checked. */
 static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);

+/*
+ * All access rights that are denied by default whether they are handled or not
+ * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
+ * entries when we need to get the absolute handled access masks.
+ */
+/* clang-format off */
+#define ACCESS_INITIALLY_DENIED ( \
+	LANDLOCK_ACCESS_FS_REFER)
+/* clang-format on */
+
 /**
  * struct landlock_layer - Access rights for a given layer
  */
@@ -246,4 +256,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 		LANDLOCK_SHIFT_ACCESS_FS) &
 	       LANDLOCK_MASK_ACCESS_FS;
 }
+
+bool unmask_layers(const struct landlock_rule *const rule,
+		   const access_mask_t access_request,
+		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
+
+access_mask_t
+init_layer_masks(const struct landlock_ruleset *const domain,
+		 const access_mask_t access_request,
+		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
+
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.25.1


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

* [PATCH v8 05/12] landlock: Refactor unmask_layers() and init_layer_masks()
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (3 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks() Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:42   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

Adds new key_type argument to init_layer_masks() helper.
Adds masks_array_size argument  to unmask_layers() helper.
These modifications support implementing new rule types in the next
Landlock versions.

Co-developed-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Refactors commit message, adds a co-developer.
* Minor fixes.

Changes since v6:
* Removes masks_size attribute from init_layer_masks().
* Refactors init_layer_masks() with new landlock_key_type.

Changes since v5:
* Splits commit.
* Formats code with clang-format-14.

Changes since v4:
* Refactors init_layer_masks(), get_handled_accesses()
and unmask_layers() functions to support multiple rule types.
* Refactors landlock_get_fs_access_mask() function with
LANDLOCK_MASK_ACCESS_FS mask.

Changes since v3:
* Splits commit.
* Refactors landlock_unmask_layers functions.

---
 security/landlock/fs.c      | 36 ++++++++++++++++++-------------
 security/landlock/ruleset.c | 42 ++++++++++++++++++++++++++-----------
 security/landlock/ruleset.h | 11 +++++-----
 3 files changed, 58 insertions(+), 31 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 240e42a8f788..fe76a11483f8 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -435,16 +435,20 @@ static bool is_access_to_paths_allowed(
 	if (unlikely(dentry_child1)) {
 		unmask_layers(find_rule(domain, dentry_child1),
 			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
-					       &_layer_masks_child1),
-			      &_layer_masks_child1);
+					       &_layer_masks_child1,
+					       LANDLOCK_KEY_INODE),
+			      &_layer_masks_child1,
+			      ARRAY_SIZE(_layer_masks_child1));
 		layer_masks_child1 = &_layer_masks_child1;
 		child1_is_directory = d_is_dir(dentry_child1);
 	}
 	if (unlikely(dentry_child2)) {
 		unmask_layers(find_rule(domain, dentry_child2),
 			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
-					       &_layer_masks_child2),
-			      &_layer_masks_child2);
+					       &_layer_masks_child2,
+					       LANDLOCK_KEY_INODE),
+			      &_layer_masks_child2,
+			      ARRAY_SIZE(_layer_masks_child2));
 		layer_masks_child2 = &_layer_masks_child2;
 		child2_is_directory = d_is_dir(dentry_child2);
 	}
@@ -496,15 +500,16 @@ static bool is_access_to_paths_allowed(
 		}

 		rule = find_rule(domain, walker_path.dentry);
-		allowed_parent1 = unmask_layers(rule, access_masked_parent1,
-						layer_masks_parent1);
-		allowed_parent2 = unmask_layers(rule, access_masked_parent2,
-						layer_masks_parent2);
+		allowed_parent1 = unmask_layers(
+			rule, access_masked_parent1, layer_masks_parent1,
+			ARRAY_SIZE(*layer_masks_parent1));
+		allowed_parent2 = unmask_layers(
+			rule, access_masked_parent2, layer_masks_parent2,
+			ARRAY_SIZE(*layer_masks_parent2));

 		/* Stops when a rule from each layer grants access. */
 		if (allowed_parent1 && allowed_parent2)
 			break;
-
 jump_up:
 		if (walker_path.dentry == walker_path.mnt->mnt_root) {
 			if (follow_up(&walker_path)) {
@@ -543,7 +548,8 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
 {
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};

-	access_request = init_layer_masks(domain, access_request, &layer_masks);
+	access_request = init_layer_masks(domain, access_request, &layer_masks,
+					  LANDLOCK_KEY_INODE);
 	if (is_access_to_paths_allowed(domain, path, access_request,
 				       &layer_masks, NULL, 0, NULL, NULL))
 		return 0;
@@ -630,7 +636,7 @@ static bool collect_domain_accesses(
 		return true;

 	access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
-				      layer_masks_dom);
+				      layer_masks_dom, LANDLOCK_KEY_INODE);

 	dget(dir);
 	while (true) {
@@ -638,7 +644,8 @@ static bool collect_domain_accesses(

 		/* Gets all layers allowing all domain accesses. */
 		if (unmask_layers(find_rule(domain, dir), access_dom,
-				  layer_masks_dom)) {
+				  layer_masks_dom,
+				  ARRAY_SIZE(*layer_masks_dom))) {
 			/*
 			 * Stops when all handled accesses are allowed by at
 			 * least one rule in each layer.
@@ -754,7 +761,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 		 */
 		access_request_parent1 = init_layer_masks(
 			dom, access_request_parent1 | access_request_parent2,
-			&layer_masks_parent1);
+			&layer_masks_parent1, LANDLOCK_KEY_INODE);
 		if (is_access_to_paths_allowed(
 			    dom, new_dir, access_request_parent1,
 			    &layer_masks_parent1, NULL, 0, NULL, NULL))
@@ -1131,7 +1138,8 @@ static int hook_file_open(struct file *const file)

 	if (is_access_to_paths_allowed(
 		    dom, &file->f_path,
-		    init_layer_masks(dom, full_access_request, &layer_masks),
+		    init_layer_masks(dom, full_access_request, &layer_masks,
+				     LANDLOCK_KEY_INODE),
 		    &layer_masks, NULL, 0, NULL, NULL)) {
 		allowed_access = full_access_request;
 	} else {
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 02ab14439c43..c7cf54ba4f6d 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -576,13 +576,15 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
 /*
  * @layer_masks is read and may be updated according to the access request and
  * the matching rule.
+ * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
  *
  * Returns true if the request is allowed (i.e. relevant layer masks for the
  * request are empty).
  */
 bool unmask_layers(const struct landlock_rule *const rule,
 		   const access_mask_t access_request,
-		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+		   layer_mask_t (*const layer_masks)[],
+		   const size_t masks_array_size)
 {
 	size_t layer_level;

@@ -614,8 +616,7 @@ bool unmask_layers(const struct landlock_rule *const rule,
 		 * requested access.
 		 */
 		is_empty = true;
-		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
+		for_each_set_bit(access_bit, &access_req, masks_array_size) {
 			if (layer->access & BIT_ULL(access_bit))
 				(*layer_masks)[access_bit] &= ~layer_bit;
 			is_empty = is_empty && !(*layer_masks)[access_bit];
@@ -626,6 +627,10 @@ bool unmask_layers(const struct landlock_rule *const rule,
 	return false;
 }

+typedef access_mask_t
+get_access_mask_t(const struct landlock_ruleset *const ruleset,
+		  const u16 layer_level);
+
 /**
  * init_layer_masks - Initialize layer masks from an access request
  *
@@ -635,19 +640,33 @@ bool unmask_layers(const struct landlock_rule *const rule,
  * @domain: The domain that defines the current restrictions.
  * @access_request: The requested access rights to check.
  * @layer_masks: The layer masks to populate.
+ * @key_type: The key type to switch between access masks of different types.
  *
  * Returns: An access mask where each access right bit is set which is handled
  * in any of the active layers in @domain.
  */
-access_mask_t
-init_layer_masks(const struct landlock_ruleset *const domain,
-		 const access_mask_t access_request,
-		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
+			       const access_mask_t access_request,
+			       layer_mask_t (*const layer_masks)[],
+			       const enum landlock_key_type key_type)
 {
 	access_mask_t handled_accesses = 0;
-	size_t layer_level;
+	size_t layer_level, num_access;
+	get_access_mask_t *get_access_mask;
+
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		get_access_mask = landlock_get_fs_access_mask;
+		num_access = LANDLOCK_NUM_ACCESS_FS;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	memset(layer_masks, 0,
+	       array_size(sizeof((*layer_masks)[0]), num_access));

-	memset(layer_masks, 0, sizeof(*layer_masks));
 	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
 	if (!access_request)
 		return 0;
@@ -657,14 +676,13 @@ init_layer_masks(const struct landlock_ruleset *const domain,
 		const unsigned long access_req = access_request;
 		unsigned long access_bit;

-		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
+		for_each_set_bit(access_bit, &access_req, num_access) {
 			/*
 			 * Artificially handles all initially denied by default
 			 * access rights.
 			 */
 			if (BIT_ULL(access_bit) &
-			    (landlock_get_fs_access_mask(domain, layer_level) |
+			    (get_access_mask(domain, layer_level) |
 			     ACCESS_INITIALLY_DENIED)) {
 				(*layer_masks)[access_bit] |=
 					BIT_ULL(layer_level);
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 50baff4fcbb4..d9eb79ea9a89 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -259,11 +259,12 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,

 bool unmask_layers(const struct landlock_rule *const rule,
 		   const access_mask_t access_request,
-		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
+		   layer_mask_t (*const layer_masks)[],
+		   const size_t masks_array_size);

-access_mask_t
-init_layer_masks(const struct landlock_ruleset *const domain,
-		 const access_mask_t access_request,
-		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
+access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
+			       const access_mask_t access_request,
+			       layer_mask_t (*const layer_masks)[],
+			       const enum landlock_key_type key_type);

 #endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.25.1


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

* [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (4 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 05/12] landlock: Refactor " Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:42   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 07/12] landlock: Add network rules support Konstantin Meskhidze
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

Modifies landlock_add_rule() syscall to support new rule types in future
Landlock versions. Adds add_rule_path_beneath() helper to support
current filesystem rules.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* None

Changes since v6:
* None

Changes since v5:
* Refactors syscall landlock_add_rule() and add_rule_path_beneath() helper
to make argument check ordering consistent and get rid of partial revertings
in following patches.
* Rolls back refactoring base_test.c seltest.
* Formats code with clang-format-14.

Changes since v4:
* Refactors add_rule_path_beneath() and landlock_add_rule() functions
to optimize code usage.
* Refactors base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
rule type in landlock_add_rule() call.

Changes since v3:
* Split commit.
* Refactors landlock_add_rule syscall.

---
 security/landlock/syscalls.c | 92 +++++++++++++++++++-----------------
 1 file changed, 48 insertions(+), 44 deletions(-)

diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 71aca7f990bc..87389d7bfbf2 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -274,6 +274,47 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
 	return err;
 }

+static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
+				 const void __user *const rule_attr)
+{
+	struct landlock_path_beneath_attr path_beneath_attr;
+	struct path path;
+	int res, err;
+	u32 mask;
+
+	/* Copies raw user space buffer, only one type for now. */
+	res = copy_from_user(&path_beneath_attr, rule_attr,
+			     sizeof(path_beneath_attr));
+	if (res)
+		return -EFAULT;
+
+	/*
+	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
+	 * are ignored in path walks.
+	 */
+	if (!path_beneath_attr.allowed_access)
+		return -ENOMSG;
+	/*
+	 * Checks that allowed_access matches the @ruleset constraints
+	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
+	 */
+	mask = landlock_get_fs_access_mask(ruleset, 0);
+	if ((path_beneath_attr.allowed_access | mask) != mask)
+		return -EINVAL;
+
+	/* Gets and checks the new rule. */
+	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
+	if (err)
+		return err;
+
+	/* Imports the new rule. */
+	err = landlock_append_fs_rule(ruleset, &path,
+				      path_beneath_attr.allowed_access);
+	path_put(&path);
+
+	return err;
+}
+
 /**
  * sys_landlock_add_rule - Add a new rule to a ruleset
  *
@@ -306,10 +347,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 		const enum landlock_rule_type, rule_type,
 		const void __user *const, rule_attr, const __u32, flags)
 {
-	struct landlock_path_beneath_attr path_beneath_attr;
-	struct path path;
 	struct landlock_ruleset *ruleset;
-	int res, err;
+	int err;

 	if (!landlock_initialized)
 		return -EOPNOTSUPP;
@@ -323,49 +362,14 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	if (IS_ERR(ruleset))
 		return PTR_ERR(ruleset);

-	if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		err = add_rule_path_beneath(ruleset, rule_attr);
+		break;
+	default:
 		err = -EINVAL;
-		goto out_put_ruleset;
-	}
-
-	/* Copies raw user space buffer, only one type for now. */
-	res = copy_from_user(&path_beneath_attr, rule_attr,
-			     sizeof(path_beneath_attr));
-	if (res) {
-		err = -EFAULT;
-		goto out_put_ruleset;
+		break;
 	}
-
-	/*
-	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
-	 * are ignored in path walks.
-	 */
-	if (!path_beneath_attr.allowed_access) {
-		err = -ENOMSG;
-		goto out_put_ruleset;
-	}
-	/*
-	 * Checks that allowed_access matches the @ruleset constraints
-	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
-	 */
-	if ((path_beneath_attr.allowed_access |
-	     landlock_get_fs_access_mask(ruleset, 0)) !=
-	    landlock_get_fs_access_mask(ruleset, 0)) {
-		err = -EINVAL;
-		goto out_put_ruleset;
-	}
-
-	/* Gets and checks the new rule. */
-	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
-	if (err)
-		goto out_put_ruleset;
-
-	/* Imports the new rule. */
-	err = landlock_append_fs_rule(ruleset, &path,
-				      path_beneath_attr.allowed_access);
-	path_put(&path);
-
-out_put_ruleset:
 	landlock_put_ruleset(ruleset);
 	return err;
 }
--
2.25.1


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

* [PATCH v8 07/12] landlock: Add network rules support
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (5 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:43   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 08/12] landlock: Implement TCP network hooks Konstantin Meskhidze
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

This commit adds network rules support in internal landlock functions
(presented in ruleset.c) and landlock_create_ruleset syscall.
Refactors user space API to support network actions. Adds new network
access flags, network rule and network attributes. Increments Landlock
ABI version.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Squashes commits.
* Increments ABI version to 4.
* Refactors commit message.
* Minor fixes.

Changes since v6:
* Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
  because it OR values.
* Makes landlock_add_net_access_mask() more resilient incorrect values.
* Refactors landlock_get_net_access_mask().
* Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
  LANDLOCK_NUM_ACCESS_FS as value.
* Updates access_masks_t to u32 to support network access actions.
* Refactors landlock internal functions to support network actions with
  landlock_key/key_type/id types.

Changes since v5:
* Gets rid of partial revert from landlock_add_rule
syscall.
* Formats code with clang-format-14.

Changes since v4:
* Refactors landlock_create_ruleset() - splits ruleset and
masks checks.
* Refactors landlock_create_ruleset() and landlock mask
setters/getters to support two rule types.
* Refactors landlock_add_rule syscall add_rule_path_beneath
function by factoring out get_ruleset_from_fd() and
landlock_put_ruleset().

Changes since v3:
* Splits commit.
* Adds network rule support for internal landlock functions.
* Adds set_mask and get_mask for network.
* Adds rb_root root_net_port.

---
 include/uapi/linux/landlock.h                | 49 ++++++++++++++
 security/landlock/limits.h                   |  6 +-
 security/landlock/ruleset.c                  | 55 ++++++++++++++--
 security/landlock/ruleset.h                  | 68 ++++++++++++++++----
 security/landlock/syscalls.c                 | 13 +++-
 tools/testing/selftests/landlock/base_test.c |  2 +-
 6 files changed, 170 insertions(+), 23 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f3223f964691..096b683c6ff3 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
 	 * this access right.
 	 */
 	__u64 handled_access_fs;
+
+	/**
+	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
+	 * that is handled by this ruleset and should then be forbidden if no
+	 * rule explicitly allow them.
+	 */
+	__u64 handled_access_net;
 };

 /*
@@ -54,6 +61,11 @@ enum landlock_rule_type {
 	 * landlock_path_beneath_attr .
 	 */
 	LANDLOCK_RULE_PATH_BENEATH = 1,
+	/**
+	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
+	 * landlock_net_service_attr .
+	 */
+	LANDLOCK_RULE_NET_SERVICE = 2,
 };

 /**
@@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
 	 */
 } __attribute__((packed));

+/**
+ * struct landlock_net_service_attr - TCP subnet definition
+ *
+ * Argument of sys_landlock_add_rule().
+ */
+struct landlock_net_service_attr {
+	/**
+	 * @allowed_access: Bitmask of allowed access network for services
+	 * (cf. `Network flags`_).
+	 */
+	__u64 allowed_access;
+	/**
+	 * @port: Network port.
+	 */
+	__u16 port;
+
+} __attribute__((packed));
+
 /**
  * DOC: fs_access
  *
@@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
 #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
 /* clang-format on */

+/**
+ * DOC: net_access
+ *
+ * Network flags
+ * ~~~~~~~~~~~~~~~~
+ *
+ * These flags enable to restrict a sandboxed process to a set of network
+ * actions.
+ *
+ * TCP sockets with allowed actions:
+ *
+ * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
+ * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
+ *   a remote port.
+ */
+/* clang-format off */
+#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
+#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
+/* clang-format on */
 #endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index bafb3b8dc677..8a1a6463c64e 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -23,6 +23,10 @@
 #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
 #define LANDLOCK_SHIFT_ACCESS_FS	0

-/* clang-format on */
+#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
+#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
+#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS

+/* clang-format on */
 #endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index c7cf54ba4f6d..9277c1295114 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -36,6 +36,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 	refcount_set(&new_ruleset->usage, 1);
 	mutex_init(&new_ruleset->lock);
 	new_ruleset->root_inode = RB_ROOT;
+#if IS_ENABLED(CONFIG_INET)
+	new_ruleset->root_net_port = RB_ROOT;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	new_ruleset->num_layers = num_layers;
 	/*
 	 * hierarchy = NULL
@@ -46,16 +49,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 }

 struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask)
+landlock_create_ruleset(const access_mask_t fs_access_mask,
+			const access_mask_t net_access_mask)
 {
 	struct landlock_ruleset *new_ruleset;

 	/* Informs about useless ruleset. */
-	if (!fs_access_mask)
+	if (!fs_access_mask && !net_access_mask)
 		return ERR_PTR(-ENOMSG);
 	new_ruleset = create_ruleset(1);
-	if (!IS_ERR(new_ruleset))
+	if (IS_ERR(new_ruleset))
+		return new_ruleset;
+	if (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);
 	return new_ruleset;
 }

@@ -73,6 +81,10 @@ static inline bool is_object_pointer(const enum landlock_key_type key_type)
 	switch (key_type) {
 	case LANDLOCK_KEY_INODE:
 		return true;
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		return false;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	}
 	WARN_ON_ONCE(1);
 	return false;
@@ -126,6 +138,11 @@ static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
 	case LANDLOCK_KEY_INODE:
 		root = &ruleset->root_inode;
 		break;
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		root = &ruleset->root_net_port;
+		break;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	}
 	if (WARN_ON_ONCE(!root))
 		return ERR_PTR(-EINVAL);
@@ -154,7 +171,9 @@ static void build_check_ruleset(void)
 	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
 	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
 	BUILD_BUG_ON(access_masks <
-		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
+		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) +
+			     (LANDLOCK_MASK_ACCESS_NET
+			      << LANDLOCK_SHIFT_ACCESS_NET));
 }

 /**
@@ -370,7 +389,12 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
 	if (err)
 		goto out_unlock;
-
+#if IS_ENABLED(CONFIG_INET)
+	/* Merges the @src network port tree. */
+	err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
+	if (err)
+		goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
 out_unlock:
 	mutex_unlock(&src->lock);
 	mutex_unlock(&dst->lock);
@@ -426,7 +450,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
 	if (err)
 		goto out_unlock;
-
+#if IS_ENABLED(CONFIG_INET)
+	/* Copies the @parent network port tree. */
+	err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
+	if (err)
+		goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
 		err = -EINVAL;
 		goto out_unlock;
@@ -459,6 +488,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
 	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
 					     node)
 		free_rule(freeme, LANDLOCK_KEY_INODE);
+#if IS_ENABLED(CONFIG_INET)
+	rbtree_postorder_for_each_entry_safe(freeme, next,
+					     &ruleset->root_net_port, node)
+		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
+#endif /* IS_ENABLED(CONFIG_INET) */
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
@@ -637,6 +671,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
  * Populates @layer_masks such that for each access right in @access_request,
  * the bits for all the layers are set where this access right is handled.
  *
+ * @layer_masks must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET
+ * elements according to @key_type.
+ *
  * @domain: The domain that defines the current restrictions.
  * @access_request: The requested access rights to check.
  * @layer_masks: The layer masks to populate.
@@ -659,6 +696,12 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
 		get_access_mask = landlock_get_fs_access_mask;
 		num_access = LANDLOCK_NUM_ACCESS_FS;
 		break;
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		get_access_mask = landlock_get_net_access_mask;
+		num_access = LANDLOCK_NUM_ACCESS_NET;
+		break;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	default:
 		WARN_ON_ONCE(1);
 		return 0;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index d9eb79ea9a89..f272d2cd518c 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -19,16 +19,20 @@
 #include "limits.h"
 #include "object.h"

+/* Rule access mask. */
 typedef u16 access_mask_t;
 /* Makes sure all filesystem access rights can be stored. */
 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 for_each_set_bit() and for_each_clear_bit() calls are OK. */
 static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));

 /* Ruleset access masks. */
-typedef u16 access_masks_t;
+typedef u32 access_masks_t;
 /* Makes sure all ruleset access rights can be stored. */
-static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
+static_assert(BITS_PER_TYPE(access_masks_t) >=
+	      LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);

 typedef u16 layer_mask_t;
 /* Makes sure all layers can be checked. */
@@ -82,6 +86,13 @@ enum landlock_key_type {
 	 * keys.
 	 */
 	LANDLOCK_KEY_INODE = 1,
+#if IS_ENABLED(CONFIG_INET)
+	/**
+	 * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
+	 * node keys.
+	 */
+	LANDLOCK_KEY_NET_PORT = 2,
+#endif /* IS_ENABLED(CONFIG_INET) */
 };

 /**
@@ -156,6 +167,15 @@ struct landlock_ruleset {
 	 * reaches zero.
 	 */
 	struct rb_root root_inode;
+#if IS_ENABLED(CONFIG_INET)
+	/**
+	 * @root_net_port: Root of a red-black tree containing &struct
+	 * landlock_rule nodes with network port. Once a ruleset is tied to a
+	 * process (i.e. as a domain), this tree is immutable until @usage
+	 * reaches zero.
+	 */
+	struct rb_root root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	/**
 	 * @hierarchy: Enables hierarchy identification even when a parent
 	 * domain vanishes.  This is needed for the ptrace protection.
@@ -166,8 +186,8 @@ struct landlock_ruleset {
 		 * @work_free: Enables to free a ruleset within a lockless
 		 * section.  This is only used by
 		 * landlock_put_ruleset_deferred() when @usage reaches zero.
-		 * The fields @lock, @usage, @num_rules, @num_layers and
-		 * @access_masks are then unused.
+		 * The fields @lock, @usage, @num_rules, @num_layers,
+		 * @net_access_mask and @access_masks are then unused.
 		 */
 		struct work_struct work_free;
 		struct {
@@ -194,13 +214,13 @@ struct landlock_ruleset {
 			 */
 			u32 num_layers;
 			/**
-			 * @access_masks: Contains the subset of filesystem
-			 * actions that are restricted by a ruleset.  A domain
-			 * saves all layers of merged rulesets in a stack
-			 * (FAM), starting from the first layer to the last
-			 * one.  These layers are used when merging rulesets,
-			 * for user space backward compatibility (i.e.
-			 * future-proof), and to properly handle merged
+			 * @access_masks: Contains the subset of filesystem and
+			 * network actions that are restricted by a ruleset.
+			 * A domain saves all layers of merged rulesets in a
+			 * stack (FAM), starting from the first layer to the
+			 * last one.  These layers are used when merging
+			 * rulesets, for user space backward compatibility
+			 * (i.e. future-proof), and to properly handle merged
 			 * rulesets without overlapping access rights.  These
 			 * layers are set once and never changed for the
 			 * lifetime of the ruleset.
@@ -211,7 +231,8 @@ struct landlock_ruleset {
 };

 struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t access_mask);
+landlock_create_ruleset(const access_mask_t access_mask_fs,
+			const access_mask_t access_mask_net);

 void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -248,6 +269,20 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
 		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
 }

+static inline void
+landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
+			     const access_mask_t net_access_mask,
+			     const u16 layer_level)
+{
+	access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET;
+
+	/* Should already be checked in sys_landlock_create_ruleset(). */
+	WARN_ON_ONCE(net_access_mask != net_mask);
+	// TODO: Add tests to check "|=" and not "="
+	ruleset->access_masks[layer_level] |=
+		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
+}
+
 static inline access_mask_t
 landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 			    const u16 layer_level)
@@ -257,6 +292,15 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 	       LANDLOCK_MASK_ACCESS_FS;
 }

+static inline access_mask_t
+landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
+			     const u16 layer_level)
+{
+	return (ruleset->access_masks[layer_level] >>
+		LANDLOCK_SHIFT_ACCESS_NET) &
+	       LANDLOCK_MASK_ACCESS_NET;
+}
+
 bool 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 87389d7bfbf2..c5a6ad4e2fca 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -82,8 +82,9 @@ static void build_check_abi(void)
 	 * struct size.
 	 */
 	ruleset_size = sizeof(ruleset_attr.handled_access_fs);
+	ruleset_size += sizeof(ruleset_attr.handled_access_net);
 	BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
-	BUILD_BUG_ON(sizeof(ruleset_attr) != 8);
+	BUILD_BUG_ON(sizeof(ruleset_attr) != 16);

 	path_beneath_size = sizeof(path_beneath_attr.allowed_access);
 	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@@ -129,7 +130,7 @@ static const struct file_operations ruleset_fops = {
 	.write = fop_dummy_write,
 };

-#define LANDLOCK_ABI_VERSION 3
+#define LANDLOCK_ABI_VERSION 4

 /**
  * sys_landlock_create_ruleset - Create a new ruleset
@@ -188,8 +189,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
 	    LANDLOCK_MASK_ACCESS_FS)
 		return -EINVAL;

+	/* Checks network content (and 32-bits cast). */
+	if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) !=
+	    LANDLOCK_MASK_ACCESS_NET)
+		return -EINVAL;
+
 	/* Checks arguments and transforms to kernel struct. */
-	ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs);
+	ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
+					  ruleset_attr.handled_access_net);
 	if (IS_ERR(ruleset))
 		return PTR_ERR(ruleset);

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 792c3f0a59b4..646f778dfb1e 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(3, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(4, landlock_create_ruleset(NULL, 0,
 					     LANDLOCK_CREATE_RULESET_VERSION));

 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
--
2.25.1


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

* [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (6 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 07/12] landlock: Add network rules support Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:43   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

This patch adds support of socket_bind() and socket_connect() hooks.
It's possible to restrict binding and connecting of TCP sockets to
particular ports.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Minor fixes.
* Refactors commit message.

Changes since v6:
* Updates copyright.
* Refactors landlock_append_net_rule() and check_socket_access()
  functions with landlock_id type.

Changes since v5:
* Fixes some logic errors.
* Formats code with clang-format-14.

Changes since v4:
* Factors out CONFIG_INET into make file.
* Refactors check_socket_access().
* Adds helper get_port().
* Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
functions to support AF_INET6 family.
* Adds AF_UNSPEC family support in hook_socket_bind/connect
functions.
* Refactors add_rule_net_service() and landlock_add_rule
syscall to support network rule inserting.
* Refactors init_layer_masks() to support network rules.

Changes since v3:
* Splits commit.
* Adds SECURITY_NETWORK in config.
* Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
* Adds hook_socket_bind and hook_socket_connect hooks.

---
 security/landlock/Kconfig    |   1 +
 security/landlock/Makefile   |   2 +
 security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
 security/landlock/net.h      |  26 ++++++
 security/landlock/setup.c    |   2 +
 security/landlock/syscalls.c |  59 ++++++++++++-
 6 files changed, 251 insertions(+), 3 deletions(-)
 create mode 100644 security/landlock/net.c
 create mode 100644 security/landlock/net.h

diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
index 8e33c4e8ffb8..10c099097533 100644
--- a/security/landlock/Kconfig
+++ b/security/landlock/Kconfig
@@ -3,6 +3,7 @@
 config SECURITY_LANDLOCK
 	bool "Landlock support"
 	depends on SECURITY && !ARCH_EPHEMERAL_INODES
+	select SECURITY_NETWORK
 	select SECURITY_PATH
 	help
 	  Landlock is a sandboxing mechanism that enables processes to restrict
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 7bbd2f413b3e..53d3c92ae22e 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o

 landlock-y := setup.o syscalls.o object.o ruleset.o \
 	cred.o ptrace.o fs.o
+
+landlock-$(CONFIG_INET) += net.o
\ No newline at end of file
diff --git a/security/landlock/net.c b/security/landlock/net.c
new file mode 100644
index 000000000000..39e8a156a1f4
--- /dev/null
+++ b/security/landlock/net.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022 Huawei Tech. Co., Ltd.
+ * Copyright © 2022 Microsoft Corporation
+ */
+
+#include <linux/in.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <net/ipv6.h>
+
+#include "common.h"
+#include "cred.h"
+#include "limits.h"
+#include "net.h"
+#include "ruleset.h"
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+			     const u16 port, access_mask_t access_rights)
+{
+	int err;
+	const struct landlock_id id = {
+		.key.data = port,
+		.type = LANDLOCK_KEY_NET_PORT,
+	};
+	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+	/* Transforms relative access rights to absolute ones. */
+	access_rights |= LANDLOCK_MASK_ACCESS_NET &
+			 ~landlock_get_net_access_mask(ruleset, 0);
+
+	mutex_lock(&ruleset->lock);
+	err = landlock_insert_rule(ruleset, id, access_rights);
+	mutex_unlock(&ruleset->lock);
+
+	return err;
+}
+
+static int check_socket_access(const struct landlock_ruleset *const domain,
+			       u16 port, access_mask_t access_request)
+{
+	bool allowed = false;
+	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+	const struct landlock_rule *rule;
+	access_mask_t handled_access;
+	const struct landlock_id id = {
+		.key.data = port,
+		.type = LANDLOCK_KEY_NET_PORT,
+	};
+
+	if (WARN_ON_ONCE(!domain))
+		return 0;
+	if (WARN_ON_ONCE(domain->num_layers < 1))
+		return -EACCES;
+
+	rule = landlock_find_rule(domain, id);
+	handled_access = init_layer_masks(domain, access_request, &layer_masks,
+					  LANDLOCK_KEY_NET_PORT);
+	allowed = unmask_layers(rule, handled_access, &layer_masks,
+				ARRAY_SIZE(layer_masks));
+
+	return allowed ? 0 : -EACCES;
+}
+
+static u16 get_port(const struct sockaddr *const address)
+{
+	/* Gets port value in host byte order. */
+	switch (address->sa_family) {
+	case AF_UNSPEC:
+	case AF_INET: {
+		const struct sockaddr_in *const sockaddr =
+			(struct sockaddr_in *)address;
+		return ntohs(sockaddr->sin_port);
+	}
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6: {
+		const struct sockaddr_in6 *const sockaddr_ip6 =
+			(struct sockaddr_in6 *)address;
+		return ntohs(sockaddr_ip6->sin6_port);
+	}
+#endif
+	}
+	WARN_ON_ONCE(1);
+	return 0;
+}
+
+static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
+			    int addrlen)
+{
+	const struct landlock_ruleset *const dom =
+		landlock_get_current_domain();
+
+	if (!dom)
+		return 0;
+
+	/* Check if it's a TCP socket. */
+	if (sock->type != SOCK_STREAM)
+		return 0;
+
+	switch (address->sa_family) {
+	case AF_UNSPEC:
+	case AF_INET:
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6:
+#endif
+		return check_socket_access(dom, get_port(address),
+					   LANDLOCK_ACCESS_NET_BIND_TCP);
+	default:
+		return 0;
+	}
+}
+
+static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
+			       int addrlen)
+{
+	const struct landlock_ruleset *const dom =
+		landlock_get_current_domain();
+
+	if (!dom)
+		return 0;
+
+	/* Check if it's a TCP socket. */
+	if (sock->type != SOCK_STREAM)
+		return 0;
+
+	/* Check if the hook is AF_INET* socket's action. */
+	switch (address->sa_family) {
+	case AF_INET:
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6:
+#endif
+		return check_socket_access(dom, get_port(address),
+					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
+	case AF_UNSPEC: {
+		u16 i;
+
+		/*
+		 * If just in a layer a mask supports connect access,
+		 * the socket_connect() hook with AF_UNSPEC family flag
+		 * must be banned. This prevents from disconnecting already
+		 * connected sockets.
+		 */
+		for (i = 0; i < dom->num_layers; i++) {
+			if (landlock_get_net_access_mask(dom, i) &
+			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
+				return -EACCES;
+		}
+	}
+	}
+	return 0;
+}
+
+static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
+	LSM_HOOK_INIT(socket_bind, hook_socket_bind),
+	LSM_HOOK_INIT(socket_connect, hook_socket_connect),
+};
+
+__init void landlock_add_net_hooks(void)
+{
+	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+			   LANDLOCK_NAME);
+}
diff --git a/security/landlock/net.h b/security/landlock/net.h
new file mode 100644
index 000000000000..0da1d9dff5ab
--- /dev/null
+++ b/security/landlock/net.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022 Huawei Tech. Co., Ltd.
+ */
+
+#ifndef _SECURITY_LANDLOCK_NET_H
+#define _SECURITY_LANDLOCK_NET_H
+
+#include "common.h"
+#include "ruleset.h"
+#include "setup.h"
+
+#if IS_ENABLED(CONFIG_INET)
+__init void landlock_add_net_hooks(void);
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+			     const u16 port, access_mask_t access_rights);
+#else /* IS_ENABLED(CONFIG_INET) */
+static inline void landlock_add_net_hooks(void)
+{
+}
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+#endif /* _SECURITY_LANDLOCK_NET_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 3f196d2ce4f9..7e4a598177b8 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -14,6 +14,7 @@
 #include "fs.h"
 #include "ptrace.h"
 #include "setup.h"
+#include "net.h"

 bool landlock_initialized __lsm_ro_after_init = false;

@@ -29,6 +30,7 @@ static int __init landlock_init(void)
 	landlock_add_cred_hooks();
 	landlock_add_ptrace_hooks();
 	landlock_add_fs_hooks();
+	landlock_add_net_hooks();
 	landlock_initialized = true;
 	pr_info("Up and running.\n");
 	return 0;
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index c5a6ad4e2fca..7853f32e8325 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -29,6 +29,7 @@
 #include "cred.h"
 #include "fs.h"
 #include "limits.h"
+#include "net.h"
 #include "ruleset.h"
 #include "setup.h"

@@ -74,7 +75,8 @@ static void build_check_abi(void)
 {
 	struct landlock_ruleset_attr ruleset_attr;
 	struct landlock_path_beneath_attr path_beneath_attr;
-	size_t ruleset_size, path_beneath_size;
+	struct landlock_net_service_attr net_service_attr;
+	size_t ruleset_size, path_beneath_size, net_service_size;

 	/*
 	 * For each user space ABI structures, first checks that there is no
@@ -90,6 +92,11 @@ static void build_check_abi(void)
 	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
 	BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
 	BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
+
+	net_service_size = sizeof(net_service_attr.allowed_access);
+	net_service_size += sizeof(net_service_attr.port);
+	BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size);
+	BUILD_BUG_ON(sizeof(net_service_attr) != 10);
 }

 /* Ruleset handling */
@@ -322,13 +329,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
 	return err;
 }

+static int add_rule_net_service(struct landlock_ruleset *ruleset,
+				const void __user *const rule_attr)
+{
+#if IS_ENABLED(CONFIG_INET)
+	struct landlock_net_service_attr net_service_attr;
+	int res;
+	u32 mask;
+
+	/* Copies raw user space buffer, only one type for now. */
+	res = copy_from_user(&net_service_attr, rule_attr,
+			     sizeof(net_service_attr));
+	if (res)
+		return -EFAULT;
+
+	/*
+	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
+	 * are ignored by network actions.
+	 */
+	if (!net_service_attr.allowed_access)
+		return -ENOMSG;
+
+	/*
+	 * Checks that allowed_access matches the @ruleset constraints
+	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
+	 */
+	mask = landlock_get_net_access_mask(ruleset, 0);
+	if ((net_service_attr.allowed_access | mask) != mask)
+		return -EINVAL;
+
+	/* Denies inserting a rule with port 0. */
+	if (net_service_attr.port == 0)
+		return -EINVAL;
+
+	/* Imports the new rule. */
+	return landlock_append_net_rule(ruleset, net_service_attr.port,
+					net_service_attr.allowed_access);
+#else /* IS_ENABLED(CONFIG_INET) */
+	return -EAFNOSUPPORT;
+#endif /* IS_ENABLED(CONFIG_INET) */
+}
+
 /**
  * sys_landlock_add_rule - Add a new rule to a ruleset
  *
  * @ruleset_fd: File descriptor tied to the ruleset that should be extended
  *		with the new rule.
- * @rule_type: Identify the structure type pointed to by @rule_attr (only
- *             %LANDLOCK_RULE_PATH_BENEATH for now).
+ * @rule_type: Identify the structure type pointed to by @rule_attr:
+ *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_SERVICE.
  * @rule_attr: Pointer to a rule (only of type &struct
  *             landlock_path_beneath_attr for now).
  * @flags: Must be 0.
@@ -339,6 +387,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
  * Possible returned errors are:
  *
  * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - %EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not
+ *   supported by the running kernel;
  * - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
  *   &landlock_path_beneath_attr.allowed_access is not a subset of the
  *   ruleset handled accesses);
@@ -373,6 +423,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	case LANDLOCK_RULE_PATH_BENEATH:
 		err = add_rule_path_beneath(ruleset, rule_attr);
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		err = add_rule_net_service(ruleset, rule_attr);
+		break;
 	default:
 		err = -EINVAL;
 		break;
--
2.25.1


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

* [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset()
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (7 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 08/12] landlock: Implement TCP network hooks Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:43   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

This commit moves enforce_ruleset() helper function to common.h so that
to be used both by filesystem tests and network ones.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Refactors commit message.

Changes since v6:
* None.

Changes since v5:
* Splits commit.
* Moves enforce_ruleset helper into common.h
* Formats code with clang-format-14.

---
 tools/testing/selftests/landlock/common.h  | 10 ++++++++++
 tools/testing/selftests/landlock/fs_test.c | 10 ----------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index d7987ae8d7fc..bafed1c0c2a6 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -256,3 +256,13 @@ static int __maybe_unused send_fd(int usock, int fd_tx)
 		return -errno;
 	return 0;
 }
+
+__attribute__((__unused__)) static void
+enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
+{
+	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
+	{
+		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
+	}
+}
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index d5dab986f612..20c1ac8485f1 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -563,16 +563,6 @@ static int create_ruleset(struct __test_metadata *const _metadata,
 	return ruleset_fd;
 }

-static void enforce_ruleset(struct __test_metadata *const _metadata,
-			    const int ruleset_fd)
-{
-	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
-	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
-	{
-		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
-	}
-}
-
 TEST_F_FORK(layout1, proc_nsfs)
 {
 	const struct rule rules[] = {
--
2.25.1


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

* [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (8 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2023-01-09 12:46   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 11/12] samples/landlock: Add network demo Konstantin Meskhidze
  2022-10-21 15:26 ` [PATCH v8 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

These test suites try to check edge cases for TCP sockets
bind() and connect() actions.

socket:
* bind_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
* bind_with_restrictions: Tests with mixed landlock rules for ipv4 and
ipv6 sockets.
* connect_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
* connect_with_restrictions: Tests with mixed landlock rules for ipv4 and
ipv6 sockets.
* connect_afunspec_no_restrictions: Tests with no landlock restrictions
allowing to disconnect already connected socket with AF_UNSPEC socket
family.
* connect_afunspec_with_restrictions: Tests with landlocked process
refusing to disconnect already connected socket.
* ruleset_overlap: Tests with overlapping rules for one port.
* ruleset_expanding: Tests with expanding rulesets in which rules are
gradually added one by one, restricting sockets' connections.
* inval: Tests with invalid user space supplied data:
    - out of range ruleset attribute;
    - unhandled allowed access;
    - zero port value;
    - zero access value;
    - legitimate access values;

layout1:
* with_net: Tests with network bind() socket action within
filesystem directory access test.

Test coverage for security/landlock is 94.3% of 920 lines according
to gcc/gcov-11.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Squashes all selftest commits.
* Adds fs test with network bind() socket action.
* Minor fixes.

---
 security/landlock/ruleset.h                 |   2 -
 tools/testing/selftests/landlock/config     |   4 +
 tools/testing/selftests/landlock/fs_test.c  |  65 ++
 tools/testing/selftests/landlock/net_test.c | 823 ++++++++++++++++++++
 4 files changed, 892 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/net_test.c

diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f272d2cd518c..ee1a02a404ce 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -264,7 +264,6 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,

 	/* Should already be checked in sys_landlock_create_ruleset(). */
 	WARN_ON_ONCE(fs_access_mask != fs_mask);
-	// TODO: Add tests to check "|=" and not "="
 	ruleset->access_masks[layer_level] |=
 		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
 }
@@ -278,7 +277,6 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,

 	/* Should already be checked in sys_landlock_create_ruleset(). */
 	WARN_ON_ONCE(net_access_mask != net_mask);
-	// TODO: Add tests to check "|=" and not "="
 	ruleset->access_masks[layer_level] |=
 		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
 }
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 0f0a65287bac..71f7e9a8a64c 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -1,3 +1,7 @@
+CONFIG_INET=y
+CONFIG_IPV6=y
+CONFIG_NET=y
+CONFIG_NET_NS=y
 CONFIG_OVERLAY_FS=y
 CONFIG_SECURITY_LANDLOCK=y
 CONFIG_SECURITY_PATH=y
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 20c1ac8485f1..5c52da1a5a69 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -8,14 +8,17 @@
  */

 #define _GNU_SOURCE
+#include <arpa/inet.h>
 #include <fcntl.h>
 #include <linux/landlock.h>
+#include <netinet/in.h>
 #include <sched.h>
 #include <string.h>
 #include <sys/capability.h>
 #include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/sendfile.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
@@ -4366,4 +4369,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
 	}
 }

+#define IP_ADDRESS "127.0.0.1"
+
+TEST_F_FORK(layout1, with_net)
+{
+	int sockfd;
+	int sock_port = 15000;
+	struct sockaddr_in addr4;
+
+	addr4.sin_family = AF_INET;
+	addr4.sin_port = htons(sock_port);
+	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
+	memset(&addr4.sin_zero, '\0', 8);
+
+	const struct rule rules[] = {
+		{
+			.path = dir_s1d2,
+			.access = ACCESS_RO,
+		},
+		{},
+	};
+
+	struct landlock_ruleset_attr ruleset_attr_net = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = sock_port,
+	};
+
+	/* Creates ruleset for network access. */
+	const int ruleset_fd_net = landlock_create_ruleset(
+		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
+	ASSERT_LE(0, ruleset_fd_net);
+
+	/* Adds a network rule. */
+	ASSERT_EQ(0,
+		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
+				    &net_service, 0));
+
+	enforce_ruleset(_metadata, ruleset_fd_net);
+	ASSERT_EQ(0, close(ruleset_fd_net));
+
+	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/* Tests on a directory with the network rule loaded. */
+	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
+	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
+
+	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port 15000. */
+	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
+
+	/* Closes bounded socket. */
+	ASSERT_EQ(0, close(sockfd));
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
new file mode 100644
index 000000000000..d1548bd7ab60
--- /dev/null
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -0,0 +1,823 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock tests - Network
+ *
+ * Copyright (C) 2022 Huawei Tech. Co., Ltd.
+ */
+
+#define _GNU_SOURCE
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <netinet/in.h>
+#include <sched.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "common.h"
+
+#define MAX_SOCKET_NUM 10
+
+#define SOCK_PORT_START 3470
+#define SOCK_PORT_ADD 10
+
+#define IP_ADDRESS "127.0.0.1"
+
+/* Number pending connections queue to be hold. */
+#define BACKLOG 10
+
+const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
+
+/* Invalid attribute, out of landlock network access range. */
+#define LANDLOCK_INVAL_ATTR 7
+
+FIXTURE(socket)
+{
+	uint port[MAX_SOCKET_NUM];
+	struct sockaddr_in addr4[MAX_SOCKET_NUM];
+	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
+};
+
+/* struct _fixture_variant_socket */
+FIXTURE_VARIANT(socket)
+{
+	const bool is_ipv4;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket, ipv4) {
+	/* clang-format on */
+	.is_ipv4 = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket, ipv6) {
+	/* clang-format on */
+	.is_ipv4 = false,
+};
+
+static int
+create_socket_variant(const struct _fixture_variant_socket *const variant,
+		      const int type)
+{
+	if (variant->is_ipv4)
+		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
+	else
+		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
+}
+
+static int bind_variant(const struct _fixture_variant_socket *const variant,
+			const int sockfd,
+			const struct _test_data_socket *const self,
+			const size_t index)
+{
+	if (variant->is_ipv4)
+		return bind(sockfd, &self->addr4[index],
+			    sizeof(self->addr4[index]));
+	else
+		return bind(sockfd, &self->addr6[index],
+			    sizeof(self->addr6[index]));
+}
+
+static int connect_variant(const struct _fixture_variant_socket *const variant,
+			   const int sockfd,
+			   const struct _test_data_socket *const self,
+			   const size_t index)
+{
+	if (variant->is_ipv4)
+		return connect(sockfd, &self->addr4[index],
+			       sizeof(self->addr4[index]));
+	else
+		return connect(sockfd, &self->addr6[index],
+			       sizeof(self->addr6[index]));
+}
+
+FIXTURE_SETUP(socket)
+{
+	int i;
+
+	/* Creates IPv4 socket addresses. */
+	for (i = 0; i < MAX_SOCKET_NUM; i++) {
+		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
+		self->addr4[i].sin_family = AF_INET;
+		self->addr4[i].sin_port = htons(self->port[i]);
+		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS);
+		memset(&(self->addr4[i].sin_zero), '\0', 8);
+	}
+
+	/* Creates IPv6 socket addresses. */
+	for (i = 0; i < MAX_SOCKET_NUM; i++) {
+		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
+		self->addr6[i].sin6_family = AF_INET6;
+		self->addr6[i].sin6_port = htons(self->port[i]);
+		inet_pton(AF_INET6, IP_ADDRESS, &(self->addr6[i].sin6_addr));
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNET));
+	ASSERT_EQ(0, system("ip link set dev lo up"));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+}
+
+FIXTURE_TEARDOWN(socket)
+{
+}
+
+TEST_F_FORK(socket, bind_no_restrictions)
+{
+	int sockfd;
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds a socket to port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	ASSERT_EQ(0, close(sockfd));
+}
+
+TEST_F_FORK(socket, bind_with_restrictions)
+{
+	int sockfd;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = self->port[0],
+	};
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = self->port[1],
+	};
+	struct landlock_net_service_attr net_service_3 = {
+		.allowed_access = 0,
+		.port = self->port[2],
+	};
+
+	const int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Allows connect and bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+	/* Allows connect and deny bind operations to the port[1] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+	/*
+	 * Empty allowed_access (i.e. deny rules) are ignored in network actions
+	 * for port[2] socket.
+	 */
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+					&net_service_3, 0));
+	ASSERT_EQ(ENOMSG, errno);
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	/* Closes bounded socket. */
+	ASSERT_EQ(0, close(sockfd));
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[1]. */
+	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 1));
+	ASSERT_EQ(EACCES, errno);
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[2]. */
+	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2));
+	ASSERT_EQ(EACCES, errno);
+}
+
+TEST_F_FORK(socket, connect_no_restrictions)
+{
+	int sockfd, new_fd;
+	pid_t child;
+	int status;
+
+	/* Creates a server socket. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds a socket to port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	/* Makes listening socket. */
+	ASSERT_EQ(0, listen(sockfd, BACKLOG));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		int child_sockfd;
+
+		/* Closes listening socket for the child. */
+		ASSERT_EQ(0, close(sockfd));
+		/* Creates a stream client socket. */
+		child_sockfd = create_socket_variant(variant, SOCK_STREAM);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket with port[0]. */
+		ASSERT_EQ(0, connect_variant(variant, child_sockfd, self, 0));
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+	/* Accepts connection from the child. */
+	new_fd = accept(sockfd, NULL, 0);
+	ASSERT_LE(0, new_fd);
+
+	/* Closes connection. */
+	ASSERT_EQ(0, close(new_fd));
+
+	/* Closes listening socket for the parent. */
+	ASSERT_EQ(0, close(sockfd));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F_FORK(socket, connect_with_restrictions)
+{
+	int new_fd;
+	int sockfd_1, sockfd_2;
+	pid_t child_1, child_2;
+	int status;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = self->port[0],
+	};
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = self->port[1],
+	};
+
+	const int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Allows connect and bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+	/* Allows connect and deny bind operations to the port[1] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+
+	/* Creates a server socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0));
+
+	/* Makes listening socket 1. */
+	ASSERT_EQ(0, listen(sockfd_1, BACKLOG));
+
+	child_1 = fork();
+	ASSERT_LE(0, child_1);
+	if (child_1 == 0) {
+		int child_sockfd;
+
+		/* Closes listening socket for the child. */
+		ASSERT_EQ(0, close(sockfd_1));
+		/* Creates a stream client socket. */
+		child_sockfd = create_socket_variant(variant, SOCK_STREAM);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket with port[0]. */
+		ASSERT_EQ(0, connect_variant(variant, child_sockfd, self, 0));
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+	/* Accepts connection from the child 1. */
+	new_fd = accept(sockfd_1, NULL, 0);
+	ASSERT_LE(0, new_fd);
+
+	/* Closes connection. */
+	ASSERT_EQ(0, close(new_fd));
+
+	/* Closes listening socket 1 for the parent. */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	ASSERT_EQ(child_1, waitpid(child_1, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+	/* Creates a server socket 2. */
+	sockfd_2 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_2);
+
+	/* Binds the socket 2 to address with port[1]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_2, self, 1));
+
+	/* Makes listening socket 2. */
+	ASSERT_EQ(0, listen(sockfd_2, BACKLOG));
+
+	child_2 = fork();
+	ASSERT_LE(0, child_2);
+	if (child_2 == 0) {
+		int child_sockfd;
+
+		/* Closes listening socket for the child. */
+		ASSERT_EQ(0, close(sockfd_2));
+		/* Creates a stream client socket. */
+		child_sockfd = create_socket_variant(variant, SOCK_STREAM);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket with port[1]. */
+		ASSERT_EQ(-1, connect_variant(variant, child_sockfd, self, 1));
+		ASSERT_EQ(EACCES, errno);
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+
+	/* Closes listening socket 2 for the parent. */
+	ASSERT_EQ(0, close(sockfd_2));
+
+	ASSERT_EQ(child_2, waitpid(child_2, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F_FORK(socket, connect_afunspec_no_restrictions)
+{
+	int sockfd;
+	pid_t child;
+	int status;
+
+	/* Creates a server socket 1. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	/* Makes connection to the socket with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		/* Child tries to disconnect already connected socket. */
+		ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&addr_unspec,
+				     sizeof(addr_unspec)));
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+	/* Closes listening socket 1 for the parent. */
+	ASSERT_EQ(0, close(sockfd));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F_FORK(socket, connect_afunspec_with_restrictions)
+{
+	int sockfd;
+	pid_t child;
+	int status;
+
+	struct landlock_ruleset_attr ruleset_attr_1 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = self->port[0],
+	};
+
+	struct landlock_ruleset_attr ruleset_attr_2 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+
+		.port = self->port[0],
+	};
+
+	const int ruleset_fd_1 = landlock_create_ruleset(
+		&ruleset_attr_1, sizeof(ruleset_attr_1), 0);
+	ASSERT_LE(0, ruleset_fd_1);
+
+	/* Allows bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_1);
+
+	/* Creates a server socket 1. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	/* Makes connection to socket with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0));
+
+	const int ruleset_fd_2 = landlock_create_ruleset(
+		&ruleset_attr_2, sizeof(ruleset_attr_2), 0);
+	ASSERT_LE(0, ruleset_fd_2);
+
+	/* Allows connect and bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_2);
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		/* Child tries to disconnect already connected socket. */
+		ASSERT_EQ(-1, connect(sockfd, (struct sockaddr *)&addr_unspec,
+				      sizeof(addr_unspec)));
+		ASSERT_EQ(EACCES, errno);
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+	/* Closes listening socket 1 for the parent. */
+	ASSERT_EQ(0, close(sockfd));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F_FORK(socket, ruleset_overlap)
+{
+	int sockfd;
+	int one = 1;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = self->port[0],
+	};
+
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+
+		.port = self->port[0],
+	};
+
+	int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Allows bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+	/* Allows connect and bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+
+	/* Creates a server socket. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	/* Makes connection to socket with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0));
+
+	/* Closes socket. */
+	ASSERT_EQ(0, close(sockfd));
+
+	/* Creates another ruleset layer. */
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/*
+	 * Allows bind operations to the port[0] socket in
+	 * the new ruleset layer.
+	 */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+
+	/* Enforces the new ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+
+	/* Creates a server socket. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
+
+	/*
+	 * Forbids to connect the socket to address with port[0],
+	 * as just one ruleset layer has connect() access rule.
+	 */
+	ASSERT_EQ(-1, connect_variant(variant, sockfd, self, 0));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Closes socket. */
+	ASSERT_EQ(0, close(sockfd));
+}
+
+TEST_F_FORK(socket, ruleset_expanding)
+{
+	int sockfd_1, sockfd_2;
+	int one = 1;
+
+	struct landlock_ruleset_attr ruleset_attr_1 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = self->port[0],
+	};
+
+	const int ruleset_fd_1 = landlock_create_ruleset(
+		&ruleset_attr_1, sizeof(ruleset_attr_1), 0);
+	ASSERT_LE(0, ruleset_fd_1);
+
+	/* Adds rule to port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_1);
+	ASSERT_EQ(0, close(ruleset_fd_1));
+
+	/* Creates a socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0));
+
+	/* Makes connection to socket 1 with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd_1, self, 0));
+
+	/* Closes socket 1. */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	/* Creates a socket 2. */
+	sockfd_2 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_2);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_2, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/*
+	 * Forbids to bind the socket 2 to address with port[1],
+	 * since there is no rule with bind() access for port[1].
+	 */
+	ASSERT_EQ(-1, bind_variant(variant, sockfd_2, self, 1));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Expands network mask. */
+	struct landlock_ruleset_attr ruleset_attr_2 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+
+	/* Adds connect() access to port[0]. */
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+
+		.port = self->port[0],
+	};
+	/* Adds bind() access to port[1]. */
+	struct landlock_net_service_attr net_service_3 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = self->port[1],
+	};
+
+	const int ruleset_fd_2 = landlock_create_ruleset(
+		&ruleset_attr_2, sizeof(ruleset_attr_2), 0);
+	ASSERT_LE(0, ruleset_fd_2);
+
+	/* Adds rule to port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+	/* Adds rule to port[1] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_3, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_2);
+	ASSERT_EQ(0, close(ruleset_fd_2));
+
+	/* Creates a socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0));
+
+	/* Makes connection to socket 1 with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd_1, self, 0));
+
+	/* Closes socket 1. */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	/* Creates a socket 2. */
+	sockfd_2 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_2);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_2, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/*
+	 * Forbids to bind the socket 2 to address with port[1],
+	 * because just one layer has bind() access rule.
+	 */
+	ASSERT_EQ(-1, bind_variant(variant, sockfd_1, self, 1));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Expands network mask. */
+	struct landlock_ruleset_attr ruleset_attr_3 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+
+	/* Restricts connect() access to port[0]. */
+	struct landlock_net_service_attr net_service_4 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = self->port[0],
+	};
+
+	const int ruleset_fd_3 = landlock_create_ruleset(
+		&ruleset_attr_3, sizeof(ruleset_attr_3), 0);
+	ASSERT_LE(0, ruleset_fd_3);
+
+	/* Adds rule to port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_3, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_4, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_3);
+	ASSERT_EQ(0, close(ruleset_fd_3));
+
+	/* Creates a socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0));
+
+	/*
+	 * Forbids to connect the socket 1 to address with port[0],
+	 * as just one layer has connect() access rule.
+	 */
+	ASSERT_EQ(-1, connect_variant(variant, sockfd_1, self, 0));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Closes socket 1. */
+	ASSERT_EQ(0, close(sockfd_1));
+}
+
+/* clang-format off */
+
+#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
+
+#define ACCESS_ALL ( \
+	LANDLOCK_ACCESS_NET_BIND_TCP | \
+	LANDLOCK_ACCESS_NET_CONNECT_TCP)
+
+/* clang-format on */
+
+TEST_F_FORK(socket, inval)
+{
+	__u64 access;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP
+	};
+	struct landlock_ruleset_attr ruleset_attr_inval = {
+		.handled_access_net = LANDLOCK_INVAL_ATTR
+	};
+	struct landlock_ruleset_attr ruleset_attr_all = { .handled_access_net =
+								  ACCESS_ALL };
+
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = self->port[0],
+	};
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = 0,
+	};
+	struct landlock_net_service_attr net_service_3 = {
+		.allowed_access = 0,
+		.port = self->port[1],
+	};
+	struct landlock_net_service_attr net_service_4 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = self->port[2],
+	};
+
+	struct landlock_net_service_attr net_service_5 = {};
+
+	/* Checks invalid ruleset attribute. */
+	const int ruleset_fd_inv = landlock_create_ruleset(
+		&ruleset_attr_inval, sizeof(ruleset_attr_inval), 0);
+	ASSERT_EQ(-1, ruleset_fd_inv);
+	ASSERT_EQ(EINVAL, errno);
+
+	/* Gets ruleset. */
+	const int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Checks unhandled allowed_access. */
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+					&net_service_1, 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	/* Checks zero port value. */
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+					&net_service_2, 0));
+	ASSERT_EQ(EINVAL, errno);
+
+	/* Checks zero access value. */
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+					&net_service_3, 0));
+	ASSERT_EQ(ENOMSG, errno);
+
+	/* Adds with legitimate values. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_4, 0));
+
+	const int ruleset_fd_all = landlock_create_ruleset(
+		&ruleset_attr_all, sizeof(ruleset_attr_all), 0);
+
+	ASSERT_LE(0, ruleset_fd_all);
+
+	/* Tests access rights for all network rules */
+	for (access = 1; access <= ACCESS_LAST; access <<= 1) {
+		net_service_5.allowed_access = access;
+		net_service_5.port = self->port[3];
+
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd_all,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_5, 0));
+	}
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	enforce_ruleset(_metadata, ruleset_fd_all);
+	ASSERT_EQ(0, close(ruleset_fd_all));
+}
+TEST_HARNESS_MAIN
--
2.25.1


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

* [PATCH v8 11/12] samples/landlock: Add network demo
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (9 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-16 14:25   ` Mickaël Salaün
  2022-10-21 15:26 ` [PATCH v8 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

This commit adds network demo. It's possible to allow a sandboxer to
bind/connect to a list of particular ports restricting network
actions to the rest of ports.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Removes network support if ABI < 4.
* Removes network support if not set by a user.

Changes since v6:
* Removes network support if ABI < 3.

Changes since v5:
* Makes network ports sandboxing optional.
* Fixes some logic errors.
* Formats code with clang-format-14.

Changes since v4:
* Adds ENV_TCP_BIND_NAME "LL_TCP_BIND" and
ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" variables
to insert TCP ports.
* Renames populate_ruleset() to populate_ruleset_fs().
* Adds populate_ruleset_net() and parse_port_num() helpers.
* Refactors main() to support network sandboxing.

---
 samples/landlock/sandboxer.c | 129 +++++++++++++++++++++++++++++++----
 1 file changed, 116 insertions(+), 13 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index fd4237c64fb2..68582b0d7c85 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -51,6 +51,8 @@ static inline int landlock_restrict_self(const int ruleset_fd,

 #define ENV_FS_RO_NAME "LL_FS_RO"
 #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_PATH_TOKEN ":"

 static int parse_path(char *env_path, const char ***const path_list)
@@ -71,6 +73,20 @@ static int parse_path(char *env_path, const char ***const path_list)
 	return num_paths;
 }

+static int parse_port_num(char *env_port)
+{
+	int i, num_ports = 0;
+
+	if (env_port) {
+		num_ports++;
+		for (i = 0; env_port[i]; i++) {
+			if (env_port[i] == ENV_PATH_TOKEN[0])
+				num_ports++;
+		}
+	}
+	return num_ports;
+}
+
 /* clang-format off */

 #define ACCESS_FILE ( \
@@ -81,8 +97,8 @@ static int parse_path(char *env_path, const char ***const path_list)

 /* clang-format on */

-static int populate_ruleset(const char *const env_var, const int ruleset_fd,
-			    const __u64 allowed_access)
+static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
+			       const __u64 allowed_access)
 {
 	int num_paths, i, ret = 1;
 	char *env_path_name;
@@ -143,6 +159,48 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
 	return ret;
 }

+static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
+				const __u64 allowed_access)
+{
+	int num_ports, i, ret = 1;
+	char *env_port_name;
+	struct landlock_net_service_attr net_service = {
+		.allowed_access = 0,
+		.port = 0,
+	};
+
+	env_port_name = getenv(env_var);
+	if (!env_port_name) {
+		ret = 0;
+		goto out_free_name;
+	}
+	env_port_name = strdup(env_port_name);
+	unsetenv(env_var);
+	num_ports = parse_port_num(env_port_name);
+
+	if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) {
+		ret = 0;
+		goto out_free_name;
+	}
+
+	for (i = 0; i < num_ports; i++) {
+		net_service.allowed_access = allowed_access;
+		net_service.port = atoi(strsep(&env_port_name, ENV_PATH_TOKEN));
+		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				      &net_service, 0)) {
+			fprintf(stderr,
+				"Failed to update the ruleset with port \"%d\": %s\n",
+				net_service.port, strerror(errno));
+			goto out_free_name;
+		}
+	}
+	ret = 0;
+
+out_free_name:
+	free(env_port_name);
+	return ret;
+}
+
 /* clang-format off */

 #define ACCESS_FS_ROUGHLY_READ ( \
@@ -164,41 +222,63 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
 	LANDLOCK_ACCESS_FS_REFER | \
 	LANDLOCK_ACCESS_FS_TRUNCATE)

+#define ACCESS_NET_BIND_CONNECT ( \
+	LANDLOCK_ACCESS_NET_BIND_TCP | \
+	LANDLOCK_ACCESS_NET_CONNECT_TCP)
+
 /* clang-format on */

-#define LANDLOCK_ABI_LAST 3
+#define LANDLOCK_ABI_LAST 4

 int main(const int argc, char *const argv[], char *const *const envp)
 {
 	const char *cmd_path;
 	char *const *cmd_argv;
 	int ruleset_fd, abi;
+	char *env_port_name;
 	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
-	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
+	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE,
+	      access_net_tcp = ACCESS_NET_BIND_CONNECT;
 	struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = access_fs_rw,
+		.handled_access_net = access_net_tcp,
 	};

 	if (argc < 2) {
 		fprintf(stderr,
-			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
-			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
+			"usage: %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]);
 		fprintf(stderr,
 			"Launch a command in a restricted environment.\n\n");
-		fprintf(stderr, "Environment variables containing paths, "
-				"each separated by a colon:\n");
+		fprintf(stderr,
+			"Environment variables containing paths and ports "
+			"each separated by a colon:\n");
 		fprintf(stderr,
 			"* %s: list of paths allowed to be used in a read-only way.\n",
 			ENV_FS_RO_NAME);
 		fprintf(stderr,
-			"* %s: list of paths allowed to be used in a read-write way.\n",
+			"* %s: list of paths allowed to be used in a read-write way.\n\n",
 			ENV_FS_RW_NAME);
+		fprintf(stderr,
+			"Environment variables containing ports are optional "
+			"and could be skipped.\n");
+		fprintf(stderr,
+			"* %s: list of ports allowed to bind (server).\n",
+			ENV_TCP_BIND_NAME);
+		fprintf(stderr,
+			"* %s: list of ports allowed to connect (client).\n",
+			ENV_TCP_CONNECT_NAME);
 		fprintf(stderr,
 			"\nexample:\n"
 			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
 			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
+			"%s=\"9418\" "
+			"%s=\"80:443\" "
 			"%s bash -i\n\n",
-			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
+			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
+			ENV_TCP_CONNECT_NAME, argv[0]);
 		fprintf(stderr,
 			"This sandboxer can use Landlock features "
 			"up to ABI version %d.\n",
@@ -240,7 +320,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	case 2:
 		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
 		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
-
+		__attribute__((fallthrough));
+	case 3:
+		/* Removes network support for ABI < 4 */
+		ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
 			"to leverage Landlock features "
@@ -259,16 +342,36 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	access_fs_ro &= ruleset_attr.handled_access_fs;
 	access_fs_rw &= ruleset_attr.handled_access_fs;

+	/* Removes bind access attribute if not supported by a user. */
+	env_port_name = getenv(ENV_TCP_BIND_NAME);
+	if (!env_port_name) {
+		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
+	}
+	/* Removes connect access attribute if not supported by a user. */
+	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
+	if (!env_port_name) {
+		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
+	}
+	ruleset_attr.handled_access_net &= access_net_tcp;
+
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	if (ruleset_fd < 0) {
 		perror("Failed to create a ruleset");
 		return 1;
 	}
-	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+		goto err_close_ruleset;
+	}
+	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+		goto err_close_ruleset;
+	}
+	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
+				 LANDLOCK_ACCESS_NET_BIND_TCP)) {
 		goto err_close_ruleset;
 	}
-	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
+				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
 		goto err_close_ruleset;
 	}
 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
--
2.25.1


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

* [PATCH v8 12/12] landlock: Document Landlock's network support
  2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (10 preceding siblings ...)
  2022-10-21 15:26 ` [PATCH v8 11/12] samples/landlock: Add network demo Konstantin Meskhidze
@ 2022-10-21 15:26 ` Konstantin Meskhidze
  2022-11-17 18:44   ` Mickaël Salaün
  11 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze @ 2022-10-21 15:26 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin

Describes network access rules for TCP sockets. Adds network access
example in the tutorial. Points out AF_UNSPEC socket family behaviour.
Adds kernel configuration support for network.

Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
---

Changes since v7:
* Fixes documentaion logic errors and typos as Mickaёl suggested:
https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/

Changes since v6:
* Adds network support documentaion.

---
 Documentation/userspace-api/landlock.rst | 72 +++++++++++++++++++-----
 1 file changed, 59 insertions(+), 13 deletions(-)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index d8cd8cd9ce25..d0610ec9ce05 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -11,10 +11,10 @@ Landlock: unprivileged access control
 :Date: October 2022

 The goal of Landlock is to enable to restrict ambient rights (e.g. global
-filesystem access) for a set of processes.  Because Landlock is a stackable
-LSM, it makes possible to create safe security sandboxes as new security layers
-in addition to the existing system-wide access-controls. This kind of sandbox
-is expected to help mitigate the security impact of bugs or
+filesystem or network access) for a set of processes.  Because Landlock
+is a stackable LSM, it makes possible to create safe security sandboxes as new
+security layers in addition to the existing system-wide access-controls. This
+kind of sandbox is expected to help mitigate the security impact of bugs or
 unexpected/malicious behaviors in user space applications.  Landlock empowers
 any process, including unprivileged ones, to securely restrict themselves.

@@ -30,18 +30,20 @@ Landlock rules

 A Landlock rule describes an action on an object.  An object is currently a
 file hierarchy, and the related filesystem actions are defined with `access
-rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
-the thread enforcing it, and its future children.
+rights`_.  Since ABI version 4 a port data appears with related network actions
+for TCP socket families.  A set of rules is aggregated in a ruleset, which
+can then restrict the thread enforcing it, and its future children.

 Defining and enforcing a security policy
 ----------------------------------------

 We first need to define the ruleset that will contain our rules.  For this
 example, the ruleset will contain rules that only allow read actions, but write
-actions will be denied.  The ruleset then needs to handle both of these kind of
+actions will be denied. The ruleset then needs to handle both of these kind of
 actions.  This is required for backward and forward compatibility (i.e. the
 kernel and user space may not know each other's supported restrictions), hence
-the need to be explicit about the denied-by-default access rights.
+the need to be explicit about the denied-by-default access rights.  Also ruleset
+will have network rules for specific ports, so it should handle network actions.

 .. code-block:: c

@@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
             LANDLOCK_ACCESS_FS_MAKE_SYM |
             LANDLOCK_ACCESS_FS_REFER |
             LANDLOCK_ACCESS_FS_TRUNCATE,
+        .handled_access_net =
+            LANDLOCK_ACCESS_NET_BIND_TCP |
+            LANDLOCK_ACCESS_NET_CONNECT_TCP,
     };

 Because we may not know on which kernel version an application will be
@@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
 using.  To avoid binary enforcement (i.e. either all security features or
 none), we can leverage a dedicated Landlock command to get the current version
 of the Landlock ABI and adapt the handled accesses.  Let's check if we should
-remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
-access rights, which are only supported starting with the second and third
-version of the ABI.
+remove the `LANDLOCK_ACCESS_FS_REFER` or `LANDLOCK_ACCESS_FS_TRUNCATE` or
+network access rights, which are only supported starting with the second,
+third and fourth version of the ABI.

 .. code-block:: c

     int abi;

+    #define ACCESS_NET_BIND_CONNECT ( \
+    LANDLOCK_ACCESS_NET_BIND_TCP | \
+    LANDLOCK_ACCESS_NET_CONNECT_TCP)
+
     abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
     if (abi < 0) {
         /* Degrades gracefully if Landlock is not handled. */
@@ -92,6 +101,9 @@ version of the ABI.
     case 2:
         /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
         ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
+    case 3:
+        /* Removes network support for ABI < 4 */
+        ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;
     }

 This enables to create an inclusive ruleset that will contain our rules.
@@ -143,8 +155,22 @@ for the ruleset creation, by filtering access rights according to the Landlock
 ABI version.  In this example, this is not required because all of the requested
 ``allowed_access`` rights are already available in ABI 1.

+For network part we can add number of rules containing a port number and actions
+that a process is allowed to do for certian ports.
+
+.. code-block:: c
+
+    struct landlock_net_service_attr net_service = {
+        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+        .port = 8080,
+    };
+
+    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+                            &net_service, 0);
+
 We now have a ruleset with one rule allowing read access to ``/usr`` while
-denying all other handled accesses for the filesystem.  The next step is to
+denying all other handled accesses for the filesystem.  The ruleset also contains
+a rule allowing to bind current proccess to the port 8080.  The next step is to
 restrict the current thread from gaining more privileges (e.g. thanks to a SUID
 binary).

@@ -296,6 +322,13 @@ not.  It is also possible to pass such file descriptors between processes,
 keeping their Landlock properties, even when these processes do not have an
 enforced Landlock ruleset.

+AF_UNSPEC socket family
+-----------------------
+
+Sockets of AF_UNSPEC family types are treated as AF_INET(IPv4) socket for bind()
+action.  But connect() one is not allowed by Landlock for AF_UNSPEC sockets. This
+logic prevents from disconnecting already connected sockets.
+
 Compatibility
 =============

@@ -355,7 +388,7 @@ Access rights
 -------------

 .. kernel-doc:: include/uapi/linux/landlock.h
-    :identifiers: fs_access
+    :identifiers: fs_access net_access

 Creating a new ruleset
 ----------------------
@@ -374,6 +407,7 @@ Extending a ruleset

 .. kernel-doc:: include/uapi/linux/landlock.h
     :identifiers: landlock_rule_type landlock_path_beneath_attr
+                  landlock_net_service_attr

 Enforcing a ruleset
 -------------------
@@ -451,6 +485,13 @@ always allowed when using a kernel that only supports the first or second ABI.
 Starting with the Landlock ABI version 3, it is now possible to securely control
 truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.

+Network support (ABI < 4)
+-------------------------
+
+Starting with the Landlock ABI version 4, it is now possible to restrict TCP
+sockets' bind() and connect() actions for specific ports allowing processes
+to establish restricted connections.
+
 .. _kernel_support:

 Kernel support
@@ -469,6 +510,11 @@ still enable it by adding ``lsm=landlock,[...]`` to
 Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
 configuration.

+To be able to explicitly allow TCP operations (e.g., adding a network rule with
+`LANDLOCK_ACCESS_NET_TCP_BIND`), the kernel must support TCP (`CONFIG_INET=y`).
+Otherwise, sys_landlock_add_rule() returns an `EAFNOSUPPORT` error, which can
+safely be ignored because this kind of TCP operation is already not possible.
+
 Questions and answers
 =====================

--
2.25.1


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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2022-10-21 15:26 ` [PATCH v8 11/12] samples/landlock: Add network demo Konstantin Meskhidze
@ 2022-11-16 14:25   ` Mickaël Salaün
  2022-11-28  2:49     ` Konstantin Meskhidze (A)
  2023-01-05  3:46     ` Konstantin Meskhidze (A)
  0 siblings, 2 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-16 14:25 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> This commit adds network demo. It's possible to allow a sandboxer to
> bind/connect to a list of particular ports restricting network
> actions to the rest of ports.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Removes network support if ABI < 4.
> * Removes network support if not set by a user.
> 
> Changes since v6:
> * Removes network support if ABI < 3.
> 
> Changes since v5:
> * Makes network ports sandboxing optional.
> * Fixes some logic errors.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Adds ENV_TCP_BIND_NAME "LL_TCP_BIND" and
> ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" variables
> to insert TCP ports.
> * Renames populate_ruleset() to populate_ruleset_fs().
> * Adds populate_ruleset_net() and parse_port_num() helpers.
> * Refactors main() to support network sandboxing.
> 
> ---
>   samples/landlock/sandboxer.c | 129 +++++++++++++++++++++++++++++++----
>   1 file changed, 116 insertions(+), 13 deletions(-)
> 
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index fd4237c64fb2..68582b0d7c85 100644
> --- a/samples/landlock/sandboxer.c
> +++ b/samples/landlock/sandboxer.c
> @@ -51,6 +51,8 @@ static inline int landlock_restrict_self(const int ruleset_fd,
> 
>   #define ENV_FS_RO_NAME "LL_FS_RO"
>   #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_PATH_TOKEN ":"
> 
>   static int parse_path(char *env_path, const char ***const path_list)
> @@ -71,6 +73,20 @@ static int parse_path(char *env_path, const char ***const path_list)
>   	return num_paths;
>   }
> 
> +static int parse_port_num(char *env_port)
> +{
> +	int i, num_ports = 0;
> +
> +	if (env_port) {
> +		num_ports++;
> +		for (i = 0; env_port[i]; i++) {
> +			if (env_port[i] == ENV_PATH_TOKEN[0])
> +				num_ports++;
> +		}
> +	}
> +	return num_ports;
> +}
> +
>   /* clang-format off */
> 
>   #define ACCESS_FILE ( \
> @@ -81,8 +97,8 @@ static int parse_path(char *env_path, const char ***const path_list)
> 
>   /* clang-format on */
> 
> -static int populate_ruleset(const char *const env_var, const int ruleset_fd,
> -			    const __u64 allowed_access)
> +static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
> +			       const __u64 allowed_access)
>   {
>   	int num_paths, i, ret = 1;
>   	char *env_path_name;
> @@ -143,6 +159,48 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>   	return ret;
>   }
> 
> +static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
> +				const __u64 allowed_access)
> +{
> +	int num_ports, i, ret = 1;
> +	char *env_port_name;
> +	struct landlock_net_service_attr net_service = {
> +		.allowed_access = 0,
> +		.port = 0,
> +	};
> +
> +	env_port_name = getenv(env_var);
> +	if (!env_port_name) {
> +		ret = 0;
> +		goto out_free_name;

This is a bug because env_port_name is not allocated. This should simply 
return 0.


> +	}
> +	env_port_name = strdup(env_port_name);
> +	unsetenv(env_var);
> +	num_ports = parse_port_num(env_port_name);
> +
> +	if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) {
> +		ret = 0;
> +		goto out_free_name;
> +	}
> +
> +	for (i = 0; i < num_ports; i++) {
> +		net_service.allowed_access = allowed_access;
> +		net_service.port = atoi(strsep(&env_port_name, ENV_PATH_TOKEN));
> +		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
> +				      &net_service, 0)) {
> +			fprintf(stderr,
> +				"Failed to update the ruleset with port \"%d\": %s\n",
> +				net_service.port, strerror(errno));
> +			goto out_free_name;
> +		}
> +	}
> +	ret = 0;
> +
> +out_free_name:
> +	free(env_port_name);
> +	return ret;
> +}
> +
>   /* clang-format off */
> 
>   #define ACCESS_FS_ROUGHLY_READ ( \
> @@ -164,41 +222,63 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>   	LANDLOCK_ACCESS_FS_REFER | \
>   	LANDLOCK_ACCESS_FS_TRUNCATE)
> 
> +#define ACCESS_NET_BIND_CONNECT ( \
> +	LANDLOCK_ACCESS_NET_BIND_TCP | \
> +	LANDLOCK_ACCESS_NET_CONNECT_TCP)

You can remove ACCESS_NET_BIND_CONNECT and make the underlying access 
rights explicit.


> +
>   /* clang-format on */
> 
> -#define LANDLOCK_ABI_LAST 3
> +#define LANDLOCK_ABI_LAST 4
> 
>   int main(const int argc, char *const argv[], char *const *const envp)
>   {
>   	const char *cmd_path;
>   	char *const *cmd_argv;
>   	int ruleset_fd, abi;
> +	char *env_port_name;
>   	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
> -	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
> +	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE,
> +	      access_net_tcp = ACCESS_NET_BIND_CONNECT;
>   	struct landlock_ruleset_attr ruleset_attr = {
>   		.handled_access_fs = access_fs_rw,
> +		.handled_access_net = access_net_tcp,
>   	};
> 
>   	if (argc < 2) {
>   		fprintf(stderr,
> -			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
> -			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
> +			"usage: %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]);
>   		fprintf(stderr,
>   			"Launch a command in a restricted environment.\n\n");
> -		fprintf(stderr, "Environment variables containing paths, "
> -				"each separated by a colon:\n");
> +		fprintf(stderr,
> +			"Environment variables containing paths and ports "
> +			"each separated by a colon:\n");
>   		fprintf(stderr,
>   			"* %s: list of paths allowed to be used in a read-only way.\n",
>   			ENV_FS_RO_NAME);
>   		fprintf(stderr,
> -			"* %s: list of paths allowed to be used in a read-write way.\n",
> +			"* %s: list of paths allowed to be used in a read-write way.\n\n",
>   			ENV_FS_RW_NAME);
> +		fprintf(stderr,
> +			"Environment variables containing ports are optional "
> +			"and could be skipped.\n");
> +		fprintf(stderr,
> +			"* %s: list of ports allowed to bind (server).\n",
> +			ENV_TCP_BIND_NAME);
> +		fprintf(stderr,
> +			"* %s: list of ports allowed to connect (client).\n",
> +			ENV_TCP_CONNECT_NAME);
>   		fprintf(stderr,
>   			"\nexample:\n"
>   			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
>   			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> +			"%s=\"9418\" "
> +			"%s=\"80:443\" "
>   			"%s bash -i\n\n",
> -			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
> +			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
> +			ENV_TCP_CONNECT_NAME, argv[0]);
>   		fprintf(stderr,
>   			"This sandboxer can use Landlock features "
>   			"up to ABI version %d.\n",
> @@ -240,7 +320,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
>   	case 2:
>   		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>   		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
> -
> +		__attribute__((fallthrough));
> +	case 3:
> +		/* Removes network support for ABI < 4 */
> +		ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;

You can check the TCP environment variables here and error out if one is 
set.

Please keep the newline here.


>   		fprintf(stderr,
>   			"Hint: You should update the running kernel "
>   			"to leverage Landlock features "
> @@ -259,16 +342,36 @@ int main(const int argc, char *const argv[], char *const *const envp)
>   	access_fs_ro &= ruleset_attr.handled_access_fs;
>   	access_fs_rw &= ruleset_attr.handled_access_fs;
> 
> +	/* Removes bind access attribute if not supported by a user. */
> +	env_port_name = getenv(ENV_TCP_BIND_NAME);
> +	if (!env_port_name) {

You can move this logic at the populate_ruleset_net() call site and 
update this helper to not call getenv() twice for the same variable.


> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
> +	}
> +	/* Removes connect access attribute if not supported by a user. */
> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
> +	if (!env_port_name) {
> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
> +	}
> +	ruleset_attr.handled_access_net &= access_net_tcp;

There is no need for access_net_tcp.

> +
>   	ruleset_fd =
>   		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>   	if (ruleset_fd < 0) {
>   		perror("Failed to create a ruleset");
>   		return 1;
>   	}

newline

> -	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
> +	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
> +		goto err_close_ruleset;
> +	}
> +	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
> +		goto err_close_ruleset;
> +	}

newline

> +	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
> +				 LANDLOCK_ACCESS_NET_BIND_TCP)) {
>   		goto err_close_ruleset;
>   	}
> -	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
> +	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
> +				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
>   		goto err_close_ruleset;
>   	}

newline

>   	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
> --
> 2.25.1
> 

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

* Re: [PATCH v8 01/12] landlock: Make ruleset's access masks more generic
  2022-10-21 15:26 ` [PATCH v8 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
@ 2022-11-17 18:41   ` Mickaël Salaün
  2022-11-28  2:53     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:41 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> To support network type rules, this modification renames ruleset's
> access masks and modifies it's type to access_masks_t. This patch
> adds filesystem helper functions to add and get filesystem mask.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Refactors commit message.
> 
> Changes since v6:
> * Adds a new access_masks_t for struct ruleset.
> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>    because it OR values.
> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
> * Refactors landlock_get_fs_access_mask().
> 
> Changes since v6:
> * Adds a new access_masks_t for struct ruleset.
> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>    because it OR values.
> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
> * Refactors landlock_get_fs_access_mask().
> 
> Changes since v5:
> * Changes access_mask_t to u32.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Deletes struct landlock_access_mask.
> 
> Changes since v3:
> * Splits commit.
> * Adds get_mask, set_mask helpers for filesystem.
> * Adds new struct landlock_access_mask.
> 
> ---
>   security/landlock/fs.c       | 10 +++++-----
>   security/landlock/limits.h   |  1 +
>   security/landlock/ruleset.c  | 17 +++++++++--------
>   security/landlock/ruleset.h  | 35 +++++++++++++++++++++++++++++++----
>   security/landlock/syscalls.c |  7 ++++---
>   5 files changed, 50 insertions(+), 20 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index adcea0fe7e68..0d57c6479d29 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -178,9 +178,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>   		return -EINVAL;
> 
>   	/* Transforms relative access rights to absolute ones. */
> -	access_rights |=
> -		LANDLOCK_MASK_ACCESS_FS &
> -		~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
> +	access_rights |= LANDLOCK_MASK_ACCESS_FS &
> +			 ~(landlock_get_fs_access_mask(ruleset, 0) |
> +			   ACCESS_INITIALLY_DENIED);
>   	object = get_inode_object(d_backing_inode(path->dentry));
>   	if (IS_ERR(object))
>   		return PTR_ERR(object);
> @@ -294,7 +294,7 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
>   	size_t layer_level;
> 
>   	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> -		access_dom |= domain->fs_access_masks[layer_level];
> +		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;

You can remove `& LANDLOCK_MASK_ACCESS_FS` here because it is now part 
of landlock_get_fs_access_mask().


>   }
> 
> @@ -336,7 +336,7 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>   			 * access rights.
>   			 */
>   			if (BIT_ULL(access_bit) &
> -			    (domain->fs_access_masks[layer_level] |
> +			    (landlock_get_fs_access_mask(domain, layer_level) |
>   			     ACCESS_INITIALLY_DENIED)) {
>   				(*layer_masks)[access_bit] |=
>   					BIT_ULL(layer_level);
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index 82288f0e9e5e..bafb3b8dc677 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -21,6 +21,7 @@
>   #define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_TRUNCATE
>   #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
> +#define LANDLOCK_SHIFT_ACCESS_FS	0
> 
>   /* clang-format on */
> 
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 996484f98bfd..1f3188b4e313 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -29,7 +29,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>   	struct landlock_ruleset *new_ruleset;
> 
>   	new_ruleset =
> -		kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers),
> +		kzalloc(struct_size(new_ruleset, access_masks, num_layers),
>   			GFP_KERNEL_ACCOUNT);
>   	if (!new_ruleset)
>   		return ERR_PTR(-ENOMEM);
> @@ -40,7 +40,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>   	/*
>   	 * hierarchy = NULL
>   	 * num_rules = 0
> -	 * fs_access_masks[] = 0
> +	 * access_masks[] = 0
>   	 */
>   	return new_ruleset;
>   }
> @@ -55,7 +55,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask)
>   		return ERR_PTR(-ENOMSG);
>   	new_ruleset = create_ruleset(1);
>   	if (!IS_ERR(new_ruleset))
> -		new_ruleset->fs_access_masks[0] = fs_access_mask;
> +		landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
>   	return new_ruleset;
>   }
> 
> @@ -117,11 +117,12 @@ static void build_check_ruleset(void)
>   		.num_rules = ~0,
>   		.num_layers = ~0,
>   	};
> -	typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0;
> +	typeof(ruleset.access_masks[0]) access_masks = ~0;
> 
>   	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>   	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
> -	BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS);
> +	BUILD_BUG_ON(access_masks <
> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>   }
> 
>   /**
> @@ -281,7 +282,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   		err = -EINVAL;
>   		goto out_unlock;
>   	}
> -	dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
> +	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
> 
>   	/* Merges the @src tree. */
>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
> @@ -340,8 +341,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>   		goto out_unlock;
>   	}
>   	/* Copies the parent layer stack and leaves a space for the new layer. */
> -	memcpy(child->fs_access_masks, parent->fs_access_masks,
> -	       flex_array_size(parent, fs_access_masks, parent->num_layers));
> +	memcpy(child->access_masks, parent->access_masks,
> +	       flex_array_size(parent, access_masks, parent->num_layers));
> 
>   	if (WARN_ON_ONCE(!parent->hierarchy)) {
>   		err = -EINVAL;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index d43231b783e4..f2ad932d396c 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -25,6 +25,11 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>   /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
>   static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
> 
> +/* Ruleset access masks. */
> +typedef u16 access_masks_t;
> +/* Makes sure all ruleset access rights can be stored. */
> +static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
> +
>   typedef u16 layer_mask_t;
>   /* Makes sure all layers can be checked. */
>   static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> @@ -110,7 +115,7 @@ struct landlock_ruleset {
>   		 * section.  This is only used by
>   		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>   		 * The fields @lock, @usage, @num_rules, @num_layers and
> -		 * @fs_access_masks are then unused.
> +		 * @access_masks are then unused.
>   		 */
>   		struct work_struct work_free;
>   		struct {
> @@ -137,7 +142,7 @@ struct landlock_ruleset {
>   			 */
>   			u32 num_layers;
>   			/**
> -			 * @fs_access_masks: Contains the subset of filesystem
> +			 * @access_masks: Contains the subset of filesystem
>   			 * actions that are restricted by a ruleset.  A domain
>   			 * saves all layers of merged rulesets in a stack
>   			 * (FAM), starting from the first layer to the last
> @@ -148,13 +153,13 @@ struct landlock_ruleset {
>   			 * layers are set once and never changed for the
>   			 * lifetime of the ruleset.
>   			 */
> -			access_mask_t fs_access_masks[];
> +			access_masks_t access_masks[];
>   		};
>   	};
>   };
> 
>   struct landlock_ruleset *
> -landlock_create_ruleset(const access_mask_t fs_access_mask);
> +landlock_create_ruleset(const access_mask_t access_mask);
> 
>   void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
>   void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
> @@ -177,4 +182,26 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>   		refcount_inc(&ruleset->usage);
>   }
> 
> +static inline void
> +landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
> +			    const access_mask_t fs_access_mask,
> +			    const u16 layer_level)
> +{
> +	access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
> +
> +	/* Should already be checked in sys_landlock_create_ruleset(). */
> +	WARN_ON_ONCE(fs_access_mask != fs_mask);
> +	// TODO: Add tests to check "|=" and not "="

This todo should be done and removed. No more todos must remain.


> +	ruleset->access_masks[layer_level] |=
> +		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
> +}
> +
> +static inline access_mask_t
> +landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> +			    const u16 layer_level)
> +{
> +	return (ruleset->access_masks[layer_level] >>
> +		LANDLOCK_SHIFT_ACCESS_FS) &
> +	       LANDLOCK_MASK_ACCESS_FS;
> +}
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 245cc650a4dc..71aca7f990bc 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -346,10 +346,11 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	}
>   	/*
>   	 * Checks that allowed_access matches the @ruleset constraints
> -	 * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits).
> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>   	 */
> -	if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) !=
> -	    ruleset->fs_access_masks[0]) {
> +	if ((path_beneath_attr.allowed_access |
> +	     landlock_get_fs_access_mask(ruleset, 0)) !=
> +	    landlock_get_fs_access_mask(ruleset, 0)) {
>   		err = -EINVAL;
>   		goto out_put_ruleset;
>   	}
> --
> 2.25.1
> 

I'll send a patch to be applied after this one.

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

* Re: [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule
  2022-10-21 15:26 ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
@ 2022-11-17 18:41   ` Mickaël Salaün
  2022-11-17 18:55     ` [PATCH] landlock: Allow filesystem layout changes for domains without such rule type Mickaël Salaün
                       ` (2 more replies)
  0 siblings, 3 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:41 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> Adds a new landlock_key union and landlock_id structure to support

Add a new…

> a socket port rule type. A struct landlock_id identifies a unique entry
> in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
> port). There is one red-black tree per key type.
> 
> This patch also adds is_object_pointer() and get_root() helpers.
> is_object_pointer() one checks whether key type is LANDLOCK_KEY_INODE.

is_object_pointer() returns true if the key type is LANDLOCK_KEY_INODE.

> get_root() helper chooses red_black tree root depending on a key type.

get_root() returns a red_black tree root pointer according to a key type.

> 
> Refactors landlock_insert_rule() and landlock_find_rule() to support coming

Refactor…

> network modifications. Now adding or searching a rule in a ruleset depends

… Adding or searching a rule in a ruleset can now be done

> on a landlock id argument provided in refactored functions.

thanks to a Landlock ID argument passed to these helpers.

> 
> Co-developed-by: Mickaël Salaün <mic@digikod.net>

Signed-off-by: Mickaël Salaün <mic@digikod.net>


> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Completes all the new field descriptions landlock_key,
>    landlock_key_type, landlock_id.
> * Refactors commit message, adds a co-developer.
> 
> Changes since v6:
> * Adds union landlock_key, enum landlock_key_type, and struct
>    landlock_id.
> * Refactors ruleset functions and improves switch/cases: create_rule(),
>    insert_rule(), get_root(), is_object_pointer(), free_rule(),
>    landlock_find_rule().
> * Refactors landlock_append_fs_rule() functions to support new
>    landlock_id type.
> 
> Changes since v5:
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors insert_rule() and create_rule() functions by deleting
> rule_type from their arguments list, it helps to reduce useless code.
> 
> Changes since v3:
> * Splits commit.
> * Refactors landlock_insert_rule and landlock_find_rule functions.
> * Rename new_ruleset->root_inode.
> 
> ---
>   security/landlock/fs.c      |  21 ++++--
>   security/landlock/ruleset.c | 146 +++++++++++++++++++++++++-----------
>   security/landlock/ruleset.h |  64 +++++++++++++---
>   3 files changed, 169 insertions(+), 62 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 0d57c6479d29..710cfa1306de 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -168,7 +168,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>   			    access_mask_t access_rights)
>   {
>   	int err;
> -	struct landlock_object *object;
> +	struct landlock_id id = {
> +		.type = LANDLOCK_KEY_INODE,
> +	};
> 
>   	/* Files only get access rights that make sense. */
>   	if (!d_is_dir(path->dentry) &&
> @@ -181,17 +183,17 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>   	access_rights |= LANDLOCK_MASK_ACCESS_FS &
>   			 ~(landlock_get_fs_access_mask(ruleset, 0) |
>   			   ACCESS_INITIALLY_DENIED);
> -	object = get_inode_object(d_backing_inode(path->dentry));
> -	if (IS_ERR(object))
> -		return PTR_ERR(object);
> +	id.key.object = get_inode_object(d_backing_inode(path->dentry));
> +	if (IS_ERR(id.key.object))
> +		return PTR_ERR(id.key.object);
>   	mutex_lock(&ruleset->lock);
> -	err = landlock_insert_rule(ruleset, object, access_rights);
> +	err = landlock_insert_rule(ruleset, id, access_rights);
>   	mutex_unlock(&ruleset->lock);
>   	/*
>   	 * No need to check for an error because landlock_insert_rule()
>   	 * increments the refcount for the new object if needed.
>   	 */
> -	landlock_put_object(object);
> +	landlock_put_object(id.key.object);
>   	return err;
>   }
> 
> @@ -208,6 +210,9 @@ find_rule(const struct landlock_ruleset *const domain,
>   {
>   	const struct landlock_rule *rule;
>   	const struct inode *inode;
> +	struct landlock_id id = {
> +		.type = LANDLOCK_KEY_INODE,
> +	};
> 
>   	/* Ignores nonexistent leafs. */
>   	if (d_is_negative(dentry))
> @@ -215,8 +220,8 @@ find_rule(const struct landlock_ruleset *const domain,
> 
>   	inode = d_backing_inode(dentry);
>   	rcu_read_lock();
> -	rule = landlock_find_rule(
> -		domain, rcu_dereference(landlock_inode(inode)->object));
> +	id.key.object = rcu_dereference(landlock_inode(inode)->object);
> +	rule = landlock_find_rule(domain, id);
>   	rcu_read_unlock();
>   	return rule;
>   }
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 1f3188b4e313..41de17d1869e 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -35,7 +35,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>   		return ERR_PTR(-ENOMEM);
>   	refcount_set(&new_ruleset->usage, 1);
>   	mutex_init(&new_ruleset->lock);
> -	new_ruleset->root = RB_ROOT;
> +	new_ruleset->root_inode = RB_ROOT;
>   	new_ruleset->num_layers = num_layers;
>   	/*
>   	 * hierarchy = NULL
> @@ -68,8 +68,18 @@ static void build_check_rule(void)
>   	BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>   }
> 
> +static inline bool is_object_pointer(const enum landlock_key_type key_type)

Let's remove all "inline" keywords in all .c files. This should be 
simple for the compiler to inline them automatically, and it makes the 
code cleaner.


> +{
> +	switch (key_type) {
> +	case LANDLOCK_KEY_INODE:
> +		return true;
> +	}
> +	WARN_ON_ONCE(1);
> +	return false;
> +}
> +
>   static struct landlock_rule *
> -create_rule(struct landlock_object *const object,
> +create_rule(const struct landlock_id id,
>   	    const struct landlock_layer (*const layers)[], const u32 num_layers,
>   	    const struct landlock_layer *const new_layer)
>   {
> @@ -90,8 +100,13 @@ create_rule(struct landlock_object *const object,
>   	if (!new_rule)
>   		return ERR_PTR(-ENOMEM);
>   	RB_CLEAR_NODE(&new_rule->node);
> -	landlock_get_object(object);
> -	new_rule->object = object;
> +	if (is_object_pointer(id.type)) {
> +		/* This should be catched by insert_rule(). */
> +		WARN_ON_ONCE(!id.key.object);
> +		landlock_get_object(id.key.object);
> +	}
> +
> +	new_rule->key = id.key;
>   	new_rule->num_layers = new_num_layers;
>   	/* Copies the original layer stack. */
>   	memcpy(new_rule->layers, layers,
> @@ -102,12 +117,29 @@ create_rule(struct landlock_object *const object,
>   	return new_rule;
>   }
> 
> -static void free_rule(struct landlock_rule *const rule)
> +static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,

Please remove "inline".

> +				       const enum landlock_key_type key_type)
> +{
> +	struct rb_root *root = NULL;
> +
> +	switch (key_type) {
> +	case LANDLOCK_KEY_INODE:
> +		root = &ruleset->root_inode;
> +		break;
> +	}
> +	if (WARN_ON_ONCE(!root))
> +		return ERR_PTR(-EINVAL);
> +	return root;
> +}
> +
> +static void free_rule(struct landlock_rule *const rule,
> +		      const enum landlock_key_type key_type)
>   {
>   	might_sleep();
>   	if (!rule)
>   		return;
> -	landlock_put_object(rule->object);
> +	if (is_object_pointer(key_type))
> +		landlock_put_object(rule->key.object);
>   	kfree(rule);
>   }
> 
> @@ -129,8 +161,8 @@ static void build_check_ruleset(void)
>    * insert_rule - Create and insert a rule in a ruleset
>    *
>    * @ruleset: The ruleset to be updated.
> - * @object: The object to build the new rule with.  The underlying kernel
> - *          object must be held by the caller.
> + * @id: The ID to build the new rule with.  The underlying kernel object, if
> + *      any, must be held by the caller.
>    * @layers: One or multiple layers to be copied into the new rule.
>    * @num_layers: The number of @layers entries.
>    *
> @@ -144,26 +176,37 @@ static void build_check_ruleset(void)
>    * access rights.
>    */
>   static int insert_rule(struct landlock_ruleset *const ruleset,
> -		       struct landlock_object *const object,
> +		       const struct landlock_id id,
>   		       const struct landlock_layer (*const layers)[],
> -		       size_t num_layers)
> +		       const size_t num_layers)
>   {
>   	struct rb_node **walker_node;
>   	struct rb_node *parent_node = NULL;
>   	struct landlock_rule *new_rule;
> +	struct rb_root *root;
> 
>   	might_sleep();
>   	lockdep_assert_held(&ruleset->lock);
> -	if (WARN_ON_ONCE(!object || !layers))
> +	if (WARN_ON_ONCE(!layers))
>   		return -ENOENT;
> -	walker_node = &(ruleset->root.rb_node);
> +
> +	if (is_object_pointer(id.type)) {
> +		if (WARN_ON_ONCE(!id.key.object))
> +			return -ENOENT;
> +	}
> +
> +	root = get_root(ruleset, id.type);
> +	if (IS_ERR(root))
> +		return PTR_ERR(root);
> +
> +	walker_node = &root->rb_node;
>   	while (*walker_node) {
>   		struct landlock_rule *const this =
>   			rb_entry(*walker_node, struct landlock_rule, node);
> 
> -		if (this->object != object) {
> +		if (this->key.data != id.key.data) {
>   			parent_node = *walker_node;
> -			if (this->object < object)
> +			if (this->key.data < id.key.data)
>   				walker_node = &((*walker_node)->rb_right);
>   			else
>   				walker_node = &((*walker_node)->rb_left);
> @@ -195,24 +238,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   		 * Intersects access rights when it is a merge between a
>   		 * ruleset and a domain.
>   		 */
> -		new_rule = create_rule(object, &this->layers, this->num_layers,
> +		new_rule = create_rule(id, &this->layers, this->num_layers,
>   				       &(*layers)[0]);
>   		if (IS_ERR(new_rule))
>   			return PTR_ERR(new_rule);
> -		rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
> -		free_rule(this);
> +		rb_replace_node(&this->node, &new_rule->node, root);
> +		free_rule(this, id.type);
>   		return 0;
>   	}
> 
> -	/* There is no match for @object. */
> +	/* There is no match for @id. */
>   	build_check_ruleset();
>   	if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
>   		return -E2BIG;
> -	new_rule = create_rule(object, layers, num_layers, NULL);
> +	new_rule = create_rule(id, layers, num_layers, NULL);
>   	if (IS_ERR(new_rule))
>   		return PTR_ERR(new_rule);
>   	rb_link_node(&new_rule->node, parent_node, walker_node);
> -	rb_insert_color(&new_rule->node, &ruleset->root);
> +	rb_insert_color(&new_rule->node, root);
>   	ruleset->num_rules++;
>   	return 0;
>   }
> @@ -230,7 +273,7 @@ static void build_check_layer(void)
> 
>   /* @ruleset must be locked by the caller. */
>   int landlock_insert_rule(struct landlock_ruleset *const ruleset,
> -			 struct landlock_object *const object,
> +			 const struct landlock_id id,
>   			 const access_mask_t access)
>   {
>   	struct landlock_layer layers[] = { {
> @@ -240,7 +283,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>   	} };
> 
>   	build_check_layer();
> -	return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers));
> +	return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
>   }
> 
>   static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
> @@ -263,6 +306,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   			 struct landlock_ruleset *const src)
>   {
>   	struct landlock_rule *walker_rule, *next_rule;
> +	struct rb_root *src_root;
>   	int err = 0;
> 
>   	might_sleep();
> @@ -273,6 +317,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
>   		return -EINVAL;
> 
> +	src_root = get_root(src, LANDLOCK_KEY_INODE);
> +	if (IS_ERR(src_root))
> +		return PTR_ERR(src_root);
> +
>   	/* Locks @dst first because we are its only owner. */
>   	mutex_lock(&dst->lock);
>   	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
> @@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
> 
>   	/* Merges the @src tree. */
> -	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
> +	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
>   					     node) {
>   		struct landlock_layer layers[] = { {
>   			.level = dst->num_layers,
>   		} };
> +		const struct landlock_id id = {
> +			.key = walker_rule->key,
> +			.type = LANDLOCK_KEY_INODE,
> +		};
> 
> -		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
> -			err = -EINVAL;
> -			goto out_unlock;
> -		}
> -		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
> -			err = -EINVAL;
> -			goto out_unlock;
> -		}
> +		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
> +			return -EINVAL;
> +		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
> +			return -EINVAL;
>   		layers[0].access = walker_rule->layers[0].access;
> -		err = insert_rule(dst, walker_rule->object, &layers,
> -				  ARRAY_SIZE(layers));
> +
> +		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
>   		if (err)
>   			goto out_unlock;
>   	}
> @@ -316,21 +364,29 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>   			   struct landlock_ruleset *const child)
>   {
>   	struct landlock_rule *walker_rule, *next_rule;
> +	struct rb_root *parent_root;
>   	int err = 0;
> 
>   	might_sleep();
>   	if (!parent)
>   		return 0;
> 
> +	parent_root = get_root(parent, LANDLOCK_KEY_INODE);
> +	if (IS_ERR(parent_root))
> +		return PTR_ERR(parent_root);
> +
>   	/* Locks @child first because we are its only owner. */
>   	mutex_lock(&child->lock);
>   	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
> 
>   	/* Copies the @parent tree. */
>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
> -					     &parent->root, node) {
> -		err = insert_rule(child, walker_rule->object,
> -				  &walker_rule->layers,
> +					     parent_root, node) {
> +		const struct landlock_id id = {
> +			.key = walker_rule->key,
> +			.type = LANDLOCK_KEY_INODE,
> +		};
> +		err = insert_rule(child, id, &walker_rule->layers,
>   				  walker_rule->num_layers);
>   		if (err)
>   			goto out_unlock;
> @@ -362,8 +418,9 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
>   	struct landlock_rule *freeme, *next;
> 
>   	might_sleep();
> -	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node)
> -		free_rule(freeme);
> +	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
> +					     node)
> +		free_rule(freeme, LANDLOCK_KEY_INODE);
>   	put_hierarchy(ruleset->hierarchy);
>   	kfree(ruleset);
>   }
> @@ -454,20 +511,23 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
>    */
>   const struct landlock_rule *
>   landlock_find_rule(const struct landlock_ruleset *const ruleset,
> -		   const struct landlock_object *const object)
> +		   const struct landlock_id id)
>   {
> +	const struct rb_root *root;
>   	const struct rb_node *node;
> 
> -	if (!object)
> +	root = get_root((struct landlock_ruleset *)ruleset, id.type);
> +	if (IS_ERR(root))
>   		return NULL;
> -	node = ruleset->root.rb_node;
> +	node = root->rb_node;
> +
>   	while (node) {
>   		struct landlock_rule *this =
>   			rb_entry(node, struct landlock_rule, node);
> 
> -		if (this->object == object)
> +		if (this->key.data == id.key.data)
>   			return this;
> -		if (this->object < object)
> +		if (this->key.data < id.key.data)
>   			node = node->rb_right;
>   		else
>   			node = node->rb_left;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index f2ad932d396c..608ab356bc3e 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -49,6 +49,46 @@ struct landlock_layer {
>   	access_mask_t access;
>   };
> 
> +/**
> + * union landlock_key - Key of a ruleset's red-black tree
> + */
> +union landlock_key {
> +	/**
> +	 * @object: Pointer to identify a kernel object (e.g. an inode).
> +	 */
> +	struct landlock_object *object;
> +	/**
> +	 * @data: A raw data value to identify a network socket port.

"Raw data to identify an arbitrary 32-bit value (e.g. a TCP port)."


> +	 */
> +	uintptr_t data;
> +};
> +
> +/**
> + * enum landlock_key_type - Type of &union landlock_key
> + */
> +enum landlock_key_type {
> +	/**
> +	 * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
> +	 * keys.
> +	 */
> +	LANDLOCK_KEY_INODE = 1,
> +};
> +
> +/**
> + * struct landlock_id - Unique rule identifier for a ruleset
> + */
> +struct landlock_id {
> +	/**
> +	 * @key: A union to identify either a kernel object (e.g. an inode) or
> +	 * a raw data value (e.g. a network socket port).

"a 32-bit value (e.g. a TCP port)."


> +	 */
> +	union landlock_key key;
> +	/**
> +	 * @type: A enumerator to identify the type of landlock_ruleset's root tree.
> +	 */
> +	const enum landlock_key_type type;
> +};
> +
>   /**
>    * struct landlock_rule - Access rights tied to an object
>    */
> @@ -58,12 +98,13 @@ struct landlock_rule {
>   	 */
>   	struct rb_node node;
>   	/**
> -	 * @object: Pointer to identify a kernel object (e.g. an inode).  This
> -	 * is used as a key for this ruleset element.  This pointer is set once
> -	 * and never modified.  It always points to an allocated object because
> -	 * each rule increments the refcount of its object.
> +	 * @key: A union to identify either a kernel object (e.g. an inode) or
> +	 * a raw data value (e.g. a network socket port). This is used as a key
> +	 * for this ruleset element.  The pointer is set once and never
> +	 * modified.  It always points to an allocated object because each rule
> +	 * increments the refcount of its object.
>   	 */
> -	struct landlock_object *object;
> +	union landlock_key key;
>   	/**
>   	 * @num_layers: Number of entries in @layers.
>   	 */
> @@ -99,11 +140,12 @@ struct landlock_hierarchy {
>    */
>   struct landlock_ruleset {
>   	/**
> -	 * @root: Root of a red-black tree containing &struct landlock_rule
> -	 * nodes.  Once a ruleset is tied to a process (i.e. as a domain), this
> -	 * tree is immutable until @usage reaches zero.
> +	 * @root_inode: Root of a red-black tree containing &struct
> +	 * landlock_rule nodes with inode object.  Once a ruleset is tied to a
> +	 * process (i.e. as a domain), this tree is immutable until @usage
> +	 * reaches zero.
>   	 */
> -	struct rb_root root;
> +	struct rb_root root_inode;
>   	/**
>   	 * @hierarchy: Enables hierarchy identification even when a parent
>   	 * domain vanishes.  This is needed for the ptrace protection.
> @@ -165,7 +207,7 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
>   void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
> 
>   int landlock_insert_rule(struct landlock_ruleset *const ruleset,
> -			 struct landlock_object *const object,
> +			 const struct landlock_id id,
>   			 const access_mask_t access);
> 
>   struct landlock_ruleset *
> @@ -174,7 +216,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
> 
>   const struct landlock_rule *
>   landlock_find_rule(const struct landlock_ruleset *const ruleset,
> -		   const struct landlock_object *const object);
> +		   const struct landlock_id id);
> 
>   static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>   {
> --
> 2.25.1
> 

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

* Re: [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions
  2022-10-21 15:26 ` [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
@ 2022-11-17 18:41   ` Mickaël Salaün
  2022-11-28  3:07     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:41 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> Refactors merge_ruleset() and inherit_ruleset() functions to support

Refactor…

> new rule types. This patch adds merge_tree() and inherit_tree()
> helpers.

> Each has key_type argument to choose a particular rb_tree

They use a specific ruleset's red-black tree according to a key type 
argument.

> structure in a ruleset.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Adds missed lockdep_assert_held it inherit_tree() and merge_tree().
> * Fixes comment.
> 
> Changes since v6:
> * Refactors merge_ruleset() and inherit_ruleset() functions to support
>    new rule types.
> * Renames tree_merge() to merge_tree() (and reorder arguments), and
>    tree_copy() to inherit_tree().
> 
> Changes since v5:
> * Refactors some logic errors.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * None
> 
> ---
>   security/landlock/ruleset.c | 108 ++++++++++++++++++++++++------------
>   1 file changed, 73 insertions(+), 35 deletions(-)
> 
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 41de17d1869e..961ffe0c709e 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -302,36 +302,22 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy)
>   	}
>   }
> 
> -static int merge_ruleset(struct landlock_ruleset *const dst,
> -			 struct landlock_ruleset *const src)
> +static int merge_tree(struct landlock_ruleset *const dst,
> +		      struct landlock_ruleset *const src,
> +		      const enum landlock_key_type key_type)
>   {
>   	struct landlock_rule *walker_rule, *next_rule;
>   	struct rb_root *src_root;
>   	int err = 0;
> 
>   	might_sleep();
> -	/* Should already be checked by landlock_merge_ruleset() */
> -	if (WARN_ON_ONCE(!src))
> -		return 0;
> -	/* Only merge into a domain. */
> -	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
> -		return -EINVAL;
> +	lockdep_assert_held(&dst->lock);
> +	lockdep_assert_held(&src->lock);
> 
> -	src_root = get_root(src, LANDLOCK_KEY_INODE);
> +	src_root = get_root(src, key_type);
>   	if (IS_ERR(src_root))
>   		return PTR_ERR(src_root);
> 
> -	/* Locks @dst first because we are its only owner. */
> -	mutex_lock(&dst->lock);
> -	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
> -
> -	/* Stacks the new layer. */
> -	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
> -		err = -EINVAL;
> -		goto out_unlock;
> -	}
> -	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
> -
>   	/* Merges the @src tree. */
>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
>   					     node) {
> @@ -340,7 +326,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   		} };
>   		const struct landlock_id id = {
>   			.key = walker_rule->key,
> -			.type = LANDLOCK_KEY_INODE,
> +			.type = key_type,
>   		};
> 
>   		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
> @@ -351,8 +337,39 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
> 
>   		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
>   		if (err)
> -			goto out_unlock;
> +			return err;
> +	}
> +	return err;
> +}
> +
> +static int merge_ruleset(struct landlock_ruleset *const dst,
> +			 struct landlock_ruleset *const src)
> +{
> +	int err = 0;
> +
> +	might_sleep();
> +	/* Should already be checked by landlock_merge_ruleset() */
> +	if (WARN_ON_ONCE(!src))
> +		return 0;
> +	/* Only merge into a domain. */
> +	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
> +		return -EINVAL;
> +
> +	/* Locks @dst first because we are its only owner. */
> +	mutex_lock(&dst->lock);
> +	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
> +
> +	/* Stacks the new layer. */
> +	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
> +		err = -EINVAL;
> +		goto out_unlock;
>   	}
> +	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
> +
> +	/* Merges the @src inode tree. */
> +	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
> +	if (err)
> +		goto out_unlock;
> 
>   out_unlock:
>   	mutex_unlock(&src->lock);
> @@ -360,43 +377,64 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   	return err;
>   }
> 
> -static int inherit_ruleset(struct landlock_ruleset *const parent,
> -			   struct landlock_ruleset *const child)
> +static int inherit_tree(struct landlock_ruleset *const parent,
> +			struct landlock_ruleset *const child,
> +			const enum landlock_key_type key_type)
>   {
>   	struct landlock_rule *walker_rule, *next_rule;
>   	struct rb_root *parent_root;
>   	int err = 0;
> 
>   	might_sleep();
> -	if (!parent)
> -		return 0;
> +	lockdep_assert_held(&parent->lock);
> +	lockdep_assert_held(&parent->lock);

lockdep_assert_held(&child->lock);

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

* Re: [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks()
  2022-10-21 15:26 ` [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks() Konstantin Meskhidze
@ 2022-11-17 18:42   ` Mickaël Salaün
  2022-11-28  3:25     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:42 UTC (permalink / raw)
  To: Konstantin Meskhidze, Andy Whitcroft, Joe Perches, Dwaipayan Ray,
	Lukas Bulwahn
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> This patch moves unmask_layers() and init_layer_masks() helpers
> to ruleset.c to share with landlock network implementation in

…to share them with the Landlock network implementation in


> following commits.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Refactors commit message.
> 
> Changes since v6:
> * Moves get_handled_accesses() helper from ruleset.c back to fs.c,
>    cause it's not used in coming network commits.
> 
> Changes since v5:
> * Splits commit.
> * Moves init_layer_masks() and get_handled_accesses() helpers
> to ruleset.c and makes then non-static.
> * Formats code with clang-format-14.
> 
> ---
>   security/landlock/fs.c      | 103 ------------------------------------
>   security/landlock/ruleset.c | 102 +++++++++++++++++++++++++++++++++++
>   security/landlock/ruleset.h |  20 +++++++
>   3 files changed, 122 insertions(+), 103 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 710cfa1306de..240e42a8f788 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -226,60 +226,6 @@ find_rule(const struct landlock_ruleset *const domain,
>   	return rule;
>   }
> 
> -/*
> - * @layer_masks is read and may be updated according to the access request and
> - * the matching rule.
> - *
> - * Returns true if the request is allowed (i.e. relevant layer masks for the
> - * request are empty).
> - */
> -static inline bool
> -unmask_layers(const struct landlock_rule *const rule,
> -	      const access_mask_t access_request,
> -	      layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> -{
> -	size_t layer_level;
> -
> -	if (!access_request || !layer_masks)
> -		return true;
> -	if (!rule)
> -		return false;
> -
> -	/*
> -	 * An access is granted if, for each policy layer, at least one rule
> -	 * encountered on the pathwalk grants the requested access,
> -	 * regardless of its position in the layer stack.  We must then check
> -	 * the remaining layers for each inode, from the first added layer to
> -	 * the last one.  When there is multiple requested accesses, for each
> -	 * policy layer, the full set of requested accesses may not be granted
> -	 * by only one rule, but by the union (binary OR) of multiple rules.
> -	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
> -	 */
> -	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
> -		const struct landlock_layer *const layer =
> -			&rule->layers[layer_level];
> -		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
> -		const unsigned long access_req = access_request;
> -		unsigned long access_bit;
> -		bool is_empty;
> -
> -		/*
> -		 * Records in @layer_masks which layer grants access to each
> -		 * requested access.
> -		 */
> -		is_empty = true;
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> -			if (layer->access & BIT_ULL(access_bit))
> -				(*layer_masks)[access_bit] &= ~layer_bit;
> -			is_empty = is_empty && !(*layer_masks)[access_bit];
> -		}
> -		if (is_empty)
> -			return true;
> -	}
> -	return false;
> -}
> -
>   /*
>    * Allows access to pseudo filesystems that will never be mountable (e.g.
>    * sockfs, pipefs), but can still be reachable through
> @@ -303,55 +249,6 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>   }
> 
> -/**
> - * init_layer_masks - Initialize layer masks from an access request
> - *
> - * Populates @layer_masks such that for each access right in @access_request,
> - * the bits for all the layers are set where this access right is handled.
> - *
> - * @domain: The domain that defines the current restrictions.
> - * @access_request: The requested access rights to check.
> - * @layer_masks: The layer masks to populate.
> - *
> - * Returns: An access mask where each access right bit is set which is handled
> - * in any of the active layers in @domain.
> - */
> -static inline access_mask_t
> -init_layer_masks(const struct landlock_ruleset *const domain,
> -		 const access_mask_t access_request,
> -		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> -{
> -	access_mask_t handled_accesses = 0;
> -	size_t layer_level;
> -
> -	memset(layer_masks, 0, sizeof(*layer_masks));
> -	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
> -	if (!access_request)
> -		return 0;
> -
> -	/* Saves all handled accesses per layer. */
> -	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
> -		const unsigned long access_req = access_request;
> -		unsigned long access_bit;
> -
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> -			/*
> -			 * Artificially handles all initially denied by default
> -			 * access rights.
> -			 */
> -			if (BIT_ULL(access_bit) &
> -			    (landlock_get_fs_access_mask(domain, layer_level) |
> -			     ACCESS_INITIALLY_DENIED)) {
> -				(*layer_masks)[access_bit] |=
> -					BIT_ULL(layer_level);
> -				handled_accesses |= BIT_ULL(access_bit);
> -			}
> -		}
> -	}
> -	return handled_accesses;
> -}
> -
>   /*
>    * Check that a destination file hierarchy has more restrictions than a source
>    * file hierarchy.  This is only used for link and rename actions.
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 961ffe0c709e..02ab14439c43 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -572,3 +572,105 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>   	}
>   	return NULL;
>   }
> +
> +/*
> + * @layer_masks is read and may be updated according to the access request and
> + * the matching rule.
> + *
> + * Returns true if the request is allowed (i.e. relevant layer masks for the
> + * request are empty).
> + */
> +bool unmask_layers(const struct landlock_rule *const rule,
> +		   const access_mask_t access_request,
> +		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> +{
> +	size_t layer_level;
> +
> +	if (!access_request || !layer_masks)
> +		return true;
> +	if (!rule)
> +		return false;
> +
> +	/*
> +	 * An access is granted if, for each policy layer, at least one rule
> +	 * encountered on the pathwalk grants the requested access,
> +	 * regardless of its position in the layer stack.  We must then check
> +	 * the remaining layers for each inode, from the first added layer to
> +	 * the last one.  When there is multiple requested accesses, for each
> +	 * policy layer, the full set of requested accesses may not be granted
> +	 * by only one rule, but by the union (binary OR) of multiple rules.
> +	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
> +	 */
> +	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
> +		const struct landlock_layer *const layer =
> +			&rule->layers[layer_level];
> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
> +		const unsigned long access_req = access_request;
> +		unsigned long access_bit;
> +		bool is_empty;
> +
> +		/*
> +		 * Records in @layer_masks which layer grants access to each
> +		 * requested access.
> +		 */
> +		is_empty = true;
> +		for_each_set_bit(access_bit, &access_req,
> +				 ARRAY_SIZE(*layer_masks)) {
> +			if (layer->access & BIT_ULL(access_bit))
> +				(*layer_masks)[access_bit] &= ~layer_bit;
> +			is_empty = is_empty && !(*layer_masks)[access_bit];
> +		}
> +		if (is_empty)
> +			return true;
> +	}
> +	return false;
> +}
> +
> +/**
> + * init_layer_masks - Initialize layer masks from an access request
> + *
> + * Populates @layer_masks such that for each access right in @access_request,
> + * the bits for all the layers are set where this access right is handled.
> + *
> + * @domain: The domain that defines the current restrictions.
> + * @access_request: The requested access rights to check.
> + * @layer_masks: The layer masks to populate.
> + *
> + * Returns: An access mask where each access right bit is set which is handled
> + * in any of the active layers in @domain.
> + */
> +access_mask_t
> +init_layer_masks(const struct landlock_ruleset *const domain,
> +		 const access_mask_t access_request,
> +		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> +{
> +	access_mask_t handled_accesses = 0;
> +	size_t layer_level;
> +
> +	memset(layer_masks, 0, sizeof(*layer_masks));
> +	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
> +	if (!access_request)
> +		return 0;
> +
> +	/* Saves all handled accesses per layer. */
> +	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
> +		const unsigned long access_req = access_request;
> +		unsigned long access_bit;
> +
> +		for_each_set_bit(access_bit, &access_req,
> +				 ARRAY_SIZE(*layer_masks)) {
> +			/*
> +			 * Artificially handles all initially denied by default
> +			 * access rights.
> +			 */
> +			if (BIT_ULL(access_bit) &
> +			    (landlock_get_fs_access_mask(domain, layer_level) |
> +			     ACCESS_INITIALLY_DENIED)) {

This is a future bug in the a next commit when 
landlock_get_fs_access_mask() is changed to get the network access mask. 
My patch will fix this part but you'll need to do a new copy of this 
function.



> +				(*layer_masks)[access_bit] |=
> +					BIT_ULL(layer_level);
> +				handled_accesses |= BIT_ULL(access_bit);
> +			}
> +		}
> +	}
> +	return handled_accesses;
> +}
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 608ab356bc3e..50baff4fcbb4 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -34,6 +34,16 @@ typedef u16 layer_mask_t;
>   /* Makes sure all layers can be checked. */
>   static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> 
> +/*
> + * All access rights that are denied by default whether they are handled or not
> + * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
> + * entries when we need to get the absolute handled access masks.
> + */
> +/* clang-format off */
> +#define ACCESS_INITIALLY_DENIED ( \
> +	LANDLOCK_ACCESS_FS_REFER)
> +/* clang-format on */

This ACCESS_INITIALLY_DENIED definition must be moved, not copied. You 
can rename ACCESS_INITIALLY_DENIED to ACCESS_FS_INITIALLY_DENIED and 
move this hunk before the access_mask_t definition.


> +
>   /**
>    * struct landlock_layer - Access rights for a given layer
>    */
> @@ -246,4 +256,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>   		LANDLOCK_SHIFT_ACCESS_FS) &
>   	       LANDLOCK_MASK_ACCESS_FS;
>   }
> +
> +bool unmask_layers(const struct landlock_rule *const rule,

All public Landlock helpers must be prefixed with "landlock_"

> +		   const access_mask_t access_request,
> +		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
> +
> +access_mask_t
> +init_layer_masks(const struct landlock_ruleset *const domain,
> +		 const access_mask_t access_request,
> +		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);

There is a warning generated by checkpatch.pl about this line:
   WARNING: function definition argument 'layer_mask_t' should also have 
an identifier name

I think this is a bug in checkpatch.pl

Any though Andy, Joe, Dwaipayan or Lukas?


> +
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> --
> 2.25.1
> 

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

* Re: [PATCH v8 05/12] landlock: Refactor unmask_layers() and init_layer_masks()
  2022-10-21 15:26 ` [PATCH v8 05/12] landlock: Refactor " Konstantin Meskhidze
@ 2022-11-17 18:42   ` Mickaël Salaün
  2022-11-28  3:30     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:42 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> Adds new key_type argument to init_layer_masks() helper.

Add a new key_type argument to the…

> Adds masks_array_size argument  to unmask_layers() helper.

Add a masks_array_size argument to the…


The name of these helpers need to be prefixed with "landlock_".


> These modifications support implementing new rule types in the next
> Landlock versions.
> 
> Co-developed-by: Mickaël Salaün <mic@digikod.net>

Signed-off-by: Mickaël Salaün <mic@digikod.net>

> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Refactors commit message, adds a co-developer.
> * Minor fixes.
> 
> Changes since v6:
> * Removes masks_size attribute from init_layer_masks().
> * Refactors init_layer_masks() with new landlock_key_type.
> 
> Changes since v5:
> * Splits commit.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors init_layer_masks(), get_handled_accesses()
> and unmask_layers() functions to support multiple rule types.
> * Refactors landlock_get_fs_access_mask() function with
> LANDLOCK_MASK_ACCESS_FS mask.
> 
> Changes since v3:
> * Splits commit.
> * Refactors landlock_unmask_layers functions.
> 
> ---
>   security/landlock/fs.c      | 36 ++++++++++++++++++-------------
>   security/landlock/ruleset.c | 42 ++++++++++++++++++++++++++-----------
>   security/landlock/ruleset.h | 11 +++++-----
>   3 files changed, 58 insertions(+), 31 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 240e42a8f788..fe76a11483f8 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -435,16 +435,20 @@ static bool is_access_to_paths_allowed(
>   	if (unlikely(dentry_child1)) {
>   		unmask_layers(find_rule(domain, dentry_child1),
>   			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> -					       &_layer_masks_child1),
> -			      &_layer_masks_child1);
> +					       &_layer_masks_child1,
> +					       LANDLOCK_KEY_INODE),
> +			      &_layer_masks_child1,
> +			      ARRAY_SIZE(_layer_masks_child1));
>   		layer_masks_child1 = &_layer_masks_child1;
>   		child1_is_directory = d_is_dir(dentry_child1);
>   	}
>   	if (unlikely(dentry_child2)) {
>   		unmask_layers(find_rule(domain, dentry_child2),
>   			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> -					       &_layer_masks_child2),
> -			      &_layer_masks_child2);
> +					       &_layer_masks_child2,
> +					       LANDLOCK_KEY_INODE),
> +			      &_layer_masks_child2,
> +			      ARRAY_SIZE(_layer_masks_child2));
>   		layer_masks_child2 = &_layer_masks_child2;
>   		child2_is_directory = d_is_dir(dentry_child2);
>   	}
> @@ -496,15 +500,16 @@ static bool is_access_to_paths_allowed(
>   		}
> 
>   		rule = find_rule(domain, walker_path.dentry);
> -		allowed_parent1 = unmask_layers(rule, access_masked_parent1,
> -						layer_masks_parent1);
> -		allowed_parent2 = unmask_layers(rule, access_masked_parent2,
> -						layer_masks_parent2);
> +		allowed_parent1 = unmask_layers(
> +			rule, access_masked_parent1, layer_masks_parent1,
> +			ARRAY_SIZE(*layer_masks_parent1));
> +		allowed_parent2 = unmask_layers(
> +			rule, access_masked_parent2, layer_masks_parent2,
> +			ARRAY_SIZE(*layer_masks_parent2));
> 
>   		/* Stops when a rule from each layer grants access. */
>   		if (allowed_parent1 && allowed_parent2)
>   			break;
> -
>   jump_up:
>   		if (walker_path.dentry == walker_path.mnt->mnt_root) {
>   			if (follow_up(&walker_path)) {
> @@ -543,7 +548,8 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>   {
>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
> 
> -	access_request = init_layer_masks(domain, access_request, &layer_masks);
> +	access_request = init_layer_masks(domain, access_request, &layer_masks,
> +					  LANDLOCK_KEY_INODE);
>   	if (is_access_to_paths_allowed(domain, path, access_request,
>   				       &layer_masks, NULL, 0, NULL, NULL))
>   		return 0;
> @@ -630,7 +636,7 @@ static bool collect_domain_accesses(
>   		return true;
> 
>   	access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> -				      layer_masks_dom);
> +				      layer_masks_dom, LANDLOCK_KEY_INODE);
> 
>   	dget(dir);
>   	while (true) {
> @@ -638,7 +644,8 @@ static bool collect_domain_accesses(
> 
>   		/* Gets all layers allowing all domain accesses. */
>   		if (unmask_layers(find_rule(domain, dir), access_dom,
> -				  layer_masks_dom)) {
> +				  layer_masks_dom,
> +				  ARRAY_SIZE(*layer_masks_dom))) {
>   			/*
>   			 * Stops when all handled accesses are allowed by at
>   			 * least one rule in each layer.
> @@ -754,7 +761,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>   		 */
>   		access_request_parent1 = init_layer_masks(
>   			dom, access_request_parent1 | access_request_parent2,
> -			&layer_masks_parent1);
> +			&layer_masks_parent1, LANDLOCK_KEY_INODE);
>   		if (is_access_to_paths_allowed(
>   			    dom, new_dir, access_request_parent1,
>   			    &layer_masks_parent1, NULL, 0, NULL, NULL))
> @@ -1131,7 +1138,8 @@ static int hook_file_open(struct file *const file)
> 
>   	if (is_access_to_paths_allowed(
>   		    dom, &file->f_path,
> -		    init_layer_masks(dom, full_access_request, &layer_masks),
> +		    init_layer_masks(dom, full_access_request, &layer_masks,
> +				     LANDLOCK_KEY_INODE),
>   		    &layer_masks, NULL, 0, NULL, NULL)) {
>   		allowed_access = full_access_request;
>   	} else {
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 02ab14439c43..c7cf54ba4f6d 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -576,13 +576,15 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>   /*
>    * @layer_masks is read and may be updated according to the access request and
>    * the matching rule.
> + * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
>    *
>    * Returns true if the request is allowed (i.e. relevant layer masks for the
>    * request are empty).
>    */
>   bool unmask_layers(const struct landlock_rule *const rule,
>   		   const access_mask_t access_request,
> -		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> +		   layer_mask_t (*const layer_masks)[],
> +		   const size_t masks_array_size)
>   {
>   	size_t layer_level;
> 
> @@ -614,8 +616,7 @@ bool unmask_layers(const struct landlock_rule *const rule,
>   		 * requested access.
>   		 */
>   		is_empty = true;
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> +		for_each_set_bit(access_bit, &access_req, masks_array_size) {
>   			if (layer->access & BIT_ULL(access_bit))
>   				(*layer_masks)[access_bit] &= ~layer_bit;
>   			is_empty = is_empty && !(*layer_masks)[access_bit];
> @@ -626,6 +627,10 @@ bool unmask_layers(const struct landlock_rule *const rule,
>   	return false;
>   }
> 
> +typedef access_mask_t
> +get_access_mask_t(const struct landlock_ruleset *const ruleset,
> +		  const u16 layer_level);
> +
>   /**
>    * init_layer_masks - Initialize layer masks from an access request
>    *
> @@ -635,19 +640,33 @@ bool unmask_layers(const struct landlock_rule *const rule,
>    * @domain: The domain that defines the current restrictions.
>    * @access_request: The requested access rights to check.
>    * @layer_masks: The layer masks to populate.
> + * @key_type: The key type to switch between access masks of different types.
>    *
>    * Returns: An access mask where each access right bit is set which is handled
>    * in any of the active layers in @domain.
>    */
> -access_mask_t
> -init_layer_masks(const struct landlock_ruleset *const domain,
> -		 const access_mask_t access_request,
> -		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
> +			       const access_mask_t access_request,
> +			       layer_mask_t (*const layer_masks)[],
> +			       const enum landlock_key_type key_type)
>   {
>   	access_mask_t handled_accesses = 0;
> -	size_t layer_level;
> +	size_t layer_level, num_access;
> +	get_access_mask_t *get_access_mask;
> +
> +	switch (key_type) {
> +	case LANDLOCK_KEY_INODE:
> +		get_access_mask = landlock_get_fs_access_mask;
> +		num_access = LANDLOCK_NUM_ACCESS_FS;
> +		break;
> +	default:
> +		WARN_ON_ONCE(1);
> +		return 0;
> +	}
> +
> +	memset(layer_masks, 0,
> +	       array_size(sizeof((*layer_masks)[0]), num_access));
> 
> -	memset(layer_masks, 0, sizeof(*layer_masks));
>   	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
>   	if (!access_request)
>   		return 0;
> @@ -657,14 +676,13 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>   		const unsigned long access_req = access_request;
>   		unsigned long access_bit;
> 
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> +		for_each_set_bit(access_bit, &access_req, num_access) {
>   			/*
>   			 * Artificially handles all initially denied by default
>   			 * access rights.
>   			 */
>   			if (BIT_ULL(access_bit) &
> -			    (landlock_get_fs_access_mask(domain, layer_level) |
> +			    (get_access_mask(domain, layer_level) |
>   			     ACCESS_INITIALLY_DENIED)) {
>   				(*layer_masks)[access_bit] |=
>   					BIT_ULL(layer_level);
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 50baff4fcbb4..d9eb79ea9a89 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -259,11 +259,12 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> 
>   bool unmask_layers(const struct landlock_rule *const rule,
>   		   const access_mask_t access_request,
> -		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
> +		   layer_mask_t (*const layer_masks)[],
> +		   const size_t masks_array_size);
> 
> -access_mask_t
> -init_layer_masks(const struct landlock_ruleset *const domain,
> -		 const access_mask_t access_request,
> -		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
> +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
> +			       const access_mask_t access_request,
> +			       layer_mask_t (*const layer_masks)[],
> +			       const enum landlock_key_type key_type);
> 
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> --
> 2.25.1
> 

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

* Re: [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall
  2022-10-21 15:26 ` [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
@ 2022-11-17 18:42   ` Mickaël Salaün
  2022-11-28  3:32     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:42 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> Modifies landlock_add_rule() syscall to support new rule types in future

Change the landlock_add_rule() syscall…


> Landlock versions. Adds add_rule_path_beneath() helper to support

Add the…

> current filesystem rules.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * None
> 
> Changes since v6:
> * None
> 
> Changes since v5:
> * Refactors syscall landlock_add_rule() and add_rule_path_beneath() helper
> to make argument check ordering consistent and get rid of partial revertings
> in following patches.
> * Rolls back refactoring base_test.c seltest.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors add_rule_path_beneath() and landlock_add_rule() functions
> to optimize code usage.
> * Refactors base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
> rule type in landlock_add_rule() call.
> 
> Changes since v3:
> * Split commit.
> * Refactors landlock_add_rule syscall.
> 
> ---
>   security/landlock/syscalls.c | 92 +++++++++++++++++++-----------------
>   1 file changed, 48 insertions(+), 44 deletions(-)
> 
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 71aca7f990bc..87389d7bfbf2 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -274,6 +274,47 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>   	return err;
>   }
> 
> +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
> +				 const void __user *const rule_attr)
> +{
> +	struct landlock_path_beneath_attr path_beneath_attr;
> +	struct path path;
> +	int res, err;
> +	u32 mask;

access_mask_t mask;

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2022-10-21 15:26 ` [PATCH v8 07/12] landlock: Add network rules support Konstantin Meskhidze
@ 2022-11-17 18:43   ` Mickaël Salaün
  2022-11-28  4:01     ` Konstantin Meskhidze (A)
                       ` (2 more replies)
  0 siblings, 3 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:43 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> This commit adds network rules support in internal landlock functions
> (presented in ruleset.c) and landlock_create_ruleset syscall.

…in the ruleset management helpers and the landlock_create_ruleset syscall.


> Refactors user space API to support network actions. Adds new network

Refactor…

> access flags, network rule and network attributes. Increments Landlock

Increment…

> ABI version.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Squashes commits.
> * Increments ABI version to 4.
> * Refactors commit message.
> * Minor fixes.
> 
> Changes since v6:
> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>    because it OR values.
> * Makes landlock_add_net_access_mask() more resilient incorrect values.
> * Refactors landlock_get_net_access_mask().
> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>    LANDLOCK_NUM_ACCESS_FS as value.
> * Updates access_masks_t to u32 to support network access actions.
> * Refactors landlock internal functions to support network actions with
>    landlock_key/key_type/id types.
> 
> Changes since v5:
> * Gets rid of partial revert from landlock_add_rule
> syscall.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors landlock_create_ruleset() - splits ruleset and
> masks checks.
> * Refactors landlock_create_ruleset() and landlock mask
> setters/getters to support two rule types.
> * Refactors landlock_add_rule syscall add_rule_path_beneath
> function by factoring out get_ruleset_from_fd() and
> landlock_put_ruleset().
> 
> Changes since v3:
> * Splits commit.
> * Adds network rule support for internal landlock functions.
> * Adds set_mask and get_mask for network.
> * Adds rb_root root_net_port.
> 
> ---
>   include/uapi/linux/landlock.h                | 49 ++++++++++++++
>   security/landlock/limits.h                   |  6 +-
>   security/landlock/ruleset.c                  | 55 ++++++++++++++--
>   security/landlock/ruleset.h                  | 68 ++++++++++++++++----
>   security/landlock/syscalls.c                 | 13 +++-
>   tools/testing/selftests/landlock/base_test.c |  2 +-
>   6 files changed, 170 insertions(+), 23 deletions(-)
> 
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index f3223f964691..096b683c6ff3 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
>   	 * this access right.
>   	 */
>   	__u64 handled_access_fs;
> +
> +	/**
> +	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
> +	 * that is handled by this ruleset and should then be forbidden if no
> +	 * rule explicitly allow them.
> +	 */
> +	__u64 handled_access_net;
>   };
> 
>   /*
> @@ -54,6 +61,11 @@ enum landlock_rule_type {
>   	 * landlock_path_beneath_attr .
>   	 */
>   	LANDLOCK_RULE_PATH_BENEATH = 1,
> +	/**
> +	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
> +	 * landlock_net_service_attr .
> +	 */
> +	LANDLOCK_RULE_NET_SERVICE = 2,
>   };
> 
>   /**
> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>   	 */
>   } __attribute__((packed));
> 
> +/**
> + * struct landlock_net_service_attr - TCP subnet definition
> + *
> + * Argument of sys_landlock_add_rule().
> + */
> +struct landlock_net_service_attr {
> +	/**
> +	 * @allowed_access: Bitmask of allowed access network for services
> +	 * (cf. `Network flags`_).
> +	 */
> +	__u64 allowed_access;
> +	/**
> +	 * @port: Network port.
> +	 */
> +	__u16 port;

 From an UAPI point of view, I think the port field should be __be16, as 
for sockaddr_in->port and other network-related APIs. This will require 
some kernel changes to please sparse: make C=2 security/landlock/ must 
not print any warning.

Using big-endian values as keys (casted to uintptr_t, not strictly 
__be16) in the rb-tree should not be an issue because there is no port 
range ordering (for now).

A dedicated test should check that endianness is correct, e.g. by using 
different port encoding. This should include passing and failing tests, 
but they should work on all architectures (i.e. big or little endian).


> +
> +} __attribute__((packed));
> +
>   /**
>    * DOC: fs_access
>    *
> @@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
>   #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
>   /* clang-format on */
> 
> +/**
> + * DOC: net_access
> + *
> + * Network flags
> + * ~~~~~~~~~~~~~~~~
> + *
> + * These flags enable to restrict a sandboxed process to a set of network
> + * actions.
> + *
> + * TCP sockets with allowed actions:
> + *
> + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
> + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
> + *   a remote port.
> + */
> +/* clang-format off */
> +#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
> +#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
> +/* clang-format on */
>   #endif /* _UAPI_LINUX_LANDLOCK_H */
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index bafb3b8dc677..8a1a6463c64e 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -23,6 +23,10 @@
>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>   #define LANDLOCK_SHIFT_ACCESS_FS	0
> 
> -/* clang-format on */
> +#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
> +#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
> +#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
> +#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
> 
> +/* clang-format on */
>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index c7cf54ba4f6d..9277c1295114 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -36,6 +36,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>   	refcount_set(&new_ruleset->usage, 1);
>   	mutex_init(&new_ruleset->lock);
>   	new_ruleset->root_inode = RB_ROOT;
> +#if IS_ENABLED(CONFIG_INET)
> +	new_ruleset->root_net_port = RB_ROOT;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	new_ruleset->num_layers = num_layers;
>   	/*
>   	 * hierarchy = NULL
> @@ -46,16 +49,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>   }
> 
>   struct landlock_ruleset *
> -landlock_create_ruleset(const access_mask_t fs_access_mask)
> +landlock_create_ruleset(const access_mask_t fs_access_mask,
> +			const access_mask_t net_access_mask)
>   {
>   	struct landlock_ruleset *new_ruleset;
> 
>   	/* Informs about useless ruleset. */
> -	if (!fs_access_mask)
> +	if (!fs_access_mask && !net_access_mask)
>   		return ERR_PTR(-ENOMSG);
>   	new_ruleset = create_ruleset(1);
> -	if (!IS_ERR(new_ruleset))
> +	if (IS_ERR(new_ruleset))
> +		return new_ruleset;
> +	if (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);
>   	return new_ruleset;
>   }
> 
> @@ -73,6 +81,10 @@ static inline bool is_object_pointer(const enum landlock_key_type key_type)
>   	switch (key_type) {
>   	case LANDLOCK_KEY_INODE:
>   		return true;
> +#if IS_ENABLED(CONFIG_INET)
> +	case LANDLOCK_KEY_NET_PORT:
> +		return false;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	}
>   	WARN_ON_ONCE(1);
>   	return false;
> @@ -126,6 +138,11 @@ static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
>   	case LANDLOCK_KEY_INODE:
>   		root = &ruleset->root_inode;
>   		break;
> +#if IS_ENABLED(CONFIG_INET)
> +	case LANDLOCK_KEY_NET_PORT:
> +		root = &ruleset->root_net_port;
> +		break;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	}
>   	if (WARN_ON_ONCE(!root))
>   		return ERR_PTR(-EINVAL);
> @@ -154,7 +171,9 @@ static void build_check_ruleset(void)
>   	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>   	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>   	BUILD_BUG_ON(access_masks <
> -		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) +

This is correct but because we are dealing with bitmasks I would prefer 
to use "|" instead of "+".


> +			     (LANDLOCK_MASK_ACCESS_NET
> +			      << LANDLOCK_SHIFT_ACCESS_NET));
>   }
> 
>   /**
> @@ -370,7 +389,12 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
>   	if (err)
>   		goto out_unlock;
> -

Please keep this newline.

> +#if IS_ENABLED(CONFIG_INET)
> +	/* Merges the @src network port tree. */
> +	err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
> +	if (err)
> +		goto out_unlock;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   out_unlock:
>   	mutex_unlock(&src->lock);
>   	mutex_unlock(&dst->lock);
> @@ -426,7 +450,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>   	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
>   	if (err)
>   		goto out_unlock;
> -

newline

> +#if IS_ENABLED(CONFIG_INET)
> +	/* Copies the @parent network port tree. */
> +	err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
> +	if (err)
> +		goto out_unlock;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
>   		err = -EINVAL;
>   		goto out_unlock;
> @@ -459,6 +488,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
>   	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
>   					     node)
>   		free_rule(freeme, LANDLOCK_KEY_INODE);
> +#if IS_ENABLED(CONFIG_INET)
> +	rbtree_postorder_for_each_entry_safe(freeme, next,
> +					     &ruleset->root_net_port, node)
> +		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	put_hierarchy(ruleset->hierarchy);
>   	kfree(ruleset);
>   }
> @@ -637,6 +671,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
>    * Populates @layer_masks such that for each access right in @access_request,
>    * the bits for all the layers are set where this access right is handled.
>    *
> + * @layer_masks must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET
> + * elements according to @key_type.

Please include this sentence in the @layer_masks description below.

> + *
>    * @domain: The domain that defines the current restrictions.
>    * @access_request: The requested access rights to check.
>    * @layer_masks: The layer masks to populate.

"It must contain…"


> @@ -659,6 +696,12 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>   		get_access_mask = landlock_get_fs_access_mask;
>   		num_access = LANDLOCK_NUM_ACCESS_FS;
>   		break;
> +#if IS_ENABLED(CONFIG_INET)
> +	case LANDLOCK_KEY_NET_PORT:
> +		get_access_mask = landlock_get_net_access_mask;
> +		num_access = LANDLOCK_NUM_ACCESS_NET;
> +		break;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	default:
>   		WARN_ON_ONCE(1);
>   		return 0;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index d9eb79ea9a89..f272d2cd518c 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -19,16 +19,20 @@
>   #include "limits.h"
>   #include "object.h"
> 
> +/* Rule access mask. */
>   typedef u16 access_mask_t;
>   /* Makes sure all filesystem access rights can be stored. */
>   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 for_each_set_bit() and for_each_clear_bit() calls are OK. */
>   static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
> 
>   /* Ruleset access masks. */
> -typedef u16 access_masks_t;
> +typedef u32 access_masks_t;

This type change need to be explained in the commit message.


>   /* Makes sure all ruleset access rights can be stored. */
> -static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
> +static_assert(BITS_PER_TYPE(access_masks_t) >=
> +	      LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
> 
>   typedef u16 layer_mask_t;
>   /* Makes sure all layers can be checked. */
> @@ -82,6 +86,13 @@ enum landlock_key_type {
>   	 * keys.
>   	 */
>   	LANDLOCK_KEY_INODE = 1,
> +#if IS_ENABLED(CONFIG_INET)
> +	/**
> +	 * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
> +	 * node keys.
> +	 */
> +	LANDLOCK_KEY_NET_PORT = 2,
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   };
> 
>   /**
> @@ -156,6 +167,15 @@ struct landlock_ruleset {
>   	 * reaches zero.
>   	 */
>   	struct rb_root root_inode;
> +#if IS_ENABLED(CONFIG_INET)
> +	/**
> +	 * @root_net_port: Root of a red-black tree containing &struct
> +	 * landlock_rule nodes with network port. Once a ruleset is tied to a
> +	 * process (i.e. as a domain), this tree is immutable until @usage
> +	 * reaches zero.
> +	 */
> +	struct rb_root root_net_port;
> +#endif /* IS_ENABLED(CONFIG_INET) */
>   	/**
>   	 * @hierarchy: Enables hierarchy identification even when a parent
>   	 * domain vanishes.  This is needed for the ptrace protection.
> @@ -166,8 +186,8 @@ struct landlock_ruleset {
>   		 * @work_free: Enables to free a ruleset within a lockless
>   		 * section.  This is only used by
>   		 * landlock_put_ruleset_deferred() when @usage reaches zero.
> -		 * The fields @lock, @usage, @num_rules, @num_layers and
> -		 * @access_masks are then unused.
> +		 * The fields @lock, @usage, @num_rules, @num_layers,
> +		 * @net_access_mask and @access_masks are then unused.

There is no net_access_mask anymore.

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-10-21 15:26 ` [PATCH v8 08/12] landlock: Implement TCP network hooks Konstantin Meskhidze
@ 2022-11-17 18:43   ` Mickaël Salaün
  2022-11-28  8:21     ` Konstantin Meskhidze (A)
  2023-01-05  8:57     ` Konstantin Meskhidze (A)
  0 siblings, 2 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:43 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-api,
	Alejandro Colomar (man-pages)


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> This patch adds support of socket_bind() and socket_connect() hooks.
> It's possible to restrict binding and connecting of TCP sockets to
> particular ports.

Implement socket_bind() and socket_connect LSM hooks, which enable to 
restrict TCP socket binding and connection to specific ports.


> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Minor fixes.
> * Refactors commit message.
> 
> Changes since v6:
> * Updates copyright.
> * Refactors landlock_append_net_rule() and check_socket_access()
>    functions with landlock_id type.
> 
> Changes since v5:
> * Fixes some logic errors.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Factors out CONFIG_INET into make file.
> * Refactors check_socket_access().
> * Adds helper get_port().
> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
> functions to support AF_INET6 family.
> * Adds AF_UNSPEC family support in hook_socket_bind/connect
> functions.
> * Refactors add_rule_net_service() and landlock_add_rule
> syscall to support network rule inserting.
> * Refactors init_layer_masks() to support network rules.
> 
> Changes since v3:
> * Splits commit.
> * Adds SECURITY_NETWORK in config.
> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
> * Adds hook_socket_bind and hook_socket_connect hooks.
> 
> ---
>   security/landlock/Kconfig    |   1 +
>   security/landlock/Makefile   |   2 +
>   security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>   security/landlock/net.h      |  26 ++++++
>   security/landlock/setup.c    |   2 +
>   security/landlock/syscalls.c |  59 ++++++++++++-
>   6 files changed, 251 insertions(+), 3 deletions(-)
>   create mode 100644 security/landlock/net.c
>   create mode 100644 security/landlock/net.h
> 
> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
> index 8e33c4e8ffb8..10c099097533 100644
> --- a/security/landlock/Kconfig
> +++ b/security/landlock/Kconfig
> @@ -3,6 +3,7 @@
>   config SECURITY_LANDLOCK
>   	bool "Landlock support"
>   	depends on SECURITY && !ARCH_EPHEMERAL_INODES
> +	select SECURITY_NETWORK
>   	select SECURITY_PATH
>   	help
>   	  Landlock is a sandboxing mechanism that enables processes to restrict
> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
> index 7bbd2f413b3e..53d3c92ae22e 100644
> --- a/security/landlock/Makefile
> +++ b/security/landlock/Makefile
> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
> 
>   landlock-y := setup.o syscalls.o object.o ruleset.o \
>   	cred.o ptrace.o fs.o
> +
> +landlock-$(CONFIG_INET) += net.o
> \ No newline at end of file
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> new file mode 100644
> index 000000000000..39e8a156a1f4
> --- /dev/null
> +++ b/security/landlock/net.c
> @@ -0,0 +1,164 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock LSM - Network management and hooks
> + *
> + * Copyright © 2022 Huawei Tech. Co., Ltd.
> + * Copyright © 2022 Microsoft Corporation
> + */
> +
> +#include <linux/in.h>
> +#include <linux/net.h>
> +#include <linux/socket.h>
> +#include <net/ipv6.h>
> +
> +#include "common.h"
> +#include "cred.h"
> +#include "limits.h"
> +#include "net.h"
> +#include "ruleset.h"
> +
> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> +			     const u16 port, access_mask_t access_rights)
> +{
> +	int err;
> +	const struct landlock_id id = {
> +		.key.data = port,
> +		.type = LANDLOCK_KEY_NET_PORT,
> +	};
> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
> +
> +	/* Transforms relative access rights to absolute ones. */
> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
> +			 ~landlock_get_net_access_mask(ruleset, 0);
> +
> +	mutex_lock(&ruleset->lock);
> +	err = landlock_insert_rule(ruleset, id, access_rights);
> +	mutex_unlock(&ruleset->lock);
> +
> +	return err;
> +}
> +
> +static int check_socket_access(const struct landlock_ruleset *const domain,
> +			       u16 port, access_mask_t access_request)
> +{
> +	bool allowed = false;
> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
> +	const struct landlock_rule *rule;
> +	access_mask_t handled_access;
> +	const struct landlock_id id = {
> +		.key.data = port,
> +		.type = LANDLOCK_KEY_NET_PORT,
> +	};
> +
> +	if (WARN_ON_ONCE(!domain))
> +		return 0;
> +	if (WARN_ON_ONCE(domain->num_layers < 1))
> +		return -EACCES;
> +
> +	rule = landlock_find_rule(domain, id);
> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
> +					  LANDLOCK_KEY_NET_PORT);
> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
> +				ARRAY_SIZE(layer_masks));
> +
> +	return allowed ? 0 : -EACCES;
> +}
> +
> +static u16 get_port(const struct sockaddr *const address)

get_port() should return a __be16 type. This enables to avoid converting 
port when checking a rule.

make C=2 security/landlock/ must not print any warning.


> +{
> +	/* Gets port value in host byte order. */
> +	switch (address->sa_family) {
> +	case AF_UNSPEC:
> +	case AF_INET: {
> +		const struct sockaddr_in *const sockaddr =
> +			(struct sockaddr_in *)address;
> +		return ntohs(sockaddr->sin_port);
> +	}
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6: {
> +		const struct sockaddr_in6 *const sockaddr_ip6 =
> +			(struct sockaddr_in6 *)address;
> +		return ntohs(sockaddr_ip6->sin6_port);
> +	}
> +#endif
> +	}
> +	WARN_ON_ONCE(1);
> +	return 0;
> +}
> +
> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
> +			    int addrlen)
> +{
> +	const struct landlock_ruleset *const dom =
> +		landlock_get_current_domain();
> +
> +	if (!dom)
> +		return 0;
> +
> +	/* Check if it's a TCP socket. */
> +	if (sock->type != SOCK_STREAM)
> +		return 0;
> +
> +	switch (address->sa_family) {
> +	case AF_UNSPEC:
> +	case AF_INET:
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6:
> +#endif
> +		return check_socket_access(dom, get_port(address),
> +					   LANDLOCK_ACCESS_NET_BIND_TCP);
> +	default:
> +		return 0;

You can remove this default case and move the return 0 at the end of the 
function.


> +	}
> +}
> +
> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
> +			       int addrlen)
> +{
> +	const struct landlock_ruleset *const dom =
> +		landlock_get_current_domain();
> +
> +	if (!dom)
> +		return 0;
> +
> +	/* Check if it's a TCP socket. */
> +	if (sock->type != SOCK_STREAM)
> +		return 0;
> +
> +	/* Check if the hook is AF_INET* socket's action. */
> +	switch (address->sa_family) {
> +	case AF_INET:
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6:
> +#endif
> +		return check_socket_access(dom, get_port(address),
> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
> +	case AF_UNSPEC: {
> +		u16 i;

You can move "i" after the "dom" declaration to remove the extra braces.


> +
> +		/*
> +		 * If just in a layer a mask supports connect access,
> +		 * the socket_connect() hook with AF_UNSPEC family flag
> +		 * must be banned. This prevents from disconnecting already
> +		 * connected sockets.
> +		 */
> +		for (i = 0; i < dom->num_layers; i++) {
> +			if (landlock_get_net_access_mask(dom, i) &
> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
> +				return -EACCES;

I'm wondering if this is the right error code for this case. EPERM may 
be more appropriate.

Thinking more about this case, I don't understand what is the rationale 
to deny such action. What would be the consequence to always allow 
connection with AF_UNSPEC (i.e. to disconnect a socket)?


> +		}
> +	}
> +	}
> +	return 0;
> +}
> +
> +static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
> +	LSM_HOOK_INIT(socket_bind, hook_socket_bind),
> +	LSM_HOOK_INIT(socket_connect, hook_socket_connect),
> +};
> +
> +__init void landlock_add_net_hooks(void)
> +{
> +	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
> +			   LANDLOCK_NAME);
> +}
> diff --git a/security/landlock/net.h b/security/landlock/net.h
> new file mode 100644
> index 000000000000..0da1d9dff5ab
> --- /dev/null
> +++ b/security/landlock/net.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Landlock LSM - Network management and hooks
> + *
> + * Copyright © 2022 Huawei Tech. Co., Ltd.
> + */
> +
> +#ifndef _SECURITY_LANDLOCK_NET_H
> +#define _SECURITY_LANDLOCK_NET_H
> +
> +#include "common.h"
> +#include "ruleset.h"
> +#include "setup.h"
> +
> +#if IS_ENABLED(CONFIG_INET)
> +__init void landlock_add_net_hooks(void);
> +
> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> +			     const u16 port, access_mask_t access_rights);
> +#else /* IS_ENABLED(CONFIG_INET) */
> +static inline void landlock_add_net_hooks(void)
> +{
> +}
> +#endif /* IS_ENABLED(CONFIG_INET) */
> +
> +#endif /* _SECURITY_LANDLOCK_NET_H */
> diff --git a/security/landlock/setup.c b/security/landlock/setup.c
> index 3f196d2ce4f9..7e4a598177b8 100644
> --- a/security/landlock/setup.c
> +++ b/security/landlock/setup.c
> @@ -14,6 +14,7 @@
>   #include "fs.h"
>   #include "ptrace.h"
>   #include "setup.h"
> +#include "net.h"
> 
>   bool landlock_initialized __lsm_ro_after_init = false;
> 
> @@ -29,6 +30,7 @@ static int __init landlock_init(void)
>   	landlock_add_cred_hooks();
>   	landlock_add_ptrace_hooks();
>   	landlock_add_fs_hooks();
> +	landlock_add_net_hooks();
>   	landlock_initialized = true;
>   	pr_info("Up and running.\n");
>   	return 0;
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index c5a6ad4e2fca..7853f32e8325 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -29,6 +29,7 @@
>   #include "cred.h"
>   #include "fs.h"
>   #include "limits.h"
> +#include "net.h"
>   #include "ruleset.h"
>   #include "setup.h"
> 
> @@ -74,7 +75,8 @@ static void build_check_abi(void)
>   {
>   	struct landlock_ruleset_attr ruleset_attr;
>   	struct landlock_path_beneath_attr path_beneath_attr;
> -	size_t ruleset_size, path_beneath_size;
> +	struct landlock_net_service_attr net_service_attr;
> +	size_t ruleset_size, path_beneath_size, net_service_size;
> 
>   	/*
>   	 * For each user space ABI structures, first checks that there is no
> @@ -90,6 +92,11 @@ static void build_check_abi(void)
>   	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
>   	BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
>   	BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
> +
> +	net_service_size = sizeof(net_service_attr.allowed_access);
> +	net_service_size += sizeof(net_service_attr.port);
> +	BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size);
> +	BUILD_BUG_ON(sizeof(net_service_attr) != 10);
>   }
> 
>   /* Ruleset handling */
> @@ -322,13 +329,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>   	return err;
>   }
> 
> +static int add_rule_net_service(struct landlock_ruleset *ruleset,
> +				const void __user *const rule_attr)
> +{
> +#if IS_ENABLED(CONFIG_INET)
> +	struct landlock_net_service_attr net_service_attr;
> +	int res;
> +	u32 mask;

access_mask_t mask;


> +
> +	/* Copies raw user space buffer, only one type for now. */
> +	res = copy_from_user(&net_service_attr, rule_attr,
> +			     sizeof(net_service_attr));
> +	if (res)
> +		return -EFAULT;
> +
> +	/*
> +	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
> +	 * are ignored by network actions.
> +	 */
> +	if (!net_service_attr.allowed_access)
> +		return -ENOMSG;
> +
> +	/*
> +	 * Checks that allowed_access matches the @ruleset constraints
> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
> +	 */
> +	mask = landlock_get_net_access_mask(ruleset, 0);
> +	if ((net_service_attr.allowed_access | mask) != mask)
> +		return -EINVAL;
> +
> +	/* Denies inserting a rule with port 0. */
> +	if (net_service_attr.port == 0)
> +		return -EINVAL;
> +
> +	/* Imports the new rule. */
> +	return landlock_append_net_rule(ruleset, net_service_attr.port,
> +					net_service_attr.allowed_access);
> +#else /* IS_ENABLED(CONFIG_INET) */
> +	return -EAFNOSUPPORT;
> +#endif /* IS_ENABLED(CONFIG_INET) */
> +}
> +
>   /**
>    * sys_landlock_add_rule - Add a new rule to a ruleset
>    *
>    * @ruleset_fd: File descriptor tied to the ruleset that should be extended
>    *		with the new rule.
> - * @rule_type: Identify the structure type pointed to by @rule_attr (only
> - *             %LANDLOCK_RULE_PATH_BENEATH for now).
> + * @rule_type: Identify the structure type pointed to by @rule_attr:
> + *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_SERVICE.
>    * @rule_attr: Pointer to a rule (only of type &struct
>    *             landlock_path_beneath_attr for now).
>    * @flags: Must be 0.
> @@ -339,6 +387,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>    * Possible returned errors are:
>    *
>    * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
> + * - %EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not

%LANDLOCK_RULE_NET_SERVICE


> + *   supported by the running kernel;
>    * - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
>    *   &landlock_path_beneath_attr.allowed_access is not a subset of the
>    *   ruleset handled accesses);
> @@ -373,6 +423,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	case LANDLOCK_RULE_PATH_BENEATH:
>   		err = add_rule_path_beneath(ruleset, rule_attr);
>   		break;
> +	case LANDLOCK_RULE_NET_SERVICE:
> +		err = add_rule_net_service(ruleset, rule_attr);
> +		break;
>   	default:
>   		err = -EINVAL;
>   		break;
> --
> 2.25.1
> 

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

* Re: [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset()
  2022-10-21 15:26 ` [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
@ 2022-11-17 18:43   ` Mickaël Salaün
  2022-11-28  4:02     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:43 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> This commit moves enforce_ruleset() helper function to common.h so that
> to be used both by filesystem tests and network ones.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Refactors commit message.
> 
> Changes since v6:
> * None.
> 
> Changes since v5:
> * Splits commit.
> * Moves enforce_ruleset helper into common.h
> * Formats code with clang-format-14.
> 
> ---
>   tools/testing/selftests/landlock/common.h  | 10 ++++++++++
>   tools/testing/selftests/landlock/fs_test.c | 10 ----------
>   2 files changed, 10 insertions(+), 10 deletions(-)
> 
> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
> index d7987ae8d7fc..bafed1c0c2a6 100644
> --- a/tools/testing/selftests/landlock/common.h
> +++ b/tools/testing/selftests/landlock/common.h
> @@ -256,3 +256,13 @@ static int __maybe_unused send_fd(int usock, int fd_tx)
>   		return -errno;
>   	return 0;
>   }
> +
> +__attribute__((__unused__)) static void

We can now use __maybe_unused instead. This enables to avoid 
checkpatch.pl warning.


> +enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
> +{
> +	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> +	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
> +	{
> +		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
> +	}
> +}
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index d5dab986f612..20c1ac8485f1 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -563,16 +563,6 @@ static int create_ruleset(struct __test_metadata *const _metadata,
>   	return ruleset_fd;
>   }
> 
> -static void enforce_ruleset(struct __test_metadata *const _metadata,
> -			    const int ruleset_fd)
> -{
> -	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> -	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
> -	{
> -		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
> -	}
> -}
> -
>   TEST_F_FORK(layout1, proc_nsfs)
>   {
>   	const struct rule rules[] = {
> --
> 2.25.1
> 

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

* Re: [PATCH v8 12/12] landlock: Document Landlock's network support
  2022-10-21 15:26 ` [PATCH v8 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
@ 2022-11-17 18:44   ` Mickaël Salaün
  2022-11-28  6:44     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:44 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-doc


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> Describes network access rules for TCP sockets. Adds network access
> example in the tutorial. Points out AF_UNSPEC socket family behaviour.
> Adds kernel configuration support for network.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Fixes documentaion logic errors and typos as Mickaёl suggested:
> https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/
> 
> Changes since v6:
> * Adds network support documentaion.
> 
> ---
>   Documentation/userspace-api/landlock.rst | 72 +++++++++++++++++++-----
>   1 file changed, 59 insertions(+), 13 deletions(-)
> 
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index d8cd8cd9ce25..d0610ec9ce05 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -11,10 +11,10 @@ Landlock: unprivileged access control
>   :Date: October 2022
> 
>   The goal of Landlock is to enable to restrict ambient rights (e.g. global
> -filesystem access) for a set of processes.  Because Landlock is a stackable
> -LSM, it makes possible to create safe security sandboxes as new security layers
> -in addition to the existing system-wide access-controls. This kind of sandbox
> -is expected to help mitigate the security impact of bugs or
> +filesystem or network access) for a set of processes.  Because Landlock
> +is a stackable LSM, it makes possible to create safe security sandboxes as new
> +security layers in addition to the existing system-wide access-controls. This
> +kind of sandbox is expected to help mitigate the security impact of bugs or
>   unexpected/malicious behaviors in user space applications.  Landlock empowers
>   any process, including unprivileged ones, to securely restrict themselves.
> 
> @@ -30,18 +30,20 @@ Landlock rules
> 
>   A Landlock rule describes an action on an object.  An object is currently a
>   file hierarchy, and the related filesystem actions are defined with `access
> -rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
> -the thread enforcing it, and its future children.
> +rights`_.  Since ABI version 4 a port data appears with related network actions
> +for TCP socket families.  A set of rules is aggregated in a ruleset, which
> +can then restrict the thread enforcing it, and its future children.
> 
>   Defining and enforcing a security policy
>   ----------------------------------------
> 
>   We first need to define the ruleset that will contain our rules.  For this
>   example, the ruleset will contain rules that only allow read actions, but write
> -actions will be denied.  The ruleset then needs to handle both of these kind of
> +actions will be denied. The ruleset then needs to handle both of these kind of
>   actions.  This is required for backward and forward compatibility (i.e. the
>   kernel and user space may not know each other's supported restrictions), hence
> -the need to be explicit about the denied-by-default access rights.
> +the need to be explicit about the denied-by-default access rights.  Also ruleset
> +will have network rules for specific ports, so it should handle network actions.
> 
>   .. code-block:: c
> 
> @@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
>               LANDLOCK_ACCESS_FS_MAKE_SYM |
>               LANDLOCK_ACCESS_FS_REFER |
>               LANDLOCK_ACCESS_FS_TRUNCATE,
> +        .handled_access_net =
> +            LANDLOCK_ACCESS_NET_BIND_TCP |
> +            LANDLOCK_ACCESS_NET_CONNECT_TCP,
>       };
> 
>   Because we may not know on which kernel version an application will be
> @@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
>   using.  To avoid binary enforcement (i.e. either all security features or
>   none), we can leverage a dedicated Landlock command to get the current version
>   of the Landlock ABI and adapt the handled accesses.  Let's check if we should
> -remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
> -access rights, which are only supported starting with the second and third
> -version of the ABI.
> +remove the `LANDLOCK_ACCESS_FS_REFER` or `LANDLOCK_ACCESS_FS_TRUNCATE` or
> +network access rights, which are only supported starting with the second,

This is a bad rebase.


> +third and fourth version of the ABI.
> 
>   .. code-block:: c
> 
>       int abi;
> 
> +    #define ACCESS_NET_BIND_CONNECT ( \
> +    LANDLOCK_ACCESS_NET_BIND_TCP | \
> +    LANDLOCK_ACCESS_NET_CONNECT_TCP)

Please add a 4-spaces prefix for these two lines.


> +
>       abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
>       if (abi < 0) {
>           /* Degrades gracefully if Landlock is not handled. */
> @@ -92,6 +101,9 @@ version of the ABI.
>       case 2:
>           /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>           ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
> +    case 3:
> +        /* Removes network support for ABI < 4 */
> +        ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;
>       }
> 
>   This enables to create an inclusive ruleset that will contain our rules.
> @@ -143,8 +155,22 @@ for the ruleset creation, by filtering access rights according to the Landlock
>   ABI version.  In this example, this is not required because all of the requested
>   ``allowed_access`` rights are already available in ABI 1.
> 
> +For network part we can add number of rules containing a port number and actions
> +that a process is allowed to do for certian ports.

For the network access-control, we can add a set of rules that allow to 
use a port number for a specific action.

> +
> +.. code-block:: c
> +
> +    struct landlock_net_service_attr net_service = {
> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> +        .port = 8080,
> +    };
> +
> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
> +                            &net_service, 0);
> +
>   We now have a ruleset with one rule allowing read access to ``/usr`` while > -denying all other handled accesses for the filesystem.  The next 
step is to
> +denying all other handled accesses for the filesystem.  The ruleset also contains
> +a rule allowing to bind current proccess to the port 8080.  The next step is to

We now have a ruleset with the first rule allowing read access to 
``/usr`` while denying all other handled accesses for the filesystem, 
and a second rule allowing TCP binding on port 8080.


>   restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>   binary).
> 
> @@ -296,6 +322,13 @@ not.  It is also possible to pass such file descriptors between processes,
>   keeping their Landlock properties, even when these processes do not have an
>   enforced Landlock ruleset.
> 
> +AF_UNSPEC socket family
> +-----------------------
> +
> +Sockets of AF_UNSPEC family types are treated as AF_INET(IPv4) socket for bind()

``AF_UNSPEC`` sockets are handled as ``AF_INET`` sockets for the TCP 
binding action. However, connecting an ``AF_UNSPEC`` socket is always 
denied if at least one Landlock domain handles 
``LANDLOCK_NET_CONNECT_TCP``.  This restriction protects against…

As commented in patch 8/12, I don't think this is the right approach 
anyway. It may then not be worth a dedicated section.


> +action.  But connect() one is not allowed by Landlock for AF_UNSPEC sockets. This
> +logic prevents from disconnecting already connected sockets.
> +
>   Compatibility
>   =============
> 
> @@ -355,7 +388,7 @@ Access rights
>   -------------
> 
>   .. kernel-doc:: include/uapi/linux/landlock.h
> -    :identifiers: fs_access
> +    :identifiers: fs_access net_access
> 
>   Creating a new ruleset
>   ----------------------
> @@ -374,6 +407,7 @@ Extending a ruleset
> 
>   .. kernel-doc:: include/uapi/linux/landlock.h
>       :identifiers: landlock_rule_type landlock_path_beneath_attr
> +                  landlock_net_service_attr
> 
>   Enforcing a ruleset
>   -------------------
> @@ -451,6 +485,13 @@ always allowed when using a kernel that only supports the first or second ABI.
>   Starting with the Landlock ABI version 3, it is now possible to securely control
>   truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
> 
> +Network support (ABI < 4)
> +-------------------------
> +
> +Starting with the Landlock ABI version 4, it is now possible to restrict TCP
> +sockets' bind() and connect() actions for specific ports allowing processes
> +to establish restricted connections.

it is now possible to restrict TCP bind and connect actions to only a 
set of allowed ports.

> +
>   .. _kernel_support:
> 
>   Kernel support
> @@ -469,6 +510,11 @@ still enable it by adding ``lsm=landlock,[...]`` to
>   Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
>   configuration.
> 
> +To be able to explicitly allow TCP operations (e.g., adding a network rule with
> +`LANDLOCK_ACCESS_NET_TCP_BIND`), the kernel must support TCP (`CONFIG_INET=y`).

Please use double backquotes everywhere, cf. 
https://git.kernel.org/torvalds/c/2fff00c81d4c


> +Otherwise, sys_landlock_add_rule() returns an `EAFNOSUPPORT` error, which can
> +safely be ignored because this kind of TCP operation is already not possible.
> +
>   Questions and answers
>   =====================
> 
> --
> 2.25.1
> 

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

* [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-17 18:41   ` Mickaël Salaün
@ 2022-11-17 18:55     ` Mickaël Salaün
  2022-11-18  9:16       ` Mickaël Salaün
  2022-11-28  3:02       ` Konstantin Meskhidze (A)
  2022-11-22 17:17     ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Mickaël Salaün
  2022-11-28  2:58     ` Konstantin Meskhidze (A)
  2 siblings, 2 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-17 18:55 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: Mickaël Salaün, artem.kuzin, gnoack3000,
	willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel

Allow mount point and root directory changes when there is no filesystem
rule tied to the current Landlock domain.  This doesn't change anything
for now because a domain must have at least a (filesystem) rule, but
this will change when other rule types will come.  For instance, a
domain only restricting the network should have no impact on filesystem
restrictions.

Add a new get_current_fs_domain() helper to quickly check filesystem
rule existence for all filesystem LSM hooks.

Remove unnecessary inlining.

Signed-off-by: Mickaël Salaün <mic@digikod.net>
---
 security/landlock/fs.c       | 73 ++++++++++++++++++------------------
 security/landlock/ruleset.h  | 25 +++++++++++-
 security/landlock/syscalls.c |  6 +--
 3 files changed, 62 insertions(+), 42 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 0d57c6479d29..0ae54a639e16 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -150,16 +150,6 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
 	LANDLOCK_ACCESS_FS_TRUNCATE)
 /* clang-format on */
 
-/*
- * All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
- * entries when we need to get the absolute handled access masks.
- */
-/* clang-format off */
-#define ACCESS_INITIALLY_DENIED ( \
-	LANDLOCK_ACCESS_FS_REFER)
-/* clang-format on */
-
 /*
  * @path: Should have been checked by get_path_from_fd().
  */
@@ -179,8 +169,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 
 	/* Transforms relative access rights to absolute ones. */
 	access_rights |= LANDLOCK_MASK_ACCESS_FS &
-			 ~(landlock_get_fs_access_mask(ruleset, 0) |
-			   ACCESS_INITIALLY_DENIED);
+			 ~landlock_get_fs_access_mask(ruleset, 0);
 	object = get_inode_object(d_backing_inode(path->dentry));
 	if (IS_ERR(object))
 		return PTR_ERR(object);
@@ -287,14 +276,15 @@ static inline bool is_nouser_or_private(const struct dentry *dentry)
 		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
 }
 
-static inline access_mask_t
-get_handled_accesses(const struct landlock_ruleset *const domain)
+static access_mask_t
+get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
 {
-	access_mask_t access_dom = ACCESS_INITIALLY_DENIED;
+	access_mask_t access_dom = 0;
 	size_t layer_level;
 
 	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
-		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
+		access_dom |=
+			landlock_get_raw_fs_access_mask(domain, layer_level);
 	return access_dom & LANDLOCK_MASK_ACCESS_FS;
 }
 
@@ -331,13 +321,8 @@ init_layer_masks(const struct landlock_ruleset *const domain,
 
 		for_each_set_bit(access_bit, &access_req,
 				 ARRAY_SIZE(*layer_masks)) {
-			/*
-			 * Artificially handles all initially denied by default
-			 * access rights.
-			 */
 			if (BIT_ULL(access_bit) &
-			    (landlock_get_fs_access_mask(domain, layer_level) |
-			     ACCESS_INITIALLY_DENIED)) {
+			    landlock_get_fs_access_mask(domain, layer_level)) {
 				(*layer_masks)[access_bit] |=
 					BIT_ULL(layer_level);
 				handled_accesses |= BIT_ULL(access_bit);
@@ -347,6 +332,24 @@ init_layer_masks(const struct landlock_ruleset *const domain,
 	return handled_accesses;
 }
 
+static access_mask_t
+get_handled_fs_accesses(const struct landlock_ruleset *const domain)
+{
+	/* Handles all initially denied by default access rights. */
+	return get_raw_handled_fs_accesses(domain) | ACCESS_FS_INITIALLY_DENIED;
+}
+
+static const struct landlock_ruleset *get_current_fs_domain(void)
+{
+	const struct landlock_ruleset *const dom =
+		landlock_get_current_domain();
+
+	if (!dom || !get_raw_handled_fs_accesses(dom))
+		return NULL;
+
+	return dom;
+}
+
 /*
  * Check that a destination file hierarchy has more restrictions than a source
  * file hierarchy.  This is only used for link and rename actions.
@@ -519,7 +522,7 @@ static bool is_access_to_paths_allowed(
 		 * a superset of the meaningful requested accesses).
 		 */
 		access_masked_parent1 = access_masked_parent2 =
-			get_handled_accesses(domain);
+			get_handled_fs_accesses(domain);
 		is_dom_check = true;
 	} else {
 		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
@@ -648,11 +651,10 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
 	return -EACCES;
 }
 
-static inline int current_check_access_path(const struct path *const path,
+static int current_check_access_path(const struct path *const path,
 					    const access_mask_t access_request)
 {
-	const struct landlock_ruleset *const dom =
-		landlock_get_current_domain();
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
 
 	if (!dom)
 		return 0;
@@ -815,8 +817,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 				    struct dentry *const new_dentry,
 				    const bool removable, const bool exchange)
 {
-	const struct landlock_ruleset *const dom =
-		landlock_get_current_domain();
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
 	bool allow_parent1, allow_parent2;
 	access_mask_t access_request_parent1, access_request_parent2;
 	struct path mnt_dir;
@@ -1050,7 +1051,7 @@ static int hook_sb_mount(const char *const dev_name,
 			 const struct path *const path, const char *const type,
 			 const unsigned long flags, void *const data)
 {
-	if (!landlock_get_current_domain())
+	if (!get_current_fs_domain())
 		return 0;
 	return -EPERM;
 }
@@ -1058,7 +1059,7 @@ static int hook_sb_mount(const char *const dev_name,
 static int hook_move_mount(const struct path *const from_path,
 			   const struct path *const to_path)
 {
-	if (!landlock_get_current_domain())
+	if (!get_current_fs_domain())
 		return 0;
 	return -EPERM;
 }
@@ -1069,14 +1070,14 @@ static int hook_move_mount(const struct path *const from_path,
  */
 static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
 {
-	if (!landlock_get_current_domain())
+	if (!get_current_fs_domain())
 		return 0;
 	return -EPERM;
 }
 
 static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
 {
-	if (!landlock_get_current_domain())
+	if (!get_current_fs_domain())
 		return 0;
 	return -EPERM;
 }
@@ -1092,7 +1093,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
 static int hook_sb_pivotroot(const struct path *const old_path,
 			     const struct path *const new_path)
 {
-	if (!landlock_get_current_domain())
+	if (!get_current_fs_domain())
 		return 0;
 	return -EPERM;
 }
@@ -1128,8 +1129,7 @@ static int hook_path_mknod(const struct path *const dir,
 			   struct dentry *const dentry, const umode_t mode,
 			   const unsigned int dev)
 {
-	const struct landlock_ruleset *const dom =
-		landlock_get_current_domain();
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
 
 	if (!dom)
 		return 0;
@@ -1208,8 +1208,7 @@ static int hook_file_open(struct file *const file)
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
 	access_mask_t open_access_request, full_access_request, allowed_access;
 	const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
-	const struct landlock_ruleset *const dom =
-		landlock_get_current_domain();
+	const struct landlock_ruleset *const dom = get_current_fs_domain();
 
 	if (!dom)
 		return 0;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f2ad932d396c..ca46393ef3bb 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -15,10 +15,21 @@
 #include <linux/rbtree.h>
 #include <linux/refcount.h>
 #include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
 
 #include "limits.h"
 #include "object.h"
 
+/*
+ * All access rights that are denied by default whether they are handled or not
+ * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
+ * entries when we need to get the absolute handled access masks.
+ */
+/* clang-format off */
+#define ACCESS_FS_INITIALLY_DENIED ( \
+	LANDLOCK_ACCESS_FS_REFER)
+/* clang-format on */
+
 typedef u16 access_mask_t;
 /* Makes sure all filesystem access rights can be stored. */
 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
@@ -197,11 +208,21 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
 }
 
 static inline access_mask_t
-landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
-			    const u16 layer_level)
+landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
+				const u16 layer_level)
 {
 	return (ruleset->access_masks[layer_level] >>
 		LANDLOCK_SHIFT_ACCESS_FS) &
 	       LANDLOCK_MASK_ACCESS_FS;
 }
+
+static inline access_mask_t
+landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
+			    const u16 layer_level)
+{
+	/* Handles all initially denied by default access rights. */
+	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
+	       ACCESS_FS_INITIALLY_DENIED;
+}
+
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 71aca7f990bc..d35cd5d304db 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -310,6 +310,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	struct path path;
 	struct landlock_ruleset *ruleset;
 	int res, err;
+	access_mask_t mask;
 
 	if (!landlock_initialized)
 		return -EOPNOTSUPP;
@@ -348,9 +349,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	 * Checks that allowed_access matches the @ruleset constraints
 	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
 	 */
-	if ((path_beneath_attr.allowed_access |
-	     landlock_get_fs_access_mask(ruleset, 0)) !=
-	    landlock_get_fs_access_mask(ruleset, 0)) {
+	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
+	if ((path_beneath_attr.allowed_access | mask) != mask) {
 		err = -EINVAL;
 		goto out_put_ruleset;
 	}
-- 
2.38.1


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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-17 18:55     ` [PATCH] landlock: Allow filesystem layout changes for domains without such rule type Mickaël Salaün
@ 2022-11-18  9:16       ` Mickaël Salaün
  2022-11-28  3:04         ` Konstantin Meskhidze (A)
  2022-11-28  3:02       ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-18  9:16 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel

Konstantin, this patch should apply cleanly just after "01/12 landlock: 
Make ruleset's access masks more generic". You can easily get this patch 
with https://git.kernel.org/pub/scm/utils/b4/b4.git/
Some adjustments are needed for the following patches. Feel free to 
review this patch.


On 17/11/2022 19:55, Mickaël Salaün wrote:
> Allow mount point and root directory changes when there is no filesystem
> rule tied to the current Landlock domain.  This doesn't change anything
> for now because a domain must have at least a (filesystem) rule, but
> this will change when other rule types will come.  For instance, a
> domain only restricting the network should have no impact on filesystem
> restrictions.
> 
> Add a new get_current_fs_domain() helper to quickly check filesystem
> rule existence for all filesystem LSM hooks.
> 
> Remove unnecessary inlining.
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
>   security/landlock/fs.c       | 73 ++++++++++++++++++------------------
>   security/landlock/ruleset.h  | 25 +++++++++++-
>   security/landlock/syscalls.c |  6 +--
>   3 files changed, 62 insertions(+), 42 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 0d57c6479d29..0ae54a639e16 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -150,16 +150,6 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
>   	LANDLOCK_ACCESS_FS_TRUNCATE)
>   /* clang-format on */
>   
> -/*
> - * All access rights that are denied by default whether they are handled or not
> - * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
> - * entries when we need to get the absolute handled access masks.
> - */
> -/* clang-format off */
> -#define ACCESS_INITIALLY_DENIED ( \
> -	LANDLOCK_ACCESS_FS_REFER)
> -/* clang-format on */
> -
>   /*
>    * @path: Should have been checked by get_path_from_fd().
>    */
> @@ -179,8 +169,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>   
>   	/* Transforms relative access rights to absolute ones. */
>   	access_rights |= LANDLOCK_MASK_ACCESS_FS &
> -			 ~(landlock_get_fs_access_mask(ruleset, 0) |
> -			   ACCESS_INITIALLY_DENIED);
> +			 ~landlock_get_fs_access_mask(ruleset, 0);
>   	object = get_inode_object(d_backing_inode(path->dentry));
>   	if (IS_ERR(object))
>   		return PTR_ERR(object);
> @@ -287,14 +276,15 @@ static inline bool is_nouser_or_private(const struct dentry *dentry)
>   		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
>   }
>   
> -static inline access_mask_t
> -get_handled_accesses(const struct landlock_ruleset *const domain)
> +static access_mask_t
> +get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
>   {
> -	access_mask_t access_dom = ACCESS_INITIALLY_DENIED;
> +	access_mask_t access_dom = 0;
>   	size_t layer_level;
>   
>   	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> -		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
> +		access_dom |=
> +			landlock_get_raw_fs_access_mask(domain, layer_level);
>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>   }
>   
> @@ -331,13 +321,8 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>   
>   		for_each_set_bit(access_bit, &access_req,
>   				 ARRAY_SIZE(*layer_masks)) {
> -			/*
> -			 * Artificially handles all initially denied by default
> -			 * access rights.
> -			 */
>   			if (BIT_ULL(access_bit) &
> -			    (landlock_get_fs_access_mask(domain, layer_level) |
> -			     ACCESS_INITIALLY_DENIED)) {
> +			    landlock_get_fs_access_mask(domain, layer_level)) {
>   				(*layer_masks)[access_bit] |=
>   					BIT_ULL(layer_level);
>   				handled_accesses |= BIT_ULL(access_bit);
> @@ -347,6 +332,24 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>   	return handled_accesses;
>   }
>   
> +static access_mask_t
> +get_handled_fs_accesses(const struct landlock_ruleset *const domain)
> +{
> +	/* Handles all initially denied by default access rights. */
> +	return get_raw_handled_fs_accesses(domain) | ACCESS_FS_INITIALLY_DENIED;
> +}
> +
> +static const struct landlock_ruleset *get_current_fs_domain(void)
> +{
> +	const struct landlock_ruleset *const dom =
> +		landlock_get_current_domain();
> +
> +	if (!dom || !get_raw_handled_fs_accesses(dom))
> +		return NULL;
> +
> +	return dom;
> +}
> +
>   /*
>    * Check that a destination file hierarchy has more restrictions than a source
>    * file hierarchy.  This is only used for link and rename actions.
> @@ -519,7 +522,7 @@ static bool is_access_to_paths_allowed(
>   		 * a superset of the meaningful requested accesses).
>   		 */
>   		access_masked_parent1 = access_masked_parent2 =
> -			get_handled_accesses(domain);
> +			get_handled_fs_accesses(domain);
>   		is_dom_check = true;
>   	} else {
>   		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
> @@ -648,11 +651,10 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>   	return -EACCES;
>   }
>   
> -static inline int current_check_access_path(const struct path *const path,
> +static int current_check_access_path(const struct path *const path,
>   					    const access_mask_t access_request)
>   {
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   
>   	if (!dom)
>   		return 0;
> @@ -815,8 +817,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>   				    struct dentry *const new_dentry,
>   				    const bool removable, const bool exchange)
>   {
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   	bool allow_parent1, allow_parent2;
>   	access_mask_t access_request_parent1, access_request_parent2;
>   	struct path mnt_dir;
> @@ -1050,7 +1051,7 @@ static int hook_sb_mount(const char *const dev_name,
>   			 const struct path *const path, const char *const type,
>   			 const unsigned long flags, void *const data)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1058,7 +1059,7 @@ static int hook_sb_mount(const char *const dev_name,
>   static int hook_move_mount(const struct path *const from_path,
>   			   const struct path *const to_path)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1069,14 +1070,14 @@ static int hook_move_mount(const struct path *const from_path,
>    */
>   static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
>   
>   static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1092,7 +1093,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
>   static int hook_sb_pivotroot(const struct path *const old_path,
>   			     const struct path *const new_path)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1128,8 +1129,7 @@ static int hook_path_mknod(const struct path *const dir,
>   			   struct dentry *const dentry, const umode_t mode,
>   			   const unsigned int dev)
>   {
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   
>   	if (!dom)
>   		return 0;
> @@ -1208,8 +1208,7 @@ static int hook_file_open(struct file *const file)
>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>   	access_mask_t open_access_request, full_access_request, allowed_access;
>   	const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   
>   	if (!dom)
>   		return 0;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index f2ad932d396c..ca46393ef3bb 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -15,10 +15,21 @@
>   #include <linux/rbtree.h>
>   #include <linux/refcount.h>
>   #include <linux/workqueue.h>
> +#include <uapi/linux/landlock.h>
>   
>   #include "limits.h"
>   #include "object.h"
>   
> +/*
> + * All access rights that are denied by default whether they are handled or not
> + * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
> + * entries when we need to get the absolute handled access masks.
> + */
> +/* clang-format off */
> +#define ACCESS_FS_INITIALLY_DENIED ( \
> +	LANDLOCK_ACCESS_FS_REFER)
> +/* clang-format on */
> +
>   typedef u16 access_mask_t;
>   /* Makes sure all filesystem access rights can be stored. */
>   static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> @@ -197,11 +208,21 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>   }
>   
>   static inline access_mask_t
> -landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> -			    const u16 layer_level)
> +landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
> +				const u16 layer_level)
>   {
>   	return (ruleset->access_masks[layer_level] >>
>   		LANDLOCK_SHIFT_ACCESS_FS) &
>   	       LANDLOCK_MASK_ACCESS_FS;
>   }
> +
> +static inline access_mask_t
> +landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> +			    const u16 layer_level)
> +{
> +	/* Handles all initially denied by default access rights. */
> +	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
> +	       ACCESS_FS_INITIALLY_DENIED;
> +}
> +
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 71aca7f990bc..d35cd5d304db 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -310,6 +310,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	struct path path;
>   	struct landlock_ruleset *ruleset;
>   	int res, err;
> +	access_mask_t mask;
>   
>   	if (!landlock_initialized)
>   		return -EOPNOTSUPP;
> @@ -348,9 +349,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	 * Checks that allowed_access matches the @ruleset constraints
>   	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>   	 */
> -	if ((path_beneath_attr.allowed_access |
> -	     landlock_get_fs_access_mask(ruleset, 0)) !=
> -	    landlock_get_fs_access_mask(ruleset, 0)) {
> +	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
> +	if ((path_beneath_attr.allowed_access | mask) != mask) {
>   		err = -EINVAL;
>   		goto out_put_ruleset;
>   	}

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

* Re: [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule
  2022-11-17 18:41   ` Mickaël Salaün
  2022-11-17 18:55     ` [PATCH] landlock: Allow filesystem layout changes for domains without such rule type Mickaël Salaün
@ 2022-11-22 17:17     ` Mickaël Salaün
  2022-11-28  3:06       ` Konstantin Meskhidze (A)
  2022-11-28  2:58     ` Konstantin Meskhidze (A)
  2 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-22 17:17 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 17/11/2022 19:41, Mickaël Salaün wrote:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:

[...]

>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index f2ad932d396c..608ab356bc3e 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -49,6 +49,46 @@ struct landlock_layer {
>>    	access_mask_t access;
>>    };
>>
>> +/**
>> + * union landlock_key - Key of a ruleset's red-black tree
>> + */
>> +union landlock_key {
>> +	/**
>> +	 * @object: Pointer to identify a kernel object (e.g. an inode).
>> +	 */
>> +	struct landlock_object *object;
>> +	/**
>> +	 * @data: A raw data value to identify a network socket port.
> 
> "Raw data to identify an arbitrary 32-bit value (e.g. a TCP port)."
> 
> 
>> +	 */
>> +	uintptr_t data;
>> +};
>> +
>> +/**
>> + * enum landlock_key_type - Type of &union landlock_key
>> + */
>> +enum landlock_key_type {
>> +	/**
>> +	 * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
>> +	 * keys.
>> +	 */
>> +	LANDLOCK_KEY_INODE = 1,
>> +};
>> +
>> +/**
>> + * struct landlock_id - Unique rule identifier for a ruleset
>> + */
>> +struct landlock_id {
>> +	/**
>> +	 * @key: A union to identify either a kernel object (e.g. an inode) or
>> +	 * a raw data value (e.g. a network socket port).
> 
> "a 32-bit value (e.g. a TCP port)."

Instead:
@key: Identifies either a kernel object (e.g. an inode) or a raw value 
(e.g. a TCP port).

> 
> 
>> +	 */
>> +	union landlock_key key;
>> +	/**
>> +	 * @type: A enumerator to identify the type of landlock_ruleset's root tree.

@type: Type of a landlock_ruleset's root tree.


>> +	 */
>> +	const enum landlock_key_type type;
>> +};

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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2022-11-16 14:25   ` Mickaël Salaün
@ 2022-11-28  2:49     ` Konstantin Meskhidze (A)
  2022-11-28 20:26       ` Mickaël Salaün
  2023-01-05  3:46     ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  2:49 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/16/2022 5:25 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This commit adds network demo. It's possible to allow a sandboxer to
>> bind/connect to a list of particular ports restricting network
>> actions to the rest of ports.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Removes network support if ABI < 4.
>> * Removes network support if not set by a user.
>> 
>> Changes since v6:
>> * Removes network support if ABI < 3.
>> 
>> Changes since v5:
>> * Makes network ports sandboxing optional.
>> * Fixes some logic errors.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Adds ENV_TCP_BIND_NAME "LL_TCP_BIND" and
>> ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" variables
>> to insert TCP ports.
>> * Renames populate_ruleset() to populate_ruleset_fs().
>> * Adds populate_ruleset_net() and parse_port_num() helpers.
>> * Refactors main() to support network sandboxing.
>> 
>> ---
>>   samples/landlock/sandboxer.c | 129 +++++++++++++++++++++++++++++++----
>>   1 file changed, 116 insertions(+), 13 deletions(-)
>> 
>> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
>> index fd4237c64fb2..68582b0d7c85 100644
>> --- a/samples/landlock/sandboxer.c
>> +++ b/samples/landlock/sandboxer.c
>> @@ -51,6 +51,8 @@ static inline int landlock_restrict_self(const int ruleset_fd,
>> 
>>   #define ENV_FS_RO_NAME "LL_FS_RO"
>>   #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_PATH_TOKEN ":"
>> 
>>   static int parse_path(char *env_path, const char ***const path_list)
>> @@ -71,6 +73,20 @@ static int parse_path(char *env_path, const char ***const path_list)
>>   	return num_paths;
>>   }
>> 
>> +static int parse_port_num(char *env_port)
>> +{
>> +	int i, num_ports = 0;
>> +
>> +	if (env_port) {
>> +		num_ports++;
>> +		for (i = 0; env_port[i]; i++) {
>> +			if (env_port[i] == ENV_PATH_TOKEN[0])
>> +				num_ports++;
>> +		}
>> +	}
>> +	return num_ports;
>> +}
>> +
>>   /* clang-format off */
>> 
>>   #define ACCESS_FILE ( \
>> @@ -81,8 +97,8 @@ static int parse_path(char *env_path, const char ***const path_list)
>> 
>>   /* clang-format on */
>> 
>> -static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>> -			    const __u64 allowed_access)
>> +static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
>> +			       const __u64 allowed_access)
>>   {
>>   	int num_paths, i, ret = 1;
>>   	char *env_path_name;
>> @@ -143,6 +159,48 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>>   	return ret;
>>   }
>> 
>> +static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>> +				const __u64 allowed_access)
>> +{
>> +	int num_ports, i, ret = 1;
>> +	char *env_port_name;
>> +	struct landlock_net_service_attr net_service = {
>> +		.allowed_access = 0,
>> +		.port = 0,
>> +	};
>> +
>> +	env_port_name = getenv(env_var);
>> +	if (!env_port_name) {
>> +		ret = 0;
>> +		goto out_free_name;
> 
> This is a bug because env_port_name is not allocated. This should simply
> return 0.

   Ok. I got it. Thanks.
> 
> 
>> +	}
>> +	env_port_name = strdup(env_port_name);
>> +	unsetenv(env_var);
>> +	num_ports = parse_port_num(env_port_name);
>> +
>> +	if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) {
>> +		ret = 0;
>> +		goto out_free_name;
>> +	}
>> +
>> +	for (i = 0; i < num_ports; i++) {
>> +		net_service.allowed_access = allowed_access;
>> +		net_service.port = atoi(strsep(&env_port_name, ENV_PATH_TOKEN));
>> +		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +				      &net_service, 0)) {
>> +			fprintf(stderr,
>> +				"Failed to update the ruleset with port \"%d\": %s\n",
>> +				net_service.port, strerror(errno));
>> +			goto out_free_name;
>> +		}
>> +	}
>> +	ret = 0;
>> +
>> +out_free_name:
>> +	free(env_port_name);
>> +	return ret;
>> +}
>> +
>>   /* clang-format off */
>> 
>>   #define ACCESS_FS_ROUGHLY_READ ( \
>> @@ -164,41 +222,63 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>>   	LANDLOCK_ACCESS_FS_REFER | \
>>   	LANDLOCK_ACCESS_FS_TRUNCATE)
>> 
>> +#define ACCESS_NET_BIND_CONNECT ( \
>> +	LANDLOCK_ACCESS_NET_BIND_TCP | \
>> +	LANDLOCK_ACCESS_NET_CONNECT_TCP)
> 
> You can remove ACCESS_NET_BIND_CONNECT and make the underlying access
> rights explicit.
> 
   Ok.
> 
>> +
>>   /* clang-format on */
>> 
>> -#define LANDLOCK_ABI_LAST 3
>> +#define LANDLOCK_ABI_LAST 4
>> 
>>   int main(const int argc, char *const argv[], char *const *const envp)
>>   {
>>   	const char *cmd_path;
>>   	char *const *cmd_argv;
>>   	int ruleset_fd, abi;
>> +	char *env_port_name;
>>   	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
>> -	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
>> +	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE,
>> +	      access_net_tcp = ACCESS_NET_BIND_CONNECT;
>>   	struct landlock_ruleset_attr ruleset_attr = {
>>   		.handled_access_fs = access_fs_rw,
>> +		.handled_access_net = access_net_tcp,
>>   	};
>> 
>>   	if (argc < 2) {
>>   		fprintf(stderr,
>> -			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
>> -			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
>> +			"usage: %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]);
>>   		fprintf(stderr,
>>   			"Launch a command in a restricted environment.\n\n");
>> -		fprintf(stderr, "Environment variables containing paths, "
>> -				"each separated by a colon:\n");
>> +		fprintf(stderr,
>> +			"Environment variables containing paths and ports "
>> +			"each separated by a colon:\n");
>>   		fprintf(stderr,
>>   			"* %s: list of paths allowed to be used in a read-only way.\n",
>>   			ENV_FS_RO_NAME);
>>   		fprintf(stderr,
>> -			"* %s: list of paths allowed to be used in a read-write way.\n",
>> +			"* %s: list of paths allowed to be used in a read-write way.\n\n",
>>   			ENV_FS_RW_NAME);
>> +		fprintf(stderr,
>> +			"Environment variables containing ports are optional "
>> +			"and could be skipped.\n");
>> +		fprintf(stderr,
>> +			"* %s: list of ports allowed to bind (server).\n",
>> +			ENV_TCP_BIND_NAME);
>> +		fprintf(stderr,
>> +			"* %s: list of ports allowed to connect (client).\n",
>> +			ENV_TCP_CONNECT_NAME);
>>   		fprintf(stderr,
>>   			"\nexample:\n"
>>   			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
>>   			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
>> +			"%s=\"9418\" "
>> +			"%s=\"80:443\" "
>>   			"%s bash -i\n\n",
>> -			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
>> +			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
>> +			ENV_TCP_CONNECT_NAME, argv[0]);
>>   		fprintf(stderr,
>>   			"This sandboxer can use Landlock features "
>>   			"up to ABI version %d.\n",
>> @@ -240,7 +320,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>   	case 2:
>>   		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>>   		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
>> -
>> +		__attribute__((fallthrough));
>> +	case 3:
>> +		/* Removes network support for ABI < 4 */
>> +		ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;
> 
> You can check the TCP environment variables here and error out if one is
> set.
> 
> Please keep the newline here.

   Ok. got it.
> 
> 
>>   		fprintf(stderr,
>>   			"Hint: You should update the running kernel "
>>   			"to leverage Landlock features "
>> @@ -259,16 +342,36 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>   	access_fs_ro &= ruleset_attr.handled_access_fs;
>>   	access_fs_rw &= ruleset_attr.handled_access_fs;
>> 
>> +	/* Removes bind access attribute if not supported by a user. */
>> +	env_port_name = getenv(ENV_TCP_BIND_NAME);
>> +	if (!env_port_name) {
> 
> You can move this logic at the populate_ruleset_net() call site and
> update this helper to not call getenv() twice for the same variable.
> 
   Ok. Thanks for the tip.
> 
>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
>> +	}
>> +	/* Removes connect access attribute if not supported by a user. */
>> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
>> +	if (!env_port_name) {
>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>> +	}
>> +	ruleset_attr.handled_access_net &= access_net_tcp;
> 
> There is no need for access_net_tcp.

   Do you mean to delete this var?
> 
>> +
>>   	ruleset_fd =
>>   		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>>   	if (ruleset_fd < 0) {
>>   		perror("Failed to create a ruleset");
>>   		return 1;
>>   	}
> 
> newline

   Got it.
> 
>> -	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
>> +	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
>> +		goto err_close_ruleset;
>> +	}
>> +	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
>> +		goto err_close_ruleset;
>> +	}
> 
> newline

   Ok.
> 
>> +	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
>> +				 LANDLOCK_ACCESS_NET_BIND_TCP)) {
>>   		goto err_close_ruleset;
>>   	}
>> -	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
>> +	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
>> +				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
>>   		goto err_close_ruleset;
>>   	}
> 
> newline

   Got it. Thnaks.
> 
>>   	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 01/12] landlock: Make ruleset's access masks more generic
  2022-11-17 18:41   ` Mickaël Salaün
@ 2022-11-28  2:53     ` Konstantin Meskhidze (A)
  2022-11-28 20:22       ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  2:53 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:41 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> To support network type rules, this modification renames ruleset's
>> access masks and modifies it's type to access_masks_t. This patch
>> adds filesystem helper functions to add and get filesystem mask.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Refactors commit message.
>> 
>> Changes since v6:
>> * Adds a new access_masks_t for struct ruleset.
>> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>>    because it OR values.
>> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_fs_access_mask().
>> 
>> Changes since v6:
>> * Adds a new access_masks_t for struct ruleset.
>> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>>    because it OR values.
>> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_fs_access_mask().
>> 
>> Changes since v5:
>> * Changes access_mask_t to u32.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Deletes struct landlock_access_mask.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds get_mask, set_mask helpers for filesystem.
>> * Adds new struct landlock_access_mask.
>> 
>> ---
>>   security/landlock/fs.c       | 10 +++++-----
>>   security/landlock/limits.h   |  1 +
>>   security/landlock/ruleset.c  | 17 +++++++++--------
>>   security/landlock/ruleset.h  | 35 +++++++++++++++++++++++++++++++----
>>   security/landlock/syscalls.c |  7 ++++---
>>   5 files changed, 50 insertions(+), 20 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index adcea0fe7e68..0d57c6479d29 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -178,9 +178,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>   		return -EINVAL;
>> 
>>   	/* Transforms relative access rights to absolute ones. */
>> -	access_rights |=
>> -		LANDLOCK_MASK_ACCESS_FS &
>> -		~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
>> +	access_rights |= LANDLOCK_MASK_ACCESS_FS &
>> +			 ~(landlock_get_fs_access_mask(ruleset, 0) |
>> +			   ACCESS_INITIALLY_DENIED);
>>   	object = get_inode_object(d_backing_inode(path->dentry));
>>   	if (IS_ERR(object))
>>   		return PTR_ERR(object);
>> @@ -294,7 +294,7 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
>>   	size_t layer_level;
>> 
>>   	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
>> -		access_dom |= domain->fs_access_masks[layer_level];
>> +		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
>>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;
> 
> You can remove `& LANDLOCK_MASK_ACCESS_FS` here because it is now part
> of landlock_get_fs_access_mask().

   Ok. I got it. Thanks.
> 
> 
>>   }
>> 
>> @@ -336,7 +336,7 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>>   			 * access rights.
>>   			 */
>>   			if (BIT_ULL(access_bit) &
>> -			    (domain->fs_access_masks[layer_level] |
>> +			    (landlock_get_fs_access_mask(domain, layer_level) |
>>   			     ACCESS_INITIALLY_DENIED)) {
>>   				(*layer_masks)[access_bit] |=
>>   					BIT_ULL(layer_level);
>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>> index 82288f0e9e5e..bafb3b8dc677 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -21,6 +21,7 @@
>>   #define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_TRUNCATE
>>   #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
>>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>> +#define LANDLOCK_SHIFT_ACCESS_FS	0
>> 
>>   /* clang-format on */
>> 
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 996484f98bfd..1f3188b4e313 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -29,7 +29,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   	struct landlock_ruleset *new_ruleset;
>> 
>>   	new_ruleset =
>> -		kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers),
>> +		kzalloc(struct_size(new_ruleset, access_masks, num_layers),
>>   			GFP_KERNEL_ACCOUNT);
>>   	if (!new_ruleset)
>>   		return ERR_PTR(-ENOMEM);
>> @@ -40,7 +40,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   	/*
>>   	 * hierarchy = NULL
>>   	 * num_rules = 0
>> -	 * fs_access_masks[] = 0
>> +	 * access_masks[] = 0
>>   	 */
>>   	return new_ruleset;
>>   }
>> @@ -55,7 +55,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask)
>>   		return ERR_PTR(-ENOMSG);
>>   	new_ruleset = create_ruleset(1);
>>   	if (!IS_ERR(new_ruleset))
>> -		new_ruleset->fs_access_masks[0] = fs_access_mask;
>> +		landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
>>   	return new_ruleset;
>>   }
>> 
>> @@ -117,11 +117,12 @@ static void build_check_ruleset(void)
>>   		.num_rules = ~0,
>>   		.num_layers = ~0,
>>   	};
>> -	typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0;
>> +	typeof(ruleset.access_masks[0]) access_masks = ~0;
>> 
>>   	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>>   	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>> -	BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS);
>> +	BUILD_BUG_ON(access_masks <
>> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>>   }
>> 
>>   /**
>> @@ -281,7 +282,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   		err = -EINVAL;
>>   		goto out_unlock;
>>   	}
>> -	dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
>> +	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
>> 
>>   	/* Merges the @src tree. */
>>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
>> @@ -340,8 +341,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>   		goto out_unlock;
>>   	}
>>   	/* Copies the parent layer stack and leaves a space for the new layer. */
>> -	memcpy(child->fs_access_masks, parent->fs_access_masks,
>> -	       flex_array_size(parent, fs_access_masks, parent->num_layers));
>> +	memcpy(child->access_masks, parent->access_masks,
>> +	       flex_array_size(parent, access_masks, parent->num_layers));
>> 
>>   	if (WARN_ON_ONCE(!parent->hierarchy)) {
>>   		err = -EINVAL;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index d43231b783e4..f2ad932d396c 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -25,6 +25,11 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>>   /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
>>   static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>> 
>> +/* Ruleset access masks. */
>> +typedef u16 access_masks_t;
>> +/* Makes sure all ruleset access rights can be stored. */
>> +static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
>> +
>>   typedef u16 layer_mask_t;
>>   /* Makes sure all layers can be checked. */
>>   static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>> @@ -110,7 +115,7 @@ struct landlock_ruleset {
>>   		 * section.  This is only used by
>>   		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>>   		 * The fields @lock, @usage, @num_rules, @num_layers and
>> -		 * @fs_access_masks are then unused.
>> +		 * @access_masks are then unused.
>>   		 */
>>   		struct work_struct work_free;
>>   		struct {
>> @@ -137,7 +142,7 @@ struct landlock_ruleset {
>>   			 */
>>   			u32 num_layers;
>>   			/**
>> -			 * @fs_access_masks: Contains the subset of filesystem
>> +			 * @access_masks: Contains the subset of filesystem
>>   			 * actions that are restricted by a ruleset.  A domain
>>   			 * saves all layers of merged rulesets in a stack
>>   			 * (FAM), starting from the first layer to the last
>> @@ -148,13 +153,13 @@ struct landlock_ruleset {
>>   			 * layers are set once and never changed for the
>>   			 * lifetime of the ruleset.
>>   			 */
>> -			access_mask_t fs_access_masks[];
>> +			access_masks_t access_masks[];
>>   		};
>>   	};
>>   };
>> 
>>   struct landlock_ruleset *
>> -landlock_create_ruleset(const access_mask_t fs_access_mask);
>> +landlock_create_ruleset(const access_mask_t access_mask);
>> 
>>   void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
>>   void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
>> @@ -177,4 +182,26 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>>   		refcount_inc(&ruleset->usage);
>>   }
>> 
>> +static inline void
>> +landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>> +			    const access_mask_t fs_access_mask,
>> +			    const u16 layer_level)
>> +{
>> +	access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
>> +
>> +	/* Should already be checked in sys_landlock_create_ruleset(). */
>> +	WARN_ON_ONCE(fs_access_mask != fs_mask);
>> +	// TODO: Add tests to check "|=" and not "="
> 
> This todo should be done and removed. No more todos must remain.
> 
   I delete it in 10/12 Patch when add network seltests.
> 
>> +	ruleset->access_masks[layer_level] |=
>> +		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
>> +}
>> +
>> +static inline access_mask_t
>> +landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>> +			    const u16 layer_level)
>> +{
>> +	return (ruleset->access_masks[layer_level] >>
>> +		LANDLOCK_SHIFT_ACCESS_FS) &
>> +	       LANDLOCK_MASK_ACCESS_FS;
>> +}
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index 245cc650a4dc..71aca7f990bc 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -346,10 +346,11 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>>   	}
>>   	/*
>>   	 * Checks that allowed_access matches the @ruleset constraints
>> -	 * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits).
>> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>>   	 */
>> -	if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) !=
>> -	    ruleset->fs_access_masks[0]) {
>> +	if ((path_beneath_attr.allowed_access |
>> +	     landlock_get_fs_access_mask(ruleset, 0)) !=
>> +	    landlock_get_fs_access_mask(ruleset, 0)) {
>>   		err = -EINVAL;
>>   		goto out_put_ruleset;
>>   	}
>> --
>> 2.25.1
>> 
> 
> I'll send a patch to be applied after this one.
> .

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

* Re: [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule
  2022-11-17 18:41   ` Mickaël Salaün
  2022-11-17 18:55     ` [PATCH] landlock: Allow filesystem layout changes for domains without such rule type Mickaël Salaün
  2022-11-22 17:17     ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Mickaël Salaün
@ 2022-11-28  2:58     ` Konstantin Meskhidze (A)
  2 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  2:58 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:41 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> Adds a new landlock_key union and landlock_id structure to support
> 
> Add a new…

   Ok. Thanks.
> 
>> a socket port rule type. A struct landlock_id identifies a unique entry
>> in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
>> port). There is one red-black tree per key type.
>> 
>> This patch also adds is_object_pointer() and get_root() helpers.
>> is_object_pointer() one checks whether key type is LANDLOCK_KEY_INODE.
> 
> is_object_pointer() returns true if the key type is LANDLOCK_KEY_INODE.
> 

  Got it.

>> get_root() helper chooses red_black tree root depending on a key type.
> 
> get_root() returns a red_black tree root pointer according to a key type.

  Ok. Thanks.

> 
>> 
>> Refactors landlock_insert_rule() and landlock_find_rule() to support coming
> 
> Refactor…

   Ok.
> 
>> network modifications. Now adding or searching a rule in a ruleset depends
> 
> … Adding or searching a rule in a ruleset can now be done

> 
>> on a landlock id argument provided in refactored functions.
> 
> thanks to a Landlock ID argument passed to these helpers.
> 

   I will rewrite it. Thanks.
>> 
>> Co-developed-by: Mickaël Salaün <mic@digikod.net>
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> 
> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Completes all the new field descriptions landlock_key,
>>    landlock_key_type, landlock_id.
>> * Refactors commit message, adds a co-developer.
>> 
>> Changes since v6:
>> * Adds union landlock_key, enum landlock_key_type, and struct
>>    landlock_id.
>> * Refactors ruleset functions and improves switch/cases: create_rule(),
>>    insert_rule(), get_root(), is_object_pointer(), free_rule(),
>>    landlock_find_rule().
>> * Refactors landlock_append_fs_rule() functions to support new
>>    landlock_id type.
>> 
>> Changes since v5:
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors insert_rule() and create_rule() functions by deleting
>> rule_type from their arguments list, it helps to reduce useless code.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Refactors landlock_insert_rule and landlock_find_rule functions.
>> * Rename new_ruleset->root_inode.
>> 
>> ---
>>   security/landlock/fs.c      |  21 ++++--
>>   security/landlock/ruleset.c | 146 +++++++++++++++++++++++++-----------
>>   security/landlock/ruleset.h |  64 +++++++++++++---
>>   3 files changed, 169 insertions(+), 62 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 0d57c6479d29..710cfa1306de 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -168,7 +168,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>   			    access_mask_t access_rights)
>>   {
>>   	int err;
>> -	struct landlock_object *object;
>> +	struct landlock_id id = {
>> +		.type = LANDLOCK_KEY_INODE,
>> +	};
>> 
>>   	/* Files only get access rights that make sense. */
>>   	if (!d_is_dir(path->dentry) &&
>> @@ -181,17 +183,17 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>   	access_rights |= LANDLOCK_MASK_ACCESS_FS &
>>   			 ~(landlock_get_fs_access_mask(ruleset, 0) |
>>   			   ACCESS_INITIALLY_DENIED);
>> -	object = get_inode_object(d_backing_inode(path->dentry));
>> -	if (IS_ERR(object))
>> -		return PTR_ERR(object);
>> +	id.key.object = get_inode_object(d_backing_inode(path->dentry));
>> +	if (IS_ERR(id.key.object))
>> +		return PTR_ERR(id.key.object);
>>   	mutex_lock(&ruleset->lock);
>> -	err = landlock_insert_rule(ruleset, object, access_rights);
>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>   	mutex_unlock(&ruleset->lock);
>>   	/*
>>   	 * No need to check for an error because landlock_insert_rule()
>>   	 * increments the refcount for the new object if needed.
>>   	 */
>> -	landlock_put_object(object);
>> +	landlock_put_object(id.key.object);
>>   	return err;
>>   }
>> 
>> @@ -208,6 +210,9 @@ find_rule(const struct landlock_ruleset *const domain,
>>   {
>>   	const struct landlock_rule *rule;
>>   	const struct inode *inode;
>> +	struct landlock_id id = {
>> +		.type = LANDLOCK_KEY_INODE,
>> +	};
>> 
>>   	/* Ignores nonexistent leafs. */
>>   	if (d_is_negative(dentry))
>> @@ -215,8 +220,8 @@ find_rule(const struct landlock_ruleset *const domain,
>> 
>>   	inode = d_backing_inode(dentry);
>>   	rcu_read_lock();
>> -	rule = landlock_find_rule(
>> -		domain, rcu_dereference(landlock_inode(inode)->object));
>> +	id.key.object = rcu_dereference(landlock_inode(inode)->object);
>> +	rule = landlock_find_rule(domain, id);
>>   	rcu_read_unlock();
>>   	return rule;
>>   }
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 1f3188b4e313..41de17d1869e 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -35,7 +35,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   		return ERR_PTR(-ENOMEM);
>>   	refcount_set(&new_ruleset->usage, 1);
>>   	mutex_init(&new_ruleset->lock);
>> -	new_ruleset->root = RB_ROOT;
>> +	new_ruleset->root_inode = RB_ROOT;
>>   	new_ruleset->num_layers = num_layers;
>>   	/*
>>   	 * hierarchy = NULL
>> @@ -68,8 +68,18 @@ static void build_check_rule(void)
>>   	BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>>   }
>> 
>> +static inline bool is_object_pointer(const enum landlock_key_type key_type)
> 
> Let's remove all "inline" keywords in all .c files. This should be
> simple for the compiler to inline them automatically, and it makes the
> code cleaner.

   Got it.
> 
> 
>> +{
>> +	switch (key_type) {
>> +	case LANDLOCK_KEY_INODE:
>> +		return true;
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return false;
>> +}
>> +
>>   static struct landlock_rule *
>> -create_rule(struct landlock_object *const object,
>> +create_rule(const struct landlock_id id,
>>   	    const struct landlock_layer (*const layers)[], const u32 num_layers,
>>   	    const struct landlock_layer *const new_layer)
>>   {
>> @@ -90,8 +100,13 @@ create_rule(struct landlock_object *const object,
>>   	if (!new_rule)
>>   		return ERR_PTR(-ENOMEM);
>>   	RB_CLEAR_NODE(&new_rule->node);
>> -	landlock_get_object(object);
>> -	new_rule->object = object;
>> +	if (is_object_pointer(id.type)) {
>> +		/* This should be catched by insert_rule(). */
>> +		WARN_ON_ONCE(!id.key.object);
>> +		landlock_get_object(id.key.object);
>> +	}
>> +
>> +	new_rule->key = id.key;
>>   	new_rule->num_layers = new_num_layers;
>>   	/* Copies the original layer stack. */
>>   	memcpy(new_rule->layers, layers,
>> @@ -102,12 +117,29 @@ create_rule(struct landlock_object *const object,
>>   	return new_rule;
>>   }
>> 
>> -static void free_rule(struct landlock_rule *const rule)
>> +static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
> 
> Please remove "inline".

   Got it. Thanks.
> 
>> +				       const enum landlock_key_type key_type)
>> +{
>> +	struct rb_root *root = NULL;
>> +
>> +	switch (key_type) {
>> +	case LANDLOCK_KEY_INODE:
>> +		root = &ruleset->root_inode;
>> +		break;
>> +	}
>> +	if (WARN_ON_ONCE(!root))
>> +		return ERR_PTR(-EINVAL);
>> +	return root;
>> +}
>> +
>> +static void free_rule(struct landlock_rule *const rule,
>> +		      const enum landlock_key_type key_type)
>>   {
>>   	might_sleep();
>>   	if (!rule)
>>   		return;
>> -	landlock_put_object(rule->object);
>> +	if (is_object_pointer(key_type))
>> +		landlock_put_object(rule->key.object);
>>   	kfree(rule);
>>   }
>> 
>> @@ -129,8 +161,8 @@ static void build_check_ruleset(void)
>>    * insert_rule - Create and insert a rule in a ruleset
>>    *
>>    * @ruleset: The ruleset to be updated.
>> - * @object: The object to build the new rule with.  The underlying kernel
>> - *          object must be held by the caller.
>> + * @id: The ID to build the new rule with.  The underlying kernel object, if
>> + *      any, must be held by the caller.
>>    * @layers: One or multiple layers to be copied into the new rule.
>>    * @num_layers: The number of @layers entries.
>>    *
>> @@ -144,26 +176,37 @@ static void build_check_ruleset(void)
>>    * access rights.
>>    */
>>   static int insert_rule(struct landlock_ruleset *const ruleset,
>> -		       struct landlock_object *const object,
>> +		       const struct landlock_id id,
>>   		       const struct landlock_layer (*const layers)[],
>> -		       size_t num_layers)
>> +		       const size_t num_layers)
>>   {
>>   	struct rb_node **walker_node;
>>   	struct rb_node *parent_node = NULL;
>>   	struct landlock_rule *new_rule;
>> +	struct rb_root *root;
>> 
>>   	might_sleep();
>>   	lockdep_assert_held(&ruleset->lock);
>> -	if (WARN_ON_ONCE(!object || !layers))
>> +	if (WARN_ON_ONCE(!layers))
>>   		return -ENOENT;
>> -	walker_node = &(ruleset->root.rb_node);
>> +
>> +	if (is_object_pointer(id.type)) {
>> +		if (WARN_ON_ONCE(!id.key.object))
>> +			return -ENOENT;
>> +	}
>> +
>> +	root = get_root(ruleset, id.type);
>> +	if (IS_ERR(root))
>> +		return PTR_ERR(root);
>> +
>> +	walker_node = &root->rb_node;
>>   	while (*walker_node) {
>>   		struct landlock_rule *const this =
>>   			rb_entry(*walker_node, struct landlock_rule, node);
>> 
>> -		if (this->object != object) {
>> +		if (this->key.data != id.key.data) {
>>   			parent_node = *walker_node;
>> -			if (this->object < object)
>> +			if (this->key.data < id.key.data)
>>   				walker_node = &((*walker_node)->rb_right);
>>   			else
>>   				walker_node = &((*walker_node)->rb_left);
>> @@ -195,24 +238,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>>   		 * Intersects access rights when it is a merge between a
>>   		 * ruleset and a domain.
>>   		 */
>> -		new_rule = create_rule(object, &this->layers, this->num_layers,
>> +		new_rule = create_rule(id, &this->layers, this->num_layers,
>>   				       &(*layers)[0]);
>>   		if (IS_ERR(new_rule))
>>   			return PTR_ERR(new_rule);
>> -		rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
>> -		free_rule(this);
>> +		rb_replace_node(&this->node, &new_rule->node, root);
>> +		free_rule(this, id.type);
>>   		return 0;
>>   	}
>> 
>> -	/* There is no match for @object. */
>> +	/* There is no match for @id. */
>>   	build_check_ruleset();
>>   	if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
>>   		return -E2BIG;
>> -	new_rule = create_rule(object, layers, num_layers, NULL);
>> +	new_rule = create_rule(id, layers, num_layers, NULL);
>>   	if (IS_ERR(new_rule))
>>   		return PTR_ERR(new_rule);
>>   	rb_link_node(&new_rule->node, parent_node, walker_node);
>> -	rb_insert_color(&new_rule->node, &ruleset->root);
>> +	rb_insert_color(&new_rule->node, root);
>>   	ruleset->num_rules++;
>>   	return 0;
>>   }
>> @@ -230,7 +273,7 @@ static void build_check_layer(void)
>> 
>>   /* @ruleset must be locked by the caller. */
>>   int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>> -			 struct landlock_object *const object,
>> +			 const struct landlock_id id,
>>   			 const access_mask_t access)
>>   {
>>   	struct landlock_layer layers[] = { {
>> @@ -240,7 +283,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>>   	} };
>> 
>>   	build_check_layer();
>> -	return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers));
>> +	return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
>>   }
>> 
>>   static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
>> @@ -263,6 +306,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   			 struct landlock_ruleset *const src)
>>   {
>>   	struct landlock_rule *walker_rule, *next_rule;
>> +	struct rb_root *src_root;
>>   	int err = 0;
>> 
>>   	might_sleep();
>> @@ -273,6 +317,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
>>   		return -EINVAL;
>> 
>> +	src_root = get_root(src, LANDLOCK_KEY_INODE);
>> +	if (IS_ERR(src_root))
>> +		return PTR_ERR(src_root);
>> +
>>   	/* Locks @dst first because we are its only owner. */
>>   	mutex_lock(&dst->lock);
>>   	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
>> @@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
>> 
>>   	/* Merges the @src tree. */
>> -	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
>> +	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
>>   					     node) {
>>   		struct landlock_layer layers[] = { {
>>   			.level = dst->num_layers,
>>   		} };
>> +		const struct landlock_id id = {
>> +			.key = walker_rule->key,
>> +			.type = LANDLOCK_KEY_INODE,
>> +		};
>> 
>> -		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
>> -			err = -EINVAL;
>> -			goto out_unlock;
>> -		}
>> -		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
>> -			err = -EINVAL;
>> -			goto out_unlock;
>> -		}
>> +		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
>> +			return -EINVAL;
>> +		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
>> +			return -EINVAL;
>>   		layers[0].access = walker_rule->layers[0].access;
>> -		err = insert_rule(dst, walker_rule->object, &layers,
>> -				  ARRAY_SIZE(layers));
>> +
>> +		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
>>   		if (err)
>>   			goto out_unlock;
>>   	}
>> @@ -316,21 +364,29 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>   			   struct landlock_ruleset *const child)
>>   {
>>   	struct landlock_rule *walker_rule, *next_rule;
>> +	struct rb_root *parent_root;
>>   	int err = 0;
>> 
>>   	might_sleep();
>>   	if (!parent)
>>   		return 0;
>> 
>> +	parent_root = get_root(parent, LANDLOCK_KEY_INODE);
>> +	if (IS_ERR(parent_root))
>> +		return PTR_ERR(parent_root);
>> +
>>   	/* Locks @child first because we are its only owner. */
>>   	mutex_lock(&child->lock);
>>   	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
>> 
>>   	/* Copies the @parent tree. */
>>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
>> -					     &parent->root, node) {
>> -		err = insert_rule(child, walker_rule->object,
>> -				  &walker_rule->layers,
>> +					     parent_root, node) {
>> +		const struct landlock_id id = {
>> +			.key = walker_rule->key,
>> +			.type = LANDLOCK_KEY_INODE,
>> +		};
>> +		err = insert_rule(child, id, &walker_rule->layers,
>>   				  walker_rule->num_layers);
>>   		if (err)
>>   			goto out_unlock;
>> @@ -362,8 +418,9 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
>>   	struct landlock_rule *freeme, *next;
>> 
>>   	might_sleep();
>> -	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node)
>> -		free_rule(freeme);
>> +	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
>> +					     node)
>> +		free_rule(freeme, LANDLOCK_KEY_INODE);
>>   	put_hierarchy(ruleset->hierarchy);
>>   	kfree(ruleset);
>>   }
>> @@ -454,20 +511,23 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
>>    */
>>   const struct landlock_rule *
>>   landlock_find_rule(const struct landlock_ruleset *const ruleset,
>> -		   const struct landlock_object *const object)
>> +		   const struct landlock_id id)
>>   {
>> +	const struct rb_root *root;
>>   	const struct rb_node *node;
>> 
>> -	if (!object)
>> +	root = get_root((struct landlock_ruleset *)ruleset, id.type);
>> +	if (IS_ERR(root))
>>   		return NULL;
>> -	node = ruleset->root.rb_node;
>> +	node = root->rb_node;
>> +
>>   	while (node) {
>>   		struct landlock_rule *this =
>>   			rb_entry(node, struct landlock_rule, node);
>> 
>> -		if (this->object == object)
>> +		if (this->key.data == id.key.data)
>>   			return this;
>> -		if (this->object < object)
>> +		if (this->key.data < id.key.data)
>>   			node = node->rb_right;
>>   		else
>>   			node = node->rb_left;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index f2ad932d396c..608ab356bc3e 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -49,6 +49,46 @@ struct landlock_layer {
>>   	access_mask_t access;
>>   };
>> 
>> +/**
>> + * union landlock_key - Key of a ruleset's red-black tree
>> + */
>> +union landlock_key {
>> +	/**
>> +	 * @object: Pointer to identify a kernel object (e.g. an inode).
>> +	 */
>> +	struct landlock_object *object;
>> +	/**
>> +	 * @data: A raw data value to identify a network socket port.
> 
> "Raw data to identify an arbitrary 32-bit value (e.g. a TCP port)."
> 
   Ok.
> 
>> +	 */
>> +	uintptr_t data;
>> +};
>> +
>> +/**
>> + * enum landlock_key_type - Type of &union landlock_key
>> + */
>> +enum landlock_key_type {
>> +	/**
>> +	 * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
>> +	 * keys.
>> +	 */
>> +	LANDLOCK_KEY_INODE = 1,
>> +};
>> +
>> +/**
>> + * struct landlock_id - Unique rule identifier for a ruleset
>> + */
>> +struct landlock_id {
>> +	/**
>> +	 * @key: A union to identify either a kernel object (e.g. an inode) or
>> +	 * a raw data value (e.g. a network socket port).
> 
> "a 32-bit value (e.g. a TCP port)."

   Ok. Got it.
> 
> 
>> +	 */
>> +	union landlock_key key;
>> +	/**
>> +	 * @type: A enumerator to identify the type of landlock_ruleset's root tree.
>> +	 */
>> +	const enum landlock_key_type type;
>> +};
>> +
>>   /**
>>    * struct landlock_rule - Access rights tied to an object
>>    */
>> @@ -58,12 +98,13 @@ struct landlock_rule {
>>   	 */
>>   	struct rb_node node;
>>   	/**
>> -	 * @object: Pointer to identify a kernel object (e.g. an inode).  This
>> -	 * is used as a key for this ruleset element.  This pointer is set once
>> -	 * and never modified.  It always points to an allocated object because
>> -	 * each rule increments the refcount of its object.
>> +	 * @key: A union to identify either a kernel object (e.g. an inode) or
>> +	 * a raw data value (e.g. a network socket port). This is used as a key
>> +	 * for this ruleset element.  The pointer is set once and never
>> +	 * modified.  It always points to an allocated object because each rule
>> +	 * increments the refcount of its object.
>>   	 */
>> -	struct landlock_object *object;
>> +	union landlock_key key;
>>   	/**
>>   	 * @num_layers: Number of entries in @layers.
>>   	 */
>> @@ -99,11 +140,12 @@ struct landlock_hierarchy {
>>    */
>>   struct landlock_ruleset {
>>   	/**
>> -	 * @root: Root of a red-black tree containing &struct landlock_rule
>> -	 * nodes.  Once a ruleset is tied to a process (i.e. as a domain), this
>> -	 * tree is immutable until @usage reaches zero.
>> +	 * @root_inode: Root of a red-black tree containing &struct
>> +	 * landlock_rule nodes with inode object.  Once a ruleset is tied to a
>> +	 * process (i.e. as a domain), this tree is immutable until @usage
>> +	 * reaches zero.
>>   	 */
>> -	struct rb_root root;
>> +	struct rb_root root_inode;
>>   	/**
>>   	 * @hierarchy: Enables hierarchy identification even when a parent
>>   	 * domain vanishes.  This is needed for the ptrace protection.
>> @@ -165,7 +207,7 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
>>   void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
>> 
>>   int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>> -			 struct landlock_object *const object,
>> +			 const struct landlock_id id,
>>   			 const access_mask_t access);
>> 
>>   struct landlock_ruleset *
>> @@ -174,7 +216,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
>> 
>>   const struct landlock_rule *
>>   landlock_find_rule(const struct landlock_ruleset *const ruleset,
>> -		   const struct landlock_object *const object);
>> +		   const struct landlock_id id);
>> 
>>   static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>>   {
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-17 18:55     ` [PATCH] landlock: Allow filesystem layout changes for domains without such rule type Mickaël Salaün
  2022-11-18  9:16       ` Mickaël Salaün
@ 2022-11-28  3:02       ` Konstantin Meskhidze (A)
  2022-11-28 20:25         ` Mickaël Salaün
  1 sibling, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:02 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



11/17/2022 9:55 PM, Mickaël Salaün пишет:
> Allow mount point and root directory changes when there is no filesystem
> rule tied to the current Landlock domain.  This doesn't change anything
> for now because a domain must have at least a (filesystem) rule, but
> this will change when other rule types will come.  For instance, a
> domain only restricting the network should have no impact on filesystem
> restrictions.
> 
> Add a new get_current_fs_domain() helper to quickly check filesystem
> rule existence for all filesystem LSM hooks.

   Ok. I got it.
   Do I need also to add a new network helper:
   like landlock_get_raw_net_access_mask?
> 
> Remove unnecessary inlining.

   Ok.
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
>   security/landlock/fs.c       | 73 ++++++++++++++++++------------------
>   security/landlock/ruleset.h  | 25 +++++++++++-
>   security/landlock/syscalls.c |  6 +--
>   3 files changed, 62 insertions(+), 42 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 0d57c6479d29..0ae54a639e16 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -150,16 +150,6 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
>   	LANDLOCK_ACCESS_FS_TRUNCATE)
>   /* clang-format on */
>   
> -/*
> - * All access rights that are denied by default whether they are handled or not
> - * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
> - * entries when we need to get the absolute handled access masks.
> - */
> -/* clang-format off */
> -#define ACCESS_INITIALLY_DENIED ( \
> -	LANDLOCK_ACCESS_FS_REFER)
> -/* clang-format on */
> -
>   /*
>    * @path: Should have been checked by get_path_from_fd().
>    */
> @@ -179,8 +169,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>   
>   	/* Transforms relative access rights to absolute ones. */
>   	access_rights |= LANDLOCK_MASK_ACCESS_FS &
> -			 ~(landlock_get_fs_access_mask(ruleset, 0) |
> -			   ACCESS_INITIALLY_DENIED);
> +			 ~landlock_get_fs_access_mask(ruleset, 0);
>   	object = get_inode_object(d_backing_inode(path->dentry));
>   	if (IS_ERR(object))
>   		return PTR_ERR(object);
> @@ -287,14 +276,15 @@ static inline bool is_nouser_or_private(const struct dentry *dentry)
>   		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
>   }
>   
> -static inline access_mask_t
> -get_handled_accesses(const struct landlock_ruleset *const domain)
> +static access_mask_t
> +get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
>   {
> -	access_mask_t access_dom = ACCESS_INITIALLY_DENIED;
> +	access_mask_t access_dom = 0;
>   	size_t layer_level;
>   
>   	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> -		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
> +		access_dom |=
> +			landlock_get_raw_fs_access_mask(domain, layer_level);
>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>   }
>   
> @@ -331,13 +321,8 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>   
>   		for_each_set_bit(access_bit, &access_req,
>   				 ARRAY_SIZE(*layer_masks)) {
> -			/*
> -			 * Artificially handles all initially denied by default
> -			 * access rights.
> -			 */
>   			if (BIT_ULL(access_bit) &
> -			    (landlock_get_fs_access_mask(domain, layer_level) |
> -			     ACCESS_INITIALLY_DENIED)) {
> +			    landlock_get_fs_access_mask(domain, layer_level)) {
>   				(*layer_masks)[access_bit] |=
>   					BIT_ULL(layer_level);
>   				handled_accesses |= BIT_ULL(access_bit);
> @@ -347,6 +332,24 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>   	return handled_accesses;
>   }
>   
> +static access_mask_t
> +get_handled_fs_accesses(const struct landlock_ruleset *const domain)
> +{
> +	/* Handles all initially denied by default access rights. */
> +	return get_raw_handled_fs_accesses(domain) | ACCESS_FS_INITIALLY_DENIED;
> +}
> +
> +static const struct landlock_ruleset *get_current_fs_domain(void)
> +{
> +	const struct landlock_ruleset *const dom =
> +		landlock_get_current_domain();
> +
> +	if (!dom || !get_raw_handled_fs_accesses(dom))
> +		return NULL;
> +
> +	return dom;
> +}
> +
>   /*
>    * Check that a destination file hierarchy has more restrictions than a source
>    * file hierarchy.  This is only used for link and rename actions.
> @@ -519,7 +522,7 @@ static bool is_access_to_paths_allowed(
>   		 * a superset of the meaningful requested accesses).
>   		 */
>   		access_masked_parent1 = access_masked_parent2 =
> -			get_handled_accesses(domain);
> +			get_handled_fs_accesses(domain);
>   		is_dom_check = true;
>   	} else {
>   		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
> @@ -648,11 +651,10 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>   	return -EACCES;
>   }
>   
> -static inline int current_check_access_path(const struct path *const path,
> +static int current_check_access_path(const struct path *const path,
>   					    const access_mask_t access_request)
>   {
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   
>   	if (!dom)
>   		return 0;
> @@ -815,8 +817,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>   				    struct dentry *const new_dentry,
>   				    const bool removable, const bool exchange)
>   {
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   	bool allow_parent1, allow_parent2;
>   	access_mask_t access_request_parent1, access_request_parent2;
>   	struct path mnt_dir;
> @@ -1050,7 +1051,7 @@ static int hook_sb_mount(const char *const dev_name,
>   			 const struct path *const path, const char *const type,
>   			 const unsigned long flags, void *const data)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1058,7 +1059,7 @@ static int hook_sb_mount(const char *const dev_name,
>   static int hook_move_mount(const struct path *const from_path,
>   			   const struct path *const to_path)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1069,14 +1070,14 @@ static int hook_move_mount(const struct path *const from_path,
>    */
>   static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
>   
>   static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1092,7 +1093,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
>   static int hook_sb_pivotroot(const struct path *const old_path,
>   			     const struct path *const new_path)
>   {
> -	if (!landlock_get_current_domain())
> +	if (!get_current_fs_domain())
>   		return 0;
>   	return -EPERM;
>   }
> @@ -1128,8 +1129,7 @@ static int hook_path_mknod(const struct path *const dir,
>   			   struct dentry *const dentry, const umode_t mode,
>   			   const unsigned int dev)
>   {
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   
>   	if (!dom)
>   		return 0;
> @@ -1208,8 +1208,7 @@ static int hook_file_open(struct file *const file)
>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>   	access_mask_t open_access_request, full_access_request, allowed_access;
>   	const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
> -	const struct landlock_ruleset *const dom =
> -		landlock_get_current_domain();
> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>   
>   	if (!dom)
>   		return 0;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index f2ad932d396c..ca46393ef3bb 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -15,10 +15,21 @@
>   #include <linux/rbtree.h>
>   #include <linux/refcount.h>
>   #include <linux/workqueue.h>
> +#include <uapi/linux/landlock.h>
>   
>   #include "limits.h"
>   #include "object.h"
>   
> +/*
> + * All access rights that are denied by default whether they are handled or not
> + * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
> + * entries when we need to get the absolute handled access masks.
> + */
> +/* clang-format off */
> +#define ACCESS_FS_INITIALLY_DENIED ( \
> +	LANDLOCK_ACCESS_FS_REFER)
> +/* clang-format on */
> +
>   typedef u16 access_mask_t;
>   /* Makes sure all filesystem access rights can be stored. */
>   static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> @@ -197,11 +208,21 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>   }
>   
>   static inline access_mask_t
> -landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> -			    const u16 layer_level)
> +landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
> +				const u16 layer_level)
>   {
>   	return (ruleset->access_masks[layer_level] >>
>   		LANDLOCK_SHIFT_ACCESS_FS) &
>   	       LANDLOCK_MASK_ACCESS_FS;
>   }
> +
> +static inline access_mask_t
> +landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> +			    const u16 layer_level)
> +{
> +	/* Handles all initially denied by default access rights. */
> +	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
> +	       ACCESS_FS_INITIALLY_DENIED;
> +}
> +
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 71aca7f990bc..d35cd5d304db 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -310,6 +310,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	struct path path;
>   	struct landlock_ruleset *ruleset;
>   	int res, err;
> +	access_mask_t mask;
>   
>   	if (!landlock_initialized)
>   		return -EOPNOTSUPP;
> @@ -348,9 +349,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	 * Checks that allowed_access matches the @ruleset constraints
>   	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>   	 */
> -	if ((path_beneath_attr.allowed_access |
> -	     landlock_get_fs_access_mask(ruleset, 0)) !=
> -	    landlock_get_fs_access_mask(ruleset, 0)) {
> +	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
> +	if ((path_beneath_attr.allowed_access | mask) != mask) {
>   		err = -EINVAL;
>   		goto out_put_ruleset;
>   	}

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-18  9:16       ` Mickaël Salaün
@ 2022-11-28  3:04         ` Konstantin Meskhidze (A)
  2022-11-28 20:23           ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:04 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



11/18/2022 12:16 PM, Mickaël Salaün пишет:
> Konstantin, this patch should apply cleanly just after "01/12 landlock:
> Make ruleset's access masks more generic". You can easily get this patch
> with https://git.kernel.org/pub/scm/utils/b4/b4.git/
> Some adjustments are needed for the following patches. Feel free to
> review this patch.
>   Do you have this patch online? Can I fetch it from your repo?

> 
> On 17/11/2022 19:55, Mickaël Salaün wrote:
>> Allow mount point and root directory changes when there is no filesystem
>> rule tied to the current Landlock domain.  This doesn't change anything
>> for now because a domain must have at least a (filesystem) rule, but
>> this will change when other rule types will come.  For instance, a
>> domain only restricting the network should have no impact on filesystem
>> restrictions.
>> 
>> Add a new get_current_fs_domain() helper to quickly check filesystem
>> rule existence for all filesystem LSM hooks.
>> 
>> Remove unnecessary inlining.
>> 
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>> ---
>>   security/landlock/fs.c       | 73 ++++++++++++++++++------------------
>>   security/landlock/ruleset.h  | 25 +++++++++++-
>>   security/landlock/syscalls.c |  6 +--
>>   3 files changed, 62 insertions(+), 42 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 0d57c6479d29..0ae54a639e16 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -150,16 +150,6 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
>>   	LANDLOCK_ACCESS_FS_TRUNCATE)
>>   /* clang-format on */
>>   
>> -/*
>> - * All access rights that are denied by default whether they are handled or not
>> - * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
>> - * entries when we need to get the absolute handled access masks.
>> - */
>> -/* clang-format off */
>> -#define ACCESS_INITIALLY_DENIED ( \
>> -	LANDLOCK_ACCESS_FS_REFER)
>> -/* clang-format on */
>> -
>>   /*
>>    * @path: Should have been checked by get_path_from_fd().
>>    */
>> @@ -179,8 +169,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>   
>>   	/* Transforms relative access rights to absolute ones. */
>>   	access_rights |= LANDLOCK_MASK_ACCESS_FS &
>> -			 ~(landlock_get_fs_access_mask(ruleset, 0) |
>> -			   ACCESS_INITIALLY_DENIED);
>> +			 ~landlock_get_fs_access_mask(ruleset, 0);
>>   	object = get_inode_object(d_backing_inode(path->dentry));
>>   	if (IS_ERR(object))
>>   		return PTR_ERR(object);
>> @@ -287,14 +276,15 @@ static inline bool is_nouser_or_private(const struct dentry *dentry)
>>   		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
>>   }
>>   
>> -static inline access_mask_t
>> -get_handled_accesses(const struct landlock_ruleset *const domain)
>> +static access_mask_t
>> +get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
>>   {
>> -	access_mask_t access_dom = ACCESS_INITIALLY_DENIED;
>> +	access_mask_t access_dom = 0;
>>   	size_t layer_level;
>>   
>>   	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
>> -		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
>> +		access_dom |=
>> +			landlock_get_raw_fs_access_mask(domain, layer_level);
>>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>>   }
>>   
>> @@ -331,13 +321,8 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>>   
>>   		for_each_set_bit(access_bit, &access_req,
>>   				 ARRAY_SIZE(*layer_masks)) {
>> -			/*
>> -			 * Artificially handles all initially denied by default
>> -			 * access rights.
>> -			 */
>>   			if (BIT_ULL(access_bit) &
>> -			    (landlock_get_fs_access_mask(domain, layer_level) |
>> -			     ACCESS_INITIALLY_DENIED)) {
>> +			    landlock_get_fs_access_mask(domain, layer_level)) {
>>   				(*layer_masks)[access_bit] |=
>>   					BIT_ULL(layer_level);
>>   				handled_accesses |= BIT_ULL(access_bit);
>> @@ -347,6 +332,24 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>>   	return handled_accesses;
>>   }
>>   
>> +static access_mask_t
>> +get_handled_fs_accesses(const struct landlock_ruleset *const domain)
>> +{
>> +	/* Handles all initially denied by default access rights. */
>> +	return get_raw_handled_fs_accesses(domain) | ACCESS_FS_INITIALLY_DENIED;
>> +}
>> +
>> +static const struct landlock_ruleset *get_current_fs_domain(void)
>> +{
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
>> +
>> +	if (!dom || !get_raw_handled_fs_accesses(dom))
>> +		return NULL;
>> +
>> +	return dom;
>> +}
>> +
>>   /*
>>    * Check that a destination file hierarchy has more restrictions than a source
>>    * file hierarchy.  This is only used for link and rename actions.
>> @@ -519,7 +522,7 @@ static bool is_access_to_paths_allowed(
>>   		 * a superset of the meaningful requested accesses).
>>   		 */
>>   		access_masked_parent1 = access_masked_parent2 =
>> -			get_handled_accesses(domain);
>> +			get_handled_fs_accesses(domain);
>>   		is_dom_check = true;
>>   	} else {
>>   		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
>> @@ -648,11 +651,10 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>>   	return -EACCES;
>>   }
>>   
>> -static inline int current_check_access_path(const struct path *const path,
>> +static int current_check_access_path(const struct path *const path,
>>   					    const access_mask_t access_request)
>>   {
>> -	const struct landlock_ruleset *const dom =
>> -		landlock_get_current_domain();
>> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>>   
>>   	if (!dom)
>>   		return 0;
>> @@ -815,8 +817,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>>   				    struct dentry *const new_dentry,
>>   				    const bool removable, const bool exchange)
>>   {
>> -	const struct landlock_ruleset *const dom =
>> -		landlock_get_current_domain();
>> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>>   	bool allow_parent1, allow_parent2;
>>   	access_mask_t access_request_parent1, access_request_parent2;
>>   	struct path mnt_dir;
>> @@ -1050,7 +1051,7 @@ static int hook_sb_mount(const char *const dev_name,
>>   			 const struct path *const path, const char *const type,
>>   			 const unsigned long flags, void *const data)
>>   {
>> -	if (!landlock_get_current_domain())
>> +	if (!get_current_fs_domain())
>>   		return 0;
>>   	return -EPERM;
>>   }
>> @@ -1058,7 +1059,7 @@ static int hook_sb_mount(const char *const dev_name,
>>   static int hook_move_mount(const struct path *const from_path,
>>   			   const struct path *const to_path)
>>   {
>> -	if (!landlock_get_current_domain())
>> +	if (!get_current_fs_domain())
>>   		return 0;
>>   	return -EPERM;
>>   }
>> @@ -1069,14 +1070,14 @@ static int hook_move_mount(const struct path *const from_path,
>>    */
>>   static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
>>   {
>> -	if (!landlock_get_current_domain())
>> +	if (!get_current_fs_domain())
>>   		return 0;
>>   	return -EPERM;
>>   }
>>   
>>   static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
>>   {
>> -	if (!landlock_get_current_domain())
>> +	if (!get_current_fs_domain())
>>   		return 0;
>>   	return -EPERM;
>>   }
>> @@ -1092,7 +1093,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
>>   static int hook_sb_pivotroot(const struct path *const old_path,
>>   			     const struct path *const new_path)
>>   {
>> -	if (!landlock_get_current_domain())
>> +	if (!get_current_fs_domain())
>>   		return 0;
>>   	return -EPERM;
>>   }
>> @@ -1128,8 +1129,7 @@ static int hook_path_mknod(const struct path *const dir,
>>   			   struct dentry *const dentry, const umode_t mode,
>>   			   const unsigned int dev)
>>   {
>> -	const struct landlock_ruleset *const dom =
>> -		landlock_get_current_domain();
>> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>>   
>>   	if (!dom)
>>   		return 0;
>> @@ -1208,8 +1208,7 @@ static int hook_file_open(struct file *const file)
>>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>>   	access_mask_t open_access_request, full_access_request, allowed_access;
>>   	const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
>> -	const struct landlock_ruleset *const dom =
>> -		landlock_get_current_domain();
>> +	const struct landlock_ruleset *const dom = get_current_fs_domain();
>>   
>>   	if (!dom)
>>   		return 0;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index f2ad932d396c..ca46393ef3bb 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -15,10 +15,21 @@
>>   #include <linux/rbtree.h>
>>   #include <linux/refcount.h>
>>   #include <linux/workqueue.h>
>> +#include <uapi/linux/landlock.h>
>>   
>>   #include "limits.h"
>>   #include "object.h"
>>   
>> +/*
>> + * All access rights that are denied by default whether they are handled or not
>> + * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
>> + * entries when we need to get the absolute handled access masks.
>> + */
>> +/* clang-format off */
>> +#define ACCESS_FS_INITIALLY_DENIED ( \
>> +	LANDLOCK_ACCESS_FS_REFER)
>> +/* clang-format on */
>> +
>>   typedef u16 access_mask_t;
>>   /* Makes sure all filesystem access rights can be stored. */
>>   static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>> @@ -197,11 +208,21 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>>   }
>>   
>>   static inline access_mask_t
>> -landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>> -			    const u16 layer_level)
>> +landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
>> +				const u16 layer_level)
>>   {
>>   	return (ruleset->access_masks[layer_level] >>
>>   		LANDLOCK_SHIFT_ACCESS_FS) &
>>   	       LANDLOCK_MASK_ACCESS_FS;
>>   }
>> +
>> +static inline access_mask_t
>> +landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>> +			    const u16 layer_level)
>> +{
>> +	/* Handles all initially denied by default access rights. */
>> +	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
>> +	       ACCESS_FS_INITIALLY_DENIED;
>> +}
>> +
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index 71aca7f990bc..d35cd5d304db 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -310,6 +310,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>>   	struct path path;
>>   	struct landlock_ruleset *ruleset;
>>   	int res, err;
>> +	access_mask_t mask;
>>   
>>   	if (!landlock_initialized)
>>   		return -EOPNOTSUPP;
>> @@ -348,9 +349,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>>   	 * Checks that allowed_access matches the @ruleset constraints
>>   	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>>   	 */
>> -	if ((path_beneath_attr.allowed_access |
>> -	     landlock_get_fs_access_mask(ruleset, 0)) !=
>> -	    landlock_get_fs_access_mask(ruleset, 0)) {
>> +	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
>> +	if ((path_beneath_attr.allowed_access | mask) != mask) {
>>   		err = -EINVAL;
>>   		goto out_put_ruleset;
>>   	}
> .

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

* Re: [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule
  2022-11-22 17:17     ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Mickaël Salaün
@ 2022-11-28  3:06       ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:06 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/22/2022 8:17 PM, Mickaël Salaün пишет:
> 
> On 17/11/2022 19:41, Mickaël Salaün wrote:
>> 
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> 
> [...]
> 
>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>> index f2ad932d396c..608ab356bc3e 100644
>>> --- a/security/landlock/ruleset.h
>>> +++ b/security/landlock/ruleset.h
>>> @@ -49,6 +49,46 @@ struct landlock_layer {
>>>    	access_mask_t access;
>>>    };
>>>
>>> +/**
>>> + * union landlock_key - Key of a ruleset's red-black tree
>>> + */
>>> +union landlock_key {
>>> +	/**
>>> +	 * @object: Pointer to identify a kernel object (e.g. an inode).
>>> +	 */
>>> +	struct landlock_object *object;
>>> +	/**
>>> +	 * @data: A raw data value to identify a network socket port.
>> 
>> "Raw data to identify an arbitrary 32-bit value (e.g. a TCP port)."
>> 
>> 
>>> +	 */
>>> +	uintptr_t data;
>>> +};
>>> +
>>> +/**
>>> + * enum landlock_key_type - Type of &union landlock_key
>>> + */
>>> +enum landlock_key_type {
>>> +	/**
>>> +	 * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
>>> +	 * keys.
>>> +	 */
>>> +	LANDLOCK_KEY_INODE = 1,
>>> +};
>>> +
>>> +/**
>>> + * struct landlock_id - Unique rule identifier for a ruleset
>>> + */
>>> +struct landlock_id {
>>> +	/**
>>> +	 * @key: A union to identify either a kernel object (e.g. an inode) or
>>> +	 * a raw data value (e.g. a network socket port).
>> 
>> "a 32-bit value (e.g. a TCP port)."
> 
> Instead:
> @key: Identifies either a kernel object (e.g. an inode) or a raw value
> (e.g. a TCP port).

   Ok. Got it.
> 
>> 
>> 
>>> +	 */
>>> +	union landlock_key key;
>>> +	/**
>>> +	 * @type: A enumerator to identify the type of landlock_ruleset's root tree.
> 
> @type: Type of a landlock_ruleset's root tree.
> 
   Ok. Thanks.
> 
>>> +	 */
>>> +	const enum landlock_key_type type;
>>> +};
> .

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

* Re: [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions
  2022-11-17 18:41   ` Mickaël Salaün
@ 2022-11-28  3:07     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:07 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:41 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> Refactors merge_ruleset() and inherit_ruleset() functions to support
> 
> Refactor…

   Ok. Thanks.
> 
>> new rule types. This patch adds merge_tree() and inherit_tree()
>> helpers.
> 
>> Each has key_type argument to choose a particular rb_tree
> 
> They use a specific ruleset's red-black tree according to a key type
> argument.

   Got it.
> 
>> structure in a ruleset.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Adds missed lockdep_assert_held it inherit_tree() and merge_tree().
>> * Fixes comment.
>> 
>> Changes since v6:
>> * Refactors merge_ruleset() and inherit_ruleset() functions to support
>>    new rule types.
>> * Renames tree_merge() to merge_tree() (and reorder arguments), and
>>    tree_copy() to inherit_tree().
>> 
>> Changes since v5:
>> * Refactors some logic errors.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * None
>> 
>> ---
>>   security/landlock/ruleset.c | 108 ++++++++++++++++++++++++------------
>>   1 file changed, 73 insertions(+), 35 deletions(-)
>> 
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 41de17d1869e..961ffe0c709e 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -302,36 +302,22 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy)
>>   	}
>>   }
>> 
>> -static int merge_ruleset(struct landlock_ruleset *const dst,
>> -			 struct landlock_ruleset *const src)
>> +static int merge_tree(struct landlock_ruleset *const dst,
>> +		      struct landlock_ruleset *const src,
>> +		      const enum landlock_key_type key_type)
>>   {
>>   	struct landlock_rule *walker_rule, *next_rule;
>>   	struct rb_root *src_root;
>>   	int err = 0;
>> 
>>   	might_sleep();
>> -	/* Should already be checked by landlock_merge_ruleset() */
>> -	if (WARN_ON_ONCE(!src))
>> -		return 0;
>> -	/* Only merge into a domain. */
>> -	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
>> -		return -EINVAL;
>> +	lockdep_assert_held(&dst->lock);
>> +	lockdep_assert_held(&src->lock);
>> 
>> -	src_root = get_root(src, LANDLOCK_KEY_INODE);
>> +	src_root = get_root(src, key_type);
>>   	if (IS_ERR(src_root))
>>   		return PTR_ERR(src_root);
>> 
>> -	/* Locks @dst first because we are its only owner. */
>> -	mutex_lock(&dst->lock);
>> -	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
>> -
>> -	/* Stacks the new layer. */
>> -	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
>> -		err = -EINVAL;
>> -		goto out_unlock;
>> -	}
>> -	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
>> -
>>   	/* Merges the @src tree. */
>>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
>>   					     node) {
>> @@ -340,7 +326,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   		} };
>>   		const struct landlock_id id = {
>>   			.key = walker_rule->key,
>> -			.type = LANDLOCK_KEY_INODE,
>> +			.type = key_type,
>>   		};
>> 
>>   		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
>> @@ -351,8 +337,39 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>> 
>>   		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
>>   		if (err)
>> -			goto out_unlock;
>> +			return err;
>> +	}
>> +	return err;
>> +}
>> +
>> +static int merge_ruleset(struct landlock_ruleset *const dst,
>> +			 struct landlock_ruleset *const src)
>> +{
>> +	int err = 0;
>> +
>> +	might_sleep();
>> +	/* Should already be checked by landlock_merge_ruleset() */
>> +	if (WARN_ON_ONCE(!src))
>> +		return 0;
>> +	/* Only merge into a domain. */
>> +	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
>> +		return -EINVAL;
>> +
>> +	/* Locks @dst first because we are its only owner. */
>> +	mutex_lock(&dst->lock);
>> +	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
>> +
>> +	/* Stacks the new layer. */
>> +	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
>> +		err = -EINVAL;
>> +		goto out_unlock;
>>   	}
>> +	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
>> +
>> +	/* Merges the @src inode tree. */
>> +	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
>> +	if (err)
>> +		goto out_unlock;
>> 
>>   out_unlock:
>>   	mutex_unlock(&src->lock);
>> @@ -360,43 +377,64 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   	return err;
>>   }
>> 
>> -static int inherit_ruleset(struct landlock_ruleset *const parent,
>> -			   struct landlock_ruleset *const child)
>> +static int inherit_tree(struct landlock_ruleset *const parent,
>> +			struct landlock_ruleset *const child,
>> +			const enum landlock_key_type key_type)
>>   {
>>   	struct landlock_rule *walker_rule, *next_rule;
>>   	struct rb_root *parent_root;
>>   	int err = 0;
>> 
>>   	might_sleep();
>> -	if (!parent)
>> -		return 0;
>> +	lockdep_assert_held(&parent->lock);
>> +	lockdep_assert_held(&parent->lock);
> 
> lockdep_assert_held(&child->lock);

   My mistake. Thanks.
> .

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

* Re: [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks()
  2022-11-17 18:42   ` Mickaël Salaün
@ 2022-11-28  3:25     ` Konstantin Meskhidze (A)
  2022-11-28 20:25       ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:25 UTC (permalink / raw)
  To: Mickaël Salaün, Andy Whitcroft, Joe Perches,
	Dwaipayan Ray, Lukas Bulwahn
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:42 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This patch moves unmask_layers() and init_layer_masks() helpers
>> to ruleset.c to share with landlock network implementation in
> 
> …to share them with the Landlock network implementation in
> 
    Got it.
> 
>> following commits.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Refactors commit message.
>> 
>> Changes since v6:
>> * Moves get_handled_accesses() helper from ruleset.c back to fs.c,
>>    cause it's not used in coming network commits.
>> 
>> Changes since v5:
>> * Splits commit.
>> * Moves init_layer_masks() and get_handled_accesses() helpers
>> to ruleset.c and makes then non-static.
>> * Formats code with clang-format-14.
>> 
>> ---
>>   security/landlock/fs.c      | 103 ------------------------------------
>>   security/landlock/ruleset.c | 102 +++++++++++++++++++++++++++++++++++
>>   security/landlock/ruleset.h |  20 +++++++
>>   3 files changed, 122 insertions(+), 103 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 710cfa1306de..240e42a8f788 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -226,60 +226,6 @@ find_rule(const struct landlock_ruleset *const domain,
>>   	return rule;
>>   }
>> 
>> -/*
>> - * @layer_masks is read and may be updated according to the access request and
>> - * the matching rule.
>> - *
>> - * Returns true if the request is allowed (i.e. relevant layer masks for the
>> - * request are empty).
>> - */
>> -static inline bool
>> -unmask_layers(const struct landlock_rule *const rule,
>> -	      const access_mask_t access_request,
>> -	      layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> -{
>> -	size_t layer_level;
>> -
>> -	if (!access_request || !layer_masks)
>> -		return true;
>> -	if (!rule)
>> -		return false;
>> -
>> -	/*
>> -	 * An access is granted if, for each policy layer, at least one rule
>> -	 * encountered on the pathwalk grants the requested access,
>> -	 * regardless of its position in the layer stack.  We must then check
>> -	 * the remaining layers for each inode, from the first added layer to
>> -	 * the last one.  When there is multiple requested accesses, for each
>> -	 * policy layer, the full set of requested accesses may not be granted
>> -	 * by only one rule, but by the union (binary OR) of multiple rules.
>> -	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
>> -	 */
>> -	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
>> -		const struct landlock_layer *const layer =
>> -			&rule->layers[layer_level];
>> -		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
>> -		const unsigned long access_req = access_request;
>> -		unsigned long access_bit;
>> -		bool is_empty;
>> -
>> -		/*
>> -		 * Records in @layer_masks which layer grants access to each
>> -		 * requested access.
>> -		 */
>> -		is_empty = true;
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> -			if (layer->access & BIT_ULL(access_bit))
>> -				(*layer_masks)[access_bit] &= ~layer_bit;
>> -			is_empty = is_empty && !(*layer_masks)[access_bit];
>> -		}
>> -		if (is_empty)
>> -			return true;
>> -	}
>> -	return false;
>> -}
>> -
>>   /*
>>    * Allows access to pseudo filesystems that will never be mountable (e.g.
>>    * sockfs, pipefs), but can still be reachable through
>> @@ -303,55 +249,6 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
>>   	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>>   }
>> 
>> -/**
>> - * init_layer_masks - Initialize layer masks from an access request
>> - *
>> - * Populates @layer_masks such that for each access right in @access_request,
>> - * the bits for all the layers are set where this access right is handled.
>> - *
>> - * @domain: The domain that defines the current restrictions.
>> - * @access_request: The requested access rights to check.
>> - * @layer_masks: The layer masks to populate.
>> - *
>> - * Returns: An access mask where each access right bit is set which is handled
>> - * in any of the active layers in @domain.
>> - */
>> -static inline access_mask_t
>> -init_layer_masks(const struct landlock_ruleset *const domain,
>> -		 const access_mask_t access_request,
>> -		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> -{
>> -	access_mask_t handled_accesses = 0;
>> -	size_t layer_level;
>> -
>> -	memset(layer_masks, 0, sizeof(*layer_masks));
>> -	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
>> -	if (!access_request)
>> -		return 0;
>> -
>> -	/* Saves all handled accesses per layer. */
>> -	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
>> -		const unsigned long access_req = access_request;
>> -		unsigned long access_bit;
>> -
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> -			/*
>> -			 * Artificially handles all initially denied by default
>> -			 * access rights.
>> -			 */
>> -			if (BIT_ULL(access_bit) &
>> -			    (landlock_get_fs_access_mask(domain, layer_level) |
>> -			     ACCESS_INITIALLY_DENIED)) {
>> -				(*layer_masks)[access_bit] |=
>> -					BIT_ULL(layer_level);
>> -				handled_accesses |= BIT_ULL(access_bit);
>> -			}
>> -		}
>> -	}
>> -	return handled_accesses;
>> -}
>> -
>>   /*
>>    * Check that a destination file hierarchy has more restrictions than a source
>>    * file hierarchy.  This is only used for link and rename actions.
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 961ffe0c709e..02ab14439c43 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -572,3 +572,105 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>>   	}
>>   	return NULL;
>>   }
>> +
>> +/*
>> + * @layer_masks is read and may be updated according to the access request and
>> + * the matching rule.
>> + *
>> + * Returns true if the request is allowed (i.e. relevant layer masks for the
>> + * request are empty).
>> + */
>> +bool unmask_layers(const struct landlock_rule *const rule,
>> +		   const access_mask_t access_request,
>> +		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> +{
>> +	size_t layer_level;
>> +
>> +	if (!access_request || !layer_masks)
>> +		return true;
>> +	if (!rule)
>> +		return false;
>> +
>> +	/*
>> +	 * An access is granted if, for each policy layer, at least one rule
>> +	 * encountered on the pathwalk grants the requested access,
>> +	 * regardless of its position in the layer stack.  We must then check
>> +	 * the remaining layers for each inode, from the first added layer to
>> +	 * the last one.  When there is multiple requested accesses, for each
>> +	 * policy layer, the full set of requested accesses may not be granted
>> +	 * by only one rule, but by the union (binary OR) of multiple rules.
>> +	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
>> +	 */
>> +	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
>> +		const struct landlock_layer *const layer =
>> +			&rule->layers[layer_level];
>> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
>> +		const unsigned long access_req = access_request;
>> +		unsigned long access_bit;
>> +		bool is_empty;
>> +
>> +		/*
>> +		 * Records in @layer_masks which layer grants access to each
>> +		 * requested access.
>> +		 */
>> +		is_empty = true;
>> +		for_each_set_bit(access_bit, &access_req,
>> +				 ARRAY_SIZE(*layer_masks)) {
>> +			if (layer->access & BIT_ULL(access_bit))
>> +				(*layer_masks)[access_bit] &= ~layer_bit;
>> +			is_empty = is_empty && !(*layer_masks)[access_bit];
>> +		}
>> +		if (is_empty)
>> +			return true;
>> +	}
>> +	return false;
>> +}
>> +
>> +/**
>> + * init_layer_masks - Initialize layer masks from an access request
>> + *
>> + * Populates @layer_masks such that for each access right in @access_request,
>> + * the bits for all the layers are set where this access right is handled.
>> + *
>> + * @domain: The domain that defines the current restrictions.
>> + * @access_request: The requested access rights to check.
>> + * @layer_masks: The layer masks to populate.
>> + *
>> + * Returns: An access mask where each access right bit is set which is handled
>> + * in any of the active layers in @domain.
>> + */
>> +access_mask_t
>> +init_layer_masks(const struct landlock_ruleset *const domain,
>> +		 const access_mask_t access_request,
>> +		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> +{
>> +	access_mask_t handled_accesses = 0;
>> +	size_t layer_level;
>> +
>> +	memset(layer_masks, 0, sizeof(*layer_masks));
>> +	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
>> +	if (!access_request)
>> +		return 0;
>> +
>> +	/* Saves all handled accesses per layer. */
>> +	for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
>> +		const unsigned long access_req = access_request;
>> +		unsigned long access_bit;
>> +
>> +		for_each_set_bit(access_bit, &access_req,
>> +				 ARRAY_SIZE(*layer_masks)) {
>> +			/*
>> +			 * Artificially handles all initially denied by default
>> +			 * access rights.
>> +			 */
>> +			if (BIT_ULL(access_bit) &
>> +			    (landlock_get_fs_access_mask(domain, layer_level) |
>> +			     ACCESS_INITIALLY_DENIED)) {
> 
> This is a future bug in the a next commit when
> landlock_get_fs_access_mask() is changed to get the network access mask.
> My patch will fix this part but you'll need to do a new copy of this
> function.

   Ok. Thanks.
> 
> 
> 
>> +				(*layer_masks)[access_bit] |=
>> +					BIT_ULL(layer_level);
>> +				handled_accesses |= BIT_ULL(access_bit);
>> +			}
>> +		}
>> +	}
>> +	return handled_accesses;
>> +}
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index 608ab356bc3e..50baff4fcbb4 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -34,6 +34,16 @@ typedef u16 layer_mask_t;
>>   /* Makes sure all layers can be checked. */
>>   static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>> 
>> +/*
>> + * All access rights that are denied by default whether they are handled or not
>> + * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
>> + * entries when we need to get the absolute handled access masks.
>> + */
>> +/* clang-format off */
>> +#define ACCESS_INITIALLY_DENIED ( \
>> +	LANDLOCK_ACCESS_FS_REFER)
>> +/* clang-format on */
> 
> This ACCESS_INITIALLY_DENIED definition must be moved, not copied. You
> can rename ACCESS_INITIALLY_DENIED to ACCESS_FS_INITIALLY_DENIED and
> move this hunk before the access_mask_t definition.
> 
   Yep. Will be fixed.
> 
>> +
>>   /**
>>    * struct landlock_layer - Access rights for a given layer
>>    */
>> @@ -246,4 +256,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>>   		LANDLOCK_SHIFT_ACCESS_FS) &
>>   	       LANDLOCK_MASK_ACCESS_FS;
>>   }
>> +
>> +bool unmask_layers(const struct landlock_rule *const rule,
> 
> All public Landlock helpers must be prefixed with "landlock_"

   Do you mean ones which are shared between fs and net parts?
> 
>> +		   const access_mask_t access_request,
>> +		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>> +
>> +access_mask_t
>> +init_layer_masks(const struct landlock_ruleset *const domain,
>> +		 const access_mask_t access_request,
>> +		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
> 
> There is a warning generated by checkpatch.pl about this line:
>     WARNING: function definition argument 'layer_mask_t' should also have
> an identifier name
> 
> I think this is a bug in checkpatch.pl
> 
    I got this warn, but cant get rid of it.
    Also think its a bug in checkpatck.pl

> Any though Andy, Joe, Dwaipayan or Lukas?
> 
> 
>> +
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 05/12] landlock: Refactor unmask_layers() and init_layer_masks()
  2022-11-17 18:42   ` Mickaël Salaün
@ 2022-11-28  3:30     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:30 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:42 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> Adds new key_type argument to init_layer_masks() helper.
> 
> Add a new key_type argument to the…
> 
>> Adds masks_array_size argument  to unmask_layers() helper.
> 
> Add a masks_array_size argument to the…
> 
> 
> The name of these helpers need to be prefixed with "landlock_".
> 
> 
  Thanks. Will be Fixed.

>> These modifications support implementing new rule types in the next
>> Landlock versions.
>> 
>> Co-developed-by: Mickaël Salaün <mic@digikod.net>
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>

   Got it.
> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Refactors commit message, adds a co-developer.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Removes masks_size attribute from init_layer_masks().
>> * Refactors init_layer_masks() with new landlock_key_type.
>> 
>> Changes since v5:
>> * Splits commit.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors init_layer_masks(), get_handled_accesses()
>> and unmask_layers() functions to support multiple rule types.
>> * Refactors landlock_get_fs_access_mask() function with
>> LANDLOCK_MASK_ACCESS_FS mask.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Refactors landlock_unmask_layers functions.
>> 
>> ---
>>   security/landlock/fs.c      | 36 ++++++++++++++++++-------------
>>   security/landlock/ruleset.c | 42 ++++++++++++++++++++++++++-----------
>>   security/landlock/ruleset.h | 11 +++++-----
>>   3 files changed, 58 insertions(+), 31 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 240e42a8f788..fe76a11483f8 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -435,16 +435,20 @@ static bool is_access_to_paths_allowed(
>>   	if (unlikely(dentry_child1)) {
>>   		unmask_layers(find_rule(domain, dentry_child1),
>>   			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>> -					       &_layer_masks_child1),
>> -			      &_layer_masks_child1);
>> +					       &_layer_masks_child1,
>> +					       LANDLOCK_KEY_INODE),
>> +			      &_layer_masks_child1,
>> +			      ARRAY_SIZE(_layer_masks_child1));
>>   		layer_masks_child1 = &_layer_masks_child1;
>>   		child1_is_directory = d_is_dir(dentry_child1);
>>   	}
>>   	if (unlikely(dentry_child2)) {
>>   		unmask_layers(find_rule(domain, dentry_child2),
>>   			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>> -					       &_layer_masks_child2),
>> -			      &_layer_masks_child2);
>> +					       &_layer_masks_child2,
>> +					       LANDLOCK_KEY_INODE),
>> +			      &_layer_masks_child2,
>> +			      ARRAY_SIZE(_layer_masks_child2));
>>   		layer_masks_child2 = &_layer_masks_child2;
>>   		child2_is_directory = d_is_dir(dentry_child2);
>>   	}
>> @@ -496,15 +500,16 @@ static bool is_access_to_paths_allowed(
>>   		}
>> 
>>   		rule = find_rule(domain, walker_path.dentry);
>> -		allowed_parent1 = unmask_layers(rule, access_masked_parent1,
>> -						layer_masks_parent1);
>> -		allowed_parent2 = unmask_layers(rule, access_masked_parent2,
>> -						layer_masks_parent2);
>> +		allowed_parent1 = unmask_layers(
>> +			rule, access_masked_parent1, layer_masks_parent1,
>> +			ARRAY_SIZE(*layer_masks_parent1));
>> +		allowed_parent2 = unmask_layers(
>> +			rule, access_masked_parent2, layer_masks_parent2,
>> +			ARRAY_SIZE(*layer_masks_parent2));
>> 
>>   		/* Stops when a rule from each layer grants access. */
>>   		if (allowed_parent1 && allowed_parent2)
>>   			break;
>> -
>>   jump_up:
>>   		if (walker_path.dentry == walker_path.mnt->mnt_root) {
>>   			if (follow_up(&walker_path)) {
>> @@ -543,7 +548,8 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>>   {
>>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>> 
>> -	access_request = init_layer_masks(domain, access_request, &layer_masks);
>> +	access_request = init_layer_masks(domain, access_request, &layer_masks,
>> +					  LANDLOCK_KEY_INODE);
>>   	if (is_access_to_paths_allowed(domain, path, access_request,
>>   				       &layer_masks, NULL, 0, NULL, NULL))
>>   		return 0;
>> @@ -630,7 +636,7 @@ static bool collect_domain_accesses(
>>   		return true;
>> 
>>   	access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>> -				      layer_masks_dom);
>> +				      layer_masks_dom, LANDLOCK_KEY_INODE);
>> 
>>   	dget(dir);
>>   	while (true) {
>> @@ -638,7 +644,8 @@ static bool collect_domain_accesses(
>> 
>>   		/* Gets all layers allowing all domain accesses. */
>>   		if (unmask_layers(find_rule(domain, dir), access_dom,
>> -				  layer_masks_dom)) {
>> +				  layer_masks_dom,
>> +				  ARRAY_SIZE(*layer_masks_dom))) {
>>   			/*
>>   			 * Stops when all handled accesses are allowed by at
>>   			 * least one rule in each layer.
>> @@ -754,7 +761,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>>   		 */
>>   		access_request_parent1 = init_layer_masks(
>>   			dom, access_request_parent1 | access_request_parent2,
>> -			&layer_masks_parent1);
>> +			&layer_masks_parent1, LANDLOCK_KEY_INODE);
>>   		if (is_access_to_paths_allowed(
>>   			    dom, new_dir, access_request_parent1,
>>   			    &layer_masks_parent1, NULL, 0, NULL, NULL))
>> @@ -1131,7 +1138,8 @@ static int hook_file_open(struct file *const file)
>> 
>>   	if (is_access_to_paths_allowed(
>>   		    dom, &file->f_path,
>> -		    init_layer_masks(dom, full_access_request, &layer_masks),
>> +		    init_layer_masks(dom, full_access_request, &layer_masks,
>> +				     LANDLOCK_KEY_INODE),
>>   		    &layer_masks, NULL, 0, NULL, NULL)) {
>>   		allowed_access = full_access_request;
>>   	} else {
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 02ab14439c43..c7cf54ba4f6d 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -576,13 +576,15 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>>   /*
>>    * @layer_masks is read and may be updated according to the access request and
>>    * the matching rule.
>> + * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
>>    *
>>    * Returns true if the request is allowed (i.e. relevant layer masks for the
>>    * request are empty).
>>    */
>>   bool unmask_layers(const struct landlock_rule *const rule,
>>   		   const access_mask_t access_request,
>> -		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> +		   layer_mask_t (*const layer_masks)[],
>> +		   const size_t masks_array_size)
>>   {
>>   	size_t layer_level;
>> 
>> @@ -614,8 +616,7 @@ bool unmask_layers(const struct landlock_rule *const rule,
>>   		 * requested access.
>>   		 */
>>   		is_empty = true;
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> +		for_each_set_bit(access_bit, &access_req, masks_array_size) {
>>   			if (layer->access & BIT_ULL(access_bit))
>>   				(*layer_masks)[access_bit] &= ~layer_bit;
>>   			is_empty = is_empty && !(*layer_masks)[access_bit];
>> @@ -626,6 +627,10 @@ bool unmask_layers(const struct landlock_rule *const rule,
>>   	return false;
>>   }
>> 
>> +typedef access_mask_t
>> +get_access_mask_t(const struct landlock_ruleset *const ruleset,
>> +		  const u16 layer_level);
>> +
>>   /**
>>    * init_layer_masks - Initialize layer masks from an access request
>>    *
>> @@ -635,19 +640,33 @@ bool unmask_layers(const struct landlock_rule *const rule,
>>    * @domain: The domain that defines the current restrictions.
>>    * @access_request: The requested access rights to check.
>>    * @layer_masks: The layer masks to populate.
>> + * @key_type: The key type to switch between access masks of different types.
>>    *
>>    * Returns: An access mask where each access right bit is set which is handled
>>    * in any of the active layers in @domain.
>>    */
>> -access_mask_t
>> -init_layer_masks(const struct landlock_ruleset *const domain,
>> -		 const access_mask_t access_request,
>> -		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>> +			       const access_mask_t access_request,
>> +			       layer_mask_t (*const layer_masks)[],
>> +			       const enum landlock_key_type key_type)
>>   {
>>   	access_mask_t handled_accesses = 0;
>> -	size_t layer_level;
>> +	size_t layer_level, num_access;
>> +	get_access_mask_t *get_access_mask;
>> +
>> +	switch (key_type) {
>> +	case LANDLOCK_KEY_INODE:
>> +		get_access_mask = landlock_get_fs_access_mask;
>> +		num_access = LANDLOCK_NUM_ACCESS_FS;
>> +		break;
>> +	default:
>> +		WARN_ON_ONCE(1);
>> +		return 0;
>> +	}
>> +
>> +	memset(layer_masks, 0,
>> +	       array_size(sizeof((*layer_masks)[0]), num_access));
>> 
>> -	memset(layer_masks, 0, sizeof(*layer_masks));
>>   	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
>>   	if (!access_request)
>>   		return 0;
>> @@ -657,14 +676,13 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>>   		const unsigned long access_req = access_request;
>>   		unsigned long access_bit;
>> 
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> +		for_each_set_bit(access_bit, &access_req, num_access) {
>>   			/*
>>   			 * Artificially handles all initially denied by default
>>   			 * access rights.
>>   			 */
>>   			if (BIT_ULL(access_bit) &
>> -			    (landlock_get_fs_access_mask(domain, layer_level) |
>> +			    (get_access_mask(domain, layer_level) |
>>   			     ACCESS_INITIALLY_DENIED)) {
>>   				(*layer_masks)[access_bit] |=
>>   					BIT_ULL(layer_level);
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index 50baff4fcbb4..d9eb79ea9a89 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -259,11 +259,12 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>> 
>>   bool unmask_layers(const struct landlock_rule *const rule,
>>   		   const access_mask_t access_request,
>> -		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>> +		   layer_mask_t (*const layer_masks)[],
>> +		   const size_t masks_array_size);
>> 
>> -access_mask_t
>> -init_layer_masks(const struct landlock_ruleset *const domain,
>> -		 const access_mask_t access_request,
>> -		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>> +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>> +			       const access_mask_t access_request,
>> +			       layer_mask_t (*const layer_masks)[],
>> +			       const enum landlock_key_type key_type);
>> 
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall
  2022-11-17 18:42   ` Mickaël Salaün
@ 2022-11-28  3:32     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  3:32 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:42 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> Modifies landlock_add_rule() syscall to support new rule types in future
> 
> Change the landlock_add_rule() syscall…
> 
   Ok.
> 
>> Landlock versions. Adds add_rule_path_beneath() helper to support
> 
> Add the…

   Got it. Thanks.
> 
>> current filesystem rules.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * None
>> 
>> Changes since v6:
>> * None
>> 
>> Changes since v5:
>> * Refactors syscall landlock_add_rule() and add_rule_path_beneath() helper
>> to make argument check ordering consistent and get rid of partial revertings
>> in following patches.
>> * Rolls back refactoring base_test.c seltest.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors add_rule_path_beneath() and landlock_add_rule() functions
>> to optimize code usage.
>> * Refactors base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
>> rule type in landlock_add_rule() call.
>> 
>> Changes since v3:
>> * Split commit.
>> * Refactors landlock_add_rule syscall.
>> 
>> ---
>>   security/landlock/syscalls.c | 92 +++++++++++++++++++-----------------
>>   1 file changed, 48 insertions(+), 44 deletions(-)
>> 
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index 71aca7f990bc..87389d7bfbf2 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -274,6 +274,47 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>>   	return err;
>>   }
>> 
>> +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>> +				 const void __user *const rule_attr)
>> +{
>> +	struct landlock_path_beneath_attr path_beneath_attr;
>> +	struct path path;
>> +	int res, err;
>> +	u32 mask;
> 
> access_mask_t mask;

   will be fixed thanks.
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2022-11-17 18:43   ` Mickaël Salaün
@ 2022-11-28  4:01     ` Konstantin Meskhidze (A)
  2022-11-28 20:26       ` Mickaël Salaün
  2023-01-03 12:44     ` Konstantin Meskhidze (A)
  2023-01-04 11:41     ` Konstantin Meskhidze (A)
  2 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  4:01 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)



11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This commit adds network rules support in internal landlock functions
>> (presented in ruleset.c) and landlock_create_ruleset syscall.
> 
> …in the ruleset management helpers and the landlock_create_ruleset syscall.
> 
> 
>> Refactors user space API to support network actions. Adds new network
> 
> Refactor…
> 
>> access flags, network rule and network attributes. Increments Landlock
> 
> Increment…

   The commit's message will be fixed. Thank you!
> 
>> ABI version.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Squashes commits.
>> * Increments ABI version to 4.
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>    because it OR values.
>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_net_access_mask().
>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>    LANDLOCK_NUM_ACCESS_FS as value.
>> * Updates access_masks_t to u32 to support network access actions.
>> * Refactors landlock internal functions to support network actions with
>>    landlock_key/key_type/id types.
>> 
>> Changes since v5:
>> * Gets rid of partial revert from landlock_add_rule
>> syscall.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactors landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds network rule support for internal landlock functions.
>> * Adds set_mask and get_mask for network.
>> * Adds rb_root root_net_port.
>> 
>> ---
>>   include/uapi/linux/landlock.h                | 49 ++++++++++++++
>>   security/landlock/limits.h                   |  6 +-
>>   security/landlock/ruleset.c                  | 55 ++++++++++++++--
>>   security/landlock/ruleset.h                  | 68 ++++++++++++++++----
>>   security/landlock/syscalls.c                 | 13 +++-
>>   tools/testing/selftests/landlock/base_test.c |  2 +-
>>   6 files changed, 170 insertions(+), 23 deletions(-)
>> 
>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>> index f3223f964691..096b683c6ff3 100644
>> --- a/include/uapi/linux/landlock.h
>> +++ b/include/uapi/linux/landlock.h
>> @@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
>>   	 * this access right.
>>   	 */
>>   	__u64 handled_access_fs;
>> +
>> +	/**
>> +	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
>> +	 * that is handled by this ruleset and should then be forbidden if no
>> +	 * rule explicitly allow them.
>> +	 */
>> +	__u64 handled_access_net;
>>   };
>> 
>>   /*
>> @@ -54,6 +61,11 @@ enum landlock_rule_type {
>>   	 * landlock_path_beneath_attr .
>>   	 */
>>   	LANDLOCK_RULE_PATH_BENEATH = 1,
>> +	/**
>> +	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
>> +	 * landlock_net_service_attr .
>> +	 */
>> +	LANDLOCK_RULE_NET_SERVICE = 2,
>>   };
>> 
>>   /**
>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>   	 */
>>   } __attribute__((packed));
>> 
>> +/**
>> + * struct landlock_net_service_attr - TCP subnet definition
>> + *
>> + * Argument of sys_landlock_add_rule().
>> + */
>> +struct landlock_net_service_attr {
>> +	/**
>> +	 * @allowed_access: Bitmask of allowed access network for services
>> +	 * (cf. `Network flags`_).
>> +	 */
>> +	__u64 allowed_access;
>> +	/**
>> +	 * @port: Network port.
>> +	 */
>> +	__u16 port;
> 
>   From an UAPI point of view, I think the port field should be __be16, as
> for sockaddr_in->port and other network-related APIs. This will require
> some kernel changes to please sparse: make C=2 security/landlock/ must
> not print any warning.

   Is sparse a default checker?
> 
> Using big-endian values as keys (casted to uintptr_t, not strictly
> __be16) in the rb-tree should not be an issue because there is no port
> range ordering (for now).
> 
> A dedicated test should check that endianness is correct, e.g. by using
> different port encoding. This should include passing and failing tests,
> but they should work on all architectures (i.e. big or little endian).
> 
   I got it. Will be fixed.
> 
>> +
>> +} __attribute__((packed));
>> +
>>   /**
>>    * DOC: fs_access
>>    *
>> @@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
>>   #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
>>   /* clang-format on */
>> 
>> +/**
>> + * DOC: net_access
>> + *
>> + * Network flags
>> + * ~~~~~~~~~~~~~~~~
>> + *
>> + * These flags enable to restrict a sandboxed process to a set of network
>> + * actions.
>> + *
>> + * TCP sockets with allowed actions:
>> + *
>> + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
>> + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
>> + *   a remote port.
>> + */
>> +/* clang-format off */
>> +#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
>> +#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
>> +/* clang-format on */
>>   #endif /* _UAPI_LINUX_LANDLOCK_H */
>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>> index bafb3b8dc677..8a1a6463c64e 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -23,6 +23,10 @@
>>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>   #define LANDLOCK_SHIFT_ACCESS_FS	0
>> 
>> -/* clang-format on */
>> +#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
>> +#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
>> +#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>> +#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
>> 
>> +/* clang-format on */
>>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index c7cf54ba4f6d..9277c1295114 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -36,6 +36,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   	refcount_set(&new_ruleset->usage, 1);
>>   	mutex_init(&new_ruleset->lock);
>>   	new_ruleset->root_inode = RB_ROOT;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	new_ruleset->root_net_port = RB_ROOT;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	new_ruleset->num_layers = num_layers;
>>   	/*
>>   	 * hierarchy = NULL
>> @@ -46,16 +49,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   }
>> 
>>   struct landlock_ruleset *
>> -landlock_create_ruleset(const access_mask_t fs_access_mask)
>> +landlock_create_ruleset(const access_mask_t fs_access_mask,
>> +			const access_mask_t net_access_mask)
>>   {
>>   	struct landlock_ruleset *new_ruleset;
>> 
>>   	/* Informs about useless ruleset. */
>> -	if (!fs_access_mask)
>> +	if (!fs_access_mask && !net_access_mask)
>>   		return ERR_PTR(-ENOMSG);
>>   	new_ruleset = create_ruleset(1);
>> -	if (!IS_ERR(new_ruleset))
>> +	if (IS_ERR(new_ruleset))
>> +		return new_ruleset;
>> +	if (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);
>>   	return new_ruleset;
>>   }
>> 
>> @@ -73,6 +81,10 @@ static inline bool is_object_pointer(const enum landlock_key_type key_type)
>>   	switch (key_type) {
>>   	case LANDLOCK_KEY_INODE:
>>   		return true;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		return false;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	}
>>   	WARN_ON_ONCE(1);
>>   	return false;
>> @@ -126,6 +138,11 @@ static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
>>   	case LANDLOCK_KEY_INODE:
>>   		root = &ruleset->root_inode;
>>   		break;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		root = &ruleset->root_net_port;
>> +		break;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	}
>>   	if (WARN_ON_ONCE(!root))
>>   		return ERR_PTR(-EINVAL);
>> @@ -154,7 +171,9 @@ static void build_check_ruleset(void)
>>   	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>>   	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>>   	BUILD_BUG_ON(access_masks <
>> -		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) +
> 
> This is correct but because we are dealing with bitmasks I would prefer
> to use "|" instead of "+".

   Ok. I will refactor it.
> 
> 
>> +			     (LANDLOCK_MASK_ACCESS_NET
>> +			      << LANDLOCK_SHIFT_ACCESS_NET));
>>   }
>> 
>>   /**
>> @@ -370,7 +389,12 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
>>   	if (err)
>>   		goto out_unlock;
>> -
> 
> Please keep this newline.

   Got it.
> 
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/* Merges the @src network port tree. */
>> +	err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
>> +	if (err)
>> +		goto out_unlock;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   out_unlock:
>>   	mutex_unlock(&src->lock);
>>   	mutex_unlock(&dst->lock);
>> @@ -426,7 +450,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>   	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
>>   	if (err)
>>   		goto out_unlock;
>> -
> 
> newline

  Ok.
> 
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/* Copies the @parent network port tree. */
>> +	err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
>> +	if (err)
>> +		goto out_unlock;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
>>   		err = -EINVAL;
>>   		goto out_unlock;
>> @@ -459,6 +488,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
>>   	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
>>   					     node)
>>   		free_rule(freeme, LANDLOCK_KEY_INODE);
>> +#if IS_ENABLED(CONFIG_INET)
>> +	rbtree_postorder_for_each_entry_safe(freeme, next,
>> +					     &ruleset->root_net_port, node)
>> +		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	put_hierarchy(ruleset->hierarchy);
>>   	kfree(ruleset);
>>   }
>> @@ -637,6 +671,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
>>    * Populates @layer_masks such that for each access right in @access_request,
>>    * the bits for all the layers are set where this access right is handled.
>>    *
>> + * @layer_masks must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET
>> + * elements according to @key_type.
> 
> Please include this sentence in the @layer_masks description below.

   Ok.
> 
>> + *
>>    * @domain: The domain that defines the current restrictions.
>>    * @access_request: The requested access rights to check.
>>    * @layer_masks: The layer masks to populate.
> 
> "It must contain…"
> 
> 
>> @@ -659,6 +696,12 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>>   		get_access_mask = landlock_get_fs_access_mask;
>>   		num_access = LANDLOCK_NUM_ACCESS_FS;
>>   		break;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		get_access_mask = landlock_get_net_access_mask;
>> +		num_access = LANDLOCK_NUM_ACCESS_NET;
>> +		break;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	default:
>>   		WARN_ON_ONCE(1);
>>   		return 0;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index d9eb79ea9a89..f272d2cd518c 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -19,16 +19,20 @@
>>   #include "limits.h"
>>   #include "object.h"
>> 
>> +/* Rule access mask. */
>>   typedef u16 access_mask_t;
>>   /* Makes sure all filesystem access rights can be stored. */
>>   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 for_each_set_bit() and for_each_clear_bit() calls are OK. */
>>   static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>> 
>>   /* Ruleset access masks. */
>> -typedef u16 access_masks_t;
>> +typedef u32 access_masks_t;
> 
> This type change need to be explained in the commit message.
> 
   Ok. I will explain it.
> 
>>   /* Makes sure all ruleset access rights can be stored. */
>> -static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
>> +static_assert(BITS_PER_TYPE(access_masks_t) >=
>> +	      LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
>> 
>>   typedef u16 layer_mask_t;
>>   /* Makes sure all layers can be checked. */
>> @@ -82,6 +86,13 @@ enum landlock_key_type {
>>   	 * keys.
>>   	 */
>>   	LANDLOCK_KEY_INODE = 1,
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/**
>> +	 * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
>> +	 * node keys.
>> +	 */
>> +	LANDLOCK_KEY_NET_PORT = 2,
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   };
>> 
>>   /**
>> @@ -156,6 +167,15 @@ struct landlock_ruleset {
>>   	 * reaches zero.
>>   	 */
>>   	struct rb_root root_inode;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/**
>> +	 * @root_net_port: Root of a red-black tree containing &struct
>> +	 * landlock_rule nodes with network port. Once a ruleset is tied to a
>> +	 * process (i.e. as a domain), this tree is immutable until @usage
>> +	 * reaches zero.
>> +	 */
>> +	struct rb_root root_net_port;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	/**
>>   	 * @hierarchy: Enables hierarchy identification even when a parent
>>   	 * domain vanishes.  This is needed for the ptrace protection.
>> @@ -166,8 +186,8 @@ struct landlock_ruleset {
>>   		 * @work_free: Enables to free a ruleset within a lockless
>>   		 * section.  This is only used by
>>   		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>> -		 * The fields @lock, @usage, @num_rules, @num_layers and
>> -		 * @access_masks are then unused.
>> +		 * The fields @lock, @usage, @num_rules, @num_layers,
>> +		 * @net_access_mask and @access_masks are then unused.
> 
> There is no net_access_mask anymore.
> .

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

* Re: [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset()
  2022-11-17 18:43   ` Mickaël Salaün
@ 2022-11-28  4:02     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  4:02 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This commit moves enforce_ruleset() helper function to common.h so that
>> to be used both by filesystem tests and network ones.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Refactors commit message.
>> 
>> Changes since v6:
>> * None.
>> 
>> Changes since v5:
>> * Splits commit.
>> * Moves enforce_ruleset helper into common.h
>> * Formats code with clang-format-14.
>> 
>> ---
>>   tools/testing/selftests/landlock/common.h  | 10 ++++++++++
>>   tools/testing/selftests/landlock/fs_test.c | 10 ----------
>>   2 files changed, 10 insertions(+), 10 deletions(-)
>> 
>> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
>> index d7987ae8d7fc..bafed1c0c2a6 100644
>> --- a/tools/testing/selftests/landlock/common.h
>> +++ b/tools/testing/selftests/landlock/common.h
>> @@ -256,3 +256,13 @@ static int __maybe_unused send_fd(int usock, int fd_tx)
>>   		return -errno;
>>   	return 0;
>>   }
>> +
>> +__attribute__((__unused__)) static void
> 
> We can now use __maybe_unused instead. This enables to avoid
> checkpatch.pl warning.

  Ok. Will be refactored. Thanks.
> 
> 
>> +enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
>> +{
>> +	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
>> +	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
>> +	{
>> +		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
>> +	}
>> +}
>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>> index d5dab986f612..20c1ac8485f1 100644
>> --- a/tools/testing/selftests/landlock/fs_test.c
>> +++ b/tools/testing/selftests/landlock/fs_test.c
>> @@ -563,16 +563,6 @@ static int create_ruleset(struct __test_metadata *const _metadata,
>>   	return ruleset_fd;
>>   }
>> 
>> -static void enforce_ruleset(struct __test_metadata *const _metadata,
>> -			    const int ruleset_fd)
>> -{
>> -	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
>> -	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
>> -	{
>> -		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
>> -	}
>> -}
>> -
>>   TEST_F_FORK(layout1, proc_nsfs)
>>   {
>>   	const struct rule rules[] = {
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 12/12] landlock: Document Landlock's network support
  2022-11-17 18:44   ` Mickaël Salaün
@ 2022-11-28  6:44     ` Konstantin Meskhidze (A)
  2022-11-28 20:26       ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  6:44 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-doc



11/17/2022 9:44 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> Describes network access rules for TCP sockets. Adds network access
>> example in the tutorial. Points out AF_UNSPEC socket family behaviour.
>> Adds kernel configuration support for network.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Fixes documentaion logic errors and typos as Mickaёl suggested:
>> https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/
>> 
>> Changes since v6:
>> * Adds network support documentaion.
>> 
>> ---
>>   Documentation/userspace-api/landlock.rst | 72 +++++++++++++++++++-----
>>   1 file changed, 59 insertions(+), 13 deletions(-)
>> 
>> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
>> index d8cd8cd9ce25..d0610ec9ce05 100644
>> --- a/Documentation/userspace-api/landlock.rst
>> +++ b/Documentation/userspace-api/landlock.rst
>> @@ -11,10 +11,10 @@ Landlock: unprivileged access control
>>   :Date: October 2022
>> 
>>   The goal of Landlock is to enable to restrict ambient rights (e.g. global
>> -filesystem access) for a set of processes.  Because Landlock is a stackable
>> -LSM, it makes possible to create safe security sandboxes as new security layers
>> -in addition to the existing system-wide access-controls. This kind of sandbox
>> -is expected to help mitigate the security impact of bugs or
>> +filesystem or network access) for a set of processes.  Because Landlock
>> +is a stackable LSM, it makes possible to create safe security sandboxes as new
>> +security layers in addition to the existing system-wide access-controls. This
>> +kind of sandbox is expected to help mitigate the security impact of bugs or
>>   unexpected/malicious behaviors in user space applications.  Landlock empowers
>>   any process, including unprivileged ones, to securely restrict themselves.
>> 
>> @@ -30,18 +30,20 @@ Landlock rules
>> 
>>   A Landlock rule describes an action on an object.  An object is currently a
>>   file hierarchy, and the related filesystem actions are defined with `access
>> -rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
>> -the thread enforcing it, and its future children.
>> +rights`_.  Since ABI version 4 a port data appears with related network actions
>> +for TCP socket families.  A set of rules is aggregated in a ruleset, which
>> +can then restrict the thread enforcing it, and its future children.
>> 
>>   Defining and enforcing a security policy
>>   ----------------------------------------
>> 
>>   We first need to define the ruleset that will contain our rules.  For this
>>   example, the ruleset will contain rules that only allow read actions, but write
>> -actions will be denied.  The ruleset then needs to handle both of these kind of
>> +actions will be denied. The ruleset then needs to handle both of these kind of
>>   actions.  This is required for backward and forward compatibility (i.e. the
>>   kernel and user space may not know each other's supported restrictions), hence
>> -the need to be explicit about the denied-by-default access rights.
>> +the need to be explicit about the denied-by-default access rights.  Also ruleset
>> +will have network rules for specific ports, so it should handle network actions.
>> 
>>   .. code-block:: c
>> 
>> @@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
>>               LANDLOCK_ACCESS_FS_MAKE_SYM |
>>               LANDLOCK_ACCESS_FS_REFER |
>>               LANDLOCK_ACCESS_FS_TRUNCATE,
>> +        .handled_access_net =
>> +            LANDLOCK_ACCESS_NET_BIND_TCP |
>> +            LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>       };
>> 
>>   Because we may not know on which kernel version an application will be
>> @@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
>>   using.  To avoid binary enforcement (i.e. either all security features or
>>   none), we can leverage a dedicated Landlock command to get the current version
>>   of the Landlock ABI and adapt the handled accesses.  Let's check if we should
>> -remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
>> -access rights, which are only supported starting with the second and third
>> -version of the ABI.
>> +remove the `LANDLOCK_ACCESS_FS_REFER` or `LANDLOCK_ACCESS_FS_TRUNCATE` or
>> +network access rights, which are only supported starting with the second,
> 
> This is a bad rebase.

   Sorry. Did not get it.
> 
> 
>> +third and fourth version of the ABI.
>> 
>>   .. code-block:: c
>> 
>>       int abi;
>> 
>> +    #define ACCESS_NET_BIND_CONNECT ( \
>> +    LANDLOCK_ACCESS_NET_BIND_TCP | \
>> +    LANDLOCK_ACCESS_NET_CONNECT_TCP)
> 
> Please add a 4-spaces prefix for these two lines.

   Like this??
	#define ACCESS_NET_BIND_CONNECT ( \
             LANDLOCK_ACCESS_NET_BIND_TCP | \
             LANDLOCK_ACCESS_NET_CONNECT_TCP)
> 
> 
>> +
>>       abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
>>       if (abi < 0) {
>>           /* Degrades gracefully if Landlock is not handled. */
>> @@ -92,6 +101,9 @@ version of the ABI.
>>       case 2:
>>           /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>>           ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
>> +    case 3:
>> +        /* Removes network support for ABI < 4 */
>> +        ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;
>>       }
>> 
>>   This enables to create an inclusive ruleset that will contain our rules.
>> @@ -143,8 +155,22 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>   ABI version.  In this example, this is not required because all of the requested
>>   ``allowed_access`` rights are already available in ABI 1.
>> 
>> +For network part we can add number of rules containing a port number and actions
>> +that a process is allowed to do for certian ports.
> 
> For the network access-control, we can add a set of rules that allow to
> use a port number for a specific action.
> 
   Ok. Will be fixed.
>> +
>> +.. code-block:: c
>> +
>> +    struct landlock_net_service_attr net_service = {
>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> +        .port = 8080,
>> +    };
>> +
>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +                            &net_service, 0);
>> +
>>   We now have a ruleset with one rule allowing read access to ``/usr`` while > -denying all other handled accesses for the filesystem.  The next 
> step is to

   Ok. Thanks.

>> +denying all other handled accesses for the filesystem.  The ruleset also contains
>> +a rule allowing to bind current proccess to the port 8080.  The next step is to
> 
> We now have a ruleset with the first rule allowing read access to
> ``/usr`` while denying all other handled accesses for the filesystem,
> and a second rule allowing TCP binding on port 8080.
> 
  Got it. Thanks.
> 
>>   restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>   binary).
>> 
>> @@ -296,6 +322,13 @@ not.  It is also possible to pass such file descriptors between processes,
>>   keeping their Landlock properties, even when these processes do not have an
>>   enforced Landlock ruleset.
>> 
>> +AF_UNSPEC socket family
>> +-----------------------
>> +
>> +Sockets of AF_UNSPEC family types are treated as AF_INET(IPv4) socket for bind()
> 
> ``AF_UNSPEC`` sockets are handled as ``AF_INET`` sockets for the TCP
> binding action. However, connecting an ``AF_UNSPEC`` socket is always
> denied if at least one Landlock domain handles
> ``LANDLOCK_NET_CONNECT_TCP``.  This restriction protects against…
> 
> As commented in patch 8/12, I don't think this is the right approach
> anyway. It may then not be worth a dedicated section.
> 
   Ok. Let's discuss it in 8/12 patch.
> 
>> +action.  But connect() one is not allowed by Landlock for AF_UNSPEC sockets. This
>> +logic prevents from disconnecting already connected sockets.
>> +
>>   Compatibility
>>   =============
>> 
>> @@ -355,7 +388,7 @@ Access rights
>>   -------------
>> 
>>   .. kernel-doc:: include/uapi/linux/landlock.h
>> -    :identifiers: fs_access
>> +    :identifiers: fs_access net_access
>> 
>>   Creating a new ruleset
>>   ----------------------
>> @@ -374,6 +407,7 @@ Extending a ruleset
>> 
>>   .. kernel-doc:: include/uapi/linux/landlock.h
>>       :identifiers: landlock_rule_type landlock_path_beneath_attr
>> +                  landlock_net_service_attr
>> 
>>   Enforcing a ruleset
>>   -------------------
>> @@ -451,6 +485,13 @@ always allowed when using a kernel that only supports the first or second ABI.
>>   Starting with the Landlock ABI version 3, it is now possible to securely control
>>   truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
>> 
>> +Network support (ABI < 4)
>> +-------------------------
>> +
>> +Starting with the Landlock ABI version 4, it is now possible to restrict TCP
>> +sockets' bind() and connect() actions for specific ports allowing processes
>> +to establish restricted connections.
> 
> it is now possible to restrict TCP bind and connect actions to only a
> set of allowed ports.

  Got it. Thanks.
> 
>> +
>>   .. _kernel_support:
>> 
>>   Kernel support
>> @@ -469,6 +510,11 @@ still enable it by adding ``lsm=landlock,[...]`` to
>>   Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
>>   configuration.
>> 
>> +To be able to explicitly allow TCP operations (e.g., adding a network rule with
>> +`LANDLOCK_ACCESS_NET_TCP_BIND`), the kernel must support TCP (`CONFIG_INET=y`).
> 
> Please use double backquotes everywhere, cf.
> https://git.kernel.org/torvalds/c/2fff00c81d4c
> 
   Ok. I will check.
> 
>> +Otherwise, sys_landlock_add_rule() returns an `EAFNOSUPPORT` error, which can
>> +safely be ignored because this kind of TCP operation is already not possible.
>> +
>>   Questions and answers
>>   =====================
>> 
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-11-17 18:43   ` Mickaël Salaün
@ 2022-11-28  8:21     ` Konstantin Meskhidze (A)
  2022-11-28 21:00       ` Mickaël Salaün
  2023-01-05  8:57     ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-11-28  8:21 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-api,
	Alejandro Colomar (man-pages)



11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This patch adds support of socket_bind() and socket_connect() hooks.
>> It's possible to restrict binding and connecting of TCP sockets to
>> particular ports.
> 
> Implement socket_bind() and socket_connect LSM hooks, which enable to
> restrict TCP socket binding and connection to specific ports.
> 
   Ok. Thanks.
> 
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Minor fixes.
>> * Refactors commit message.
>> 
>> Changes since v6:
>> * Updates copyright.
>> * Refactors landlock_append_net_rule() and check_socket_access()
>>    functions with landlock_id type.
>> 
>> Changes since v5:
>> * Fixes some logic errors.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Factors out CONFIG_INET into make file.
>> * Refactors check_socket_access().
>> * Adds helper get_port().
>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>> functions to support AF_INET6 family.
>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>> functions.
>> * Refactors add_rule_net_service() and landlock_add_rule
>> syscall to support network rule inserting.
>> * Refactors init_layer_masks() to support network rules.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds SECURITY_NETWORK in config.
>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>> * Adds hook_socket_bind and hook_socket_connect hooks.
>> 
>> ---
>>   security/landlock/Kconfig    |   1 +
>>   security/landlock/Makefile   |   2 +
>>   security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>   security/landlock/net.h      |  26 ++++++
>>   security/landlock/setup.c    |   2 +
>>   security/landlock/syscalls.c |  59 ++++++++++++-
>>   6 files changed, 251 insertions(+), 3 deletions(-)
>>   create mode 100644 security/landlock/net.c
>>   create mode 100644 security/landlock/net.h
>> 
>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>> index 8e33c4e8ffb8..10c099097533 100644
>> --- a/security/landlock/Kconfig
>> +++ b/security/landlock/Kconfig
>> @@ -3,6 +3,7 @@
>>   config SECURITY_LANDLOCK
>>   	bool "Landlock support"
>>   	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>> +	select SECURITY_NETWORK
>>   	select SECURITY_PATH
>>   	help
>>   	  Landlock is a sandboxing mechanism that enables processes to restrict
>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>> index 7bbd2f413b3e..53d3c92ae22e 100644
>> --- a/security/landlock/Makefile
>> +++ b/security/landlock/Makefile
>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>> 
>>   landlock-y := setup.o syscalls.o object.o ruleset.o \
>>   	cred.o ptrace.o fs.o
>> +
>> +landlock-$(CONFIG_INET) += net.o
>> \ No newline at end of file
>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>> new file mode 100644
>> index 000000000000..39e8a156a1f4
>> --- /dev/null
>> +++ b/security/landlock/net.c
>> @@ -0,0 +1,164 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>> + * Copyright © 2022 Microsoft Corporation
>> + */
>> +
>> +#include <linux/in.h>
>> +#include <linux/net.h>
>> +#include <linux/socket.h>
>> +#include <net/ipv6.h>
>> +
>> +#include "common.h"
>> +#include "cred.h"
>> +#include "limits.h"
>> +#include "net.h"
>> +#include "ruleset.h"
>> +
>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>> +			     const u16 port, access_mask_t access_rights)
>> +{
>> +	int err;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>> +
>> +	/* Transforms relative access rights to absolute ones. */
>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>> +
>> +	mutex_lock(&ruleset->lock);
>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>> +	mutex_unlock(&ruleset->lock);
>> +
>> +	return err;
>> +}
>> +
>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>> +			       u16 port, access_mask_t access_request)
>> +{
>> +	bool allowed = false;
>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>> +	const struct landlock_rule *rule;
>> +	access_mask_t handled_access;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +
>> +	if (WARN_ON_ONCE(!domain))
>> +		return 0;
>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>> +		return -EACCES;
>> +
>> +	rule = landlock_find_rule(domain, id);
>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>> +					  LANDLOCK_KEY_NET_PORT);
>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>> +				ARRAY_SIZE(layer_masks));
>> +
>> +	return allowed ? 0 : -EACCES;
>> +}
>> +
>> +static u16 get_port(const struct sockaddr *const address)
> 
> get_port() should return a __be16 type. This enables to avoid converting
> port when checking a rule.
> 
> make C=2 security/landlock/ must not print any warning.

   Got it.
> 
> 
>> +{
>> +	/* Gets port value in host byte order. */
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +	case AF_INET: {
>> +		const struct sockaddr_in *const sockaddr =
>> +			(struct sockaddr_in *)address;
>> +		return ntohs(sockaddr->sin_port);
>> +	}
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6: {
>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>> +			(struct sockaddr_in6 *)address;
>> +		return ntohs(sockaddr_ip6->sin6_port);
>> +	}
>> +#endif
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>> +			    int addrlen)
>> +{
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
>> +
>> +	if (!dom)
>> +		return 0;
>> +
>> +	/* Check if it's a TCP socket. */
>> +	if (sock->type != SOCK_STREAM)
>> +		return 0;
>> +
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		return check_socket_access(dom, get_port(address),
>> +					   LANDLOCK_ACCESS_NET_BIND_TCP);
>> +	default:
>> +		return 0;
> 
> You can remove this default case and move the return 0 at the end of the
> function.
> 
   Ok. Will be refactored.
> 
>> +	}
>> +}
>> +
>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>> +			       int addrlen)
>> +{
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
>> +
>> +	if (!dom)
>> +		return 0;
>> +
>> +	/* Check if it's a TCP socket. */
>> +	if (sock->type != SOCK_STREAM)
>> +		return 0;
>> +
>> +	/* Check if the hook is AF_INET* socket's action. */
>> +	switch (address->sa_family) {
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		return check_socket_access(dom, get_port(address),
>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>> +	case AF_UNSPEC: {
>> +		u16 i;
> 
> You can move "i" after the "dom" declaration to remove the extra braces.
> 
   Ok. Thanks.
> 
>> +
>> +		/*
>> +		 * If just in a layer a mask supports connect access,
>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>> +		 * must be banned. This prevents from disconnecting already
>> +		 * connected sockets.
>> +		 */
>> +		for (i = 0; i < dom->num_layers; i++) {
>> +			if (landlock_get_net_access_mask(dom, i) &
>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> +				return -EACCES;
> 
> I'm wondering if this is the right error code for this case. EPERM may
> be more appropriate.

   Ok. Will be refactored.
> 
> Thinking more about this case, I don't understand what is the rationale
> to deny such action. What would be the consequence to always allow
> connection with AF_UNSPEC (i.e. to disconnect a socket)?
> 
   I thought we have come to a conclusion about connect(...AF_UNSPEC..) 
  behaviour in the patchset V3:
https://lore.kernel.org/linux-security-module/19ad3a01-d76e-0e73-7833-99acd4afd97e@huawei.com/
> 
>> +		}
>> +	}
>> +	}
>> +	return 0;
>> +}
>> +
>> +static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
>> +	LSM_HOOK_INIT(socket_bind, hook_socket_bind),
>> +	LSM_HOOK_INIT(socket_connect, hook_socket_connect),
>> +};
>> +
>> +__init void landlock_add_net_hooks(void)
>> +{
>> +	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
>> +			   LANDLOCK_NAME);
>> +}
>> diff --git a/security/landlock/net.h b/security/landlock/net.h
>> new file mode 100644
>> index 000000000000..0da1d9dff5ab
>> --- /dev/null
>> +++ b/security/landlock/net.h
>> @@ -0,0 +1,26 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>> + */
>> +
>> +#ifndef _SECURITY_LANDLOCK_NET_H
>> +#define _SECURITY_LANDLOCK_NET_H
>> +
>> +#include "common.h"
>> +#include "ruleset.h"
>> +#include "setup.h"
>> +
>> +#if IS_ENABLED(CONFIG_INET)
>> +__init void landlock_add_net_hooks(void);
>> +
>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>> +			     const u16 port, access_mask_t access_rights);
>> +#else /* IS_ENABLED(CONFIG_INET) */
>> +static inline void landlock_add_net_hooks(void)
>> +{
>> +}
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>> +
>> +#endif /* _SECURITY_LANDLOCK_NET_H */
>> diff --git a/security/landlock/setup.c b/security/landlock/setup.c
>> index 3f196d2ce4f9..7e4a598177b8 100644
>> --- a/security/landlock/setup.c
>> +++ b/security/landlock/setup.c
>> @@ -14,6 +14,7 @@
>>   #include "fs.h"
>>   #include "ptrace.h"
>>   #include "setup.h"
>> +#include "net.h"
>> 
>>   bool landlock_initialized __lsm_ro_after_init = false;
>> 
>> @@ -29,6 +30,7 @@ static int __init landlock_init(void)
>>   	landlock_add_cred_hooks();
>>   	landlock_add_ptrace_hooks();
>>   	landlock_add_fs_hooks();
>> +	landlock_add_net_hooks();
>>   	landlock_initialized = true;
>>   	pr_info("Up and running.\n");
>>   	return 0;
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index c5a6ad4e2fca..7853f32e8325 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -29,6 +29,7 @@
>>   #include "cred.h"
>>   #include "fs.h"
>>   #include "limits.h"
>> +#include "net.h"
>>   #include "ruleset.h"
>>   #include "setup.h"
>> 
>> @@ -74,7 +75,8 @@ static void build_check_abi(void)
>>   {
>>   	struct landlock_ruleset_attr ruleset_attr;
>>   	struct landlock_path_beneath_attr path_beneath_attr;
>> -	size_t ruleset_size, path_beneath_size;
>> +	struct landlock_net_service_attr net_service_attr;
>> +	size_t ruleset_size, path_beneath_size, net_service_size;
>> 
>>   	/*
>>   	 * For each user space ABI structures, first checks that there is no
>> @@ -90,6 +92,11 @@ static void build_check_abi(void)
>>   	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
>>   	BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
>>   	BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
>> +
>> +	net_service_size = sizeof(net_service_attr.allowed_access);
>> +	net_service_size += sizeof(net_service_attr.port);
>> +	BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size);
>> +	BUILD_BUG_ON(sizeof(net_service_attr) != 10);
>>   }
>> 
>>   /* Ruleset handling */
>> @@ -322,13 +329,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>>   	return err;
>>   }
>> 
>> +static int add_rule_net_service(struct landlock_ruleset *ruleset,
>> +				const void __user *const rule_attr)
>> +{
>> +#if IS_ENABLED(CONFIG_INET)
>> +	struct landlock_net_service_attr net_service_attr;
>> +	int res;
>> +	u32 mask;
> 
> access_mask_t mask;

  Got it. Thanks.
> 
> 
>> +
>> +	/* Copies raw user space buffer, only one type for now. */
>> +	res = copy_from_user(&net_service_attr, rule_attr,
>> +			     sizeof(net_service_attr));
>> +	if (res)
>> +		return -EFAULT;
>> +
>> +	/*
>> +	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
>> +	 * are ignored by network actions.
>> +	 */
>> +	if (!net_service_attr.allowed_access)
>> +		return -ENOMSG;
>> +
>> +	/*
>> +	 * Checks that allowed_access matches the @ruleset constraints
>> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>> +	 */
>> +	mask = landlock_get_net_access_mask(ruleset, 0);
>> +	if ((net_service_attr.allowed_access | mask) != mask)
>> +		return -EINVAL;
>> +
>> +	/* Denies inserting a rule with port 0. */
>> +	if (net_service_attr.port == 0)
>> +		return -EINVAL;
>> +
>> +	/* Imports the new rule. */
>> +	return landlock_append_net_rule(ruleset, net_service_attr.port,
>> +					net_service_attr.allowed_access);
>> +#else /* IS_ENABLED(CONFIG_INET) */
>> +	return -EAFNOSUPPORT;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>> +}
>> +
>>   /**
>>    * sys_landlock_add_rule - Add a new rule to a ruleset
>>    *
>>    * @ruleset_fd: File descriptor tied to the ruleset that should be extended
>>    *		with the new rule.
>> - * @rule_type: Identify the structure type pointed to by @rule_attr (only
>> - *             %LANDLOCK_RULE_PATH_BENEATH for now).
>> + * @rule_type: Identify the structure type pointed to by @rule_attr:
>> + *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_SERVICE.
>>    * @rule_attr: Pointer to a rule (only of type &struct
>>    *             landlock_path_beneath_attr for now).
>>    * @flags: Must be 0.
>> @@ -339,6 +387,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>>    * Possible returned errors are:
>>    *
>>    * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
>> + * - %EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not
> 
> %LANDLOCK_RULE_NET_SERVICE

  Ok.
> 
> 
>> + *   supported by the running kernel;
>>    * - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
>>    *   &landlock_path_beneath_attr.allowed_access is not a subset of the
>>    *   ruleset handled accesses);
>> @@ -373,6 +423,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>>   	case LANDLOCK_RULE_PATH_BENEATH:
>>   		err = add_rule_path_beneath(ruleset, rule_attr);
>>   		break;
>> +	case LANDLOCK_RULE_NET_SERVICE:
>> +		err = add_rule_net_service(ruleset, rule_attr);
>> +		break;
>>   	default:
>>   		err = -EINVAL;
>>   		break;
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 01/12] landlock: Make ruleset's access masks more generic
  2022-11-28  2:53     ` Konstantin Meskhidze (A)
@ 2022-11-28 20:22       ` Mickaël Salaün
  2022-12-02  2:49         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:22 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 28/11/2022 03:53, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:41 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> To support network type rules, this modification renames ruleset's
>>> access masks and modifies it's type to access_masks_t. This patch
>>> adds filesystem helper functions to add and get filesystem mask.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v7:
>>> * Refactors commit message.
>>>
>>> Changes since v6:
>>> * Adds a new access_masks_t for struct ruleset.
>>> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>>>     because it OR values.
>>> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
>>> * Refactors landlock_get_fs_access_mask().
>>>
>>> Changes since v6:
>>> * Adds a new access_masks_t for struct ruleset.
>>> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>>>     because it OR values.
>>> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
>>> * Refactors landlock_get_fs_access_mask().
>>>
>>> Changes since v5:
>>> * Changes access_mask_t to u32.
>>> * Formats code with clang-format-14.
>>>
>>> Changes since v4:
>>> * Deletes struct landlock_access_mask.
>>>
>>> Changes since v3:
>>> * Splits commit.
>>> * Adds get_mask, set_mask helpers for filesystem.
>>> * Adds new struct landlock_access_mask.
>>>
>>> ---
>>>    security/landlock/fs.c       | 10 +++++-----
>>>    security/landlock/limits.h   |  1 +
>>>    security/landlock/ruleset.c  | 17 +++++++++--------
>>>    security/landlock/ruleset.h  | 35 +++++++++++++++++++++++++++++++----
>>>    security/landlock/syscalls.c |  7 ++++---
>>>    5 files changed, 50 insertions(+), 20 deletions(-)
>>>
>>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>>> index adcea0fe7e68..0d57c6479d29 100644
>>> --- a/security/landlock/fs.c
>>> +++ b/security/landlock/fs.c
>>> @@ -178,9 +178,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>>    		return -EINVAL;
>>>
>>>    	/* Transforms relative access rights to absolute ones. */
>>> -	access_rights |=
>>> -		LANDLOCK_MASK_ACCESS_FS &
>>> -		~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
>>> +	access_rights |= LANDLOCK_MASK_ACCESS_FS &
>>> +			 ~(landlock_get_fs_access_mask(ruleset, 0) |
>>> +			   ACCESS_INITIALLY_DENIED);
>>>    	object = get_inode_object(d_backing_inode(path->dentry));
>>>    	if (IS_ERR(object))
>>>    		return PTR_ERR(object);
>>> @@ -294,7 +294,7 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
>>>    	size_t layer_level;
>>>
>>>    	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
>>> -		access_dom |= domain->fs_access_masks[layer_level];
>>> +		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
>>>    	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>>
>> You can remove `& LANDLOCK_MASK_ACCESS_FS` here because it is now part
>> of landlock_get_fs_access_mask().
> 
>     Ok. I got it. Thanks.
>>
>>
>>>    }
>>>
>>> @@ -336,7 +336,7 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>>>    			 * access rights.
>>>    			 */
>>>    			if (BIT_ULL(access_bit) &
>>> -			    (domain->fs_access_masks[layer_level] |
>>> +			    (landlock_get_fs_access_mask(domain, layer_level) |
>>>    			     ACCESS_INITIALLY_DENIED)) {
>>>    				(*layer_masks)[access_bit] |=
>>>    					BIT_ULL(layer_level);
>>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>>> index 82288f0e9e5e..bafb3b8dc677 100644
>>> --- a/security/landlock/limits.h
>>> +++ b/security/landlock/limits.h
>>> @@ -21,6 +21,7 @@
>>>    #define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_TRUNCATE
>>>    #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
>>>    #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>> +#define LANDLOCK_SHIFT_ACCESS_FS	0
>>>
>>>    /* clang-format on */
>>>
>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>> index 996484f98bfd..1f3188b4e313 100644
>>> --- a/security/landlock/ruleset.c
>>> +++ b/security/landlock/ruleset.c
>>> @@ -29,7 +29,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>>    	struct landlock_ruleset *new_ruleset;
>>>
>>>    	new_ruleset =
>>> -		kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers),
>>> +		kzalloc(struct_size(new_ruleset, access_masks, num_layers),
>>>    			GFP_KERNEL_ACCOUNT);
>>>    	if (!new_ruleset)
>>>    		return ERR_PTR(-ENOMEM);
>>> @@ -40,7 +40,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>>    	/*
>>>    	 * hierarchy = NULL
>>>    	 * num_rules = 0
>>> -	 * fs_access_masks[] = 0
>>> +	 * access_masks[] = 0
>>>    	 */
>>>    	return new_ruleset;
>>>    }
>>> @@ -55,7 +55,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask)
>>>    		return ERR_PTR(-ENOMSG);
>>>    	new_ruleset = create_ruleset(1);
>>>    	if (!IS_ERR(new_ruleset))
>>> -		new_ruleset->fs_access_masks[0] = fs_access_mask;
>>> +		landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
>>>    	return new_ruleset;
>>>    }
>>>
>>> @@ -117,11 +117,12 @@ static void build_check_ruleset(void)
>>>    		.num_rules = ~0,
>>>    		.num_layers = ~0,
>>>    	};
>>> -	typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0;
>>> +	typeof(ruleset.access_masks[0]) access_masks = ~0;
>>>
>>>    	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>>>    	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>>> -	BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS);
>>> +	BUILD_BUG_ON(access_masks <
>>> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>>>    }
>>>
>>>    /**
>>> @@ -281,7 +282,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>>    		err = -EINVAL;
>>>    		goto out_unlock;
>>>    	}
>>> -	dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
>>> +	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
>>>
>>>    	/* Merges the @src tree. */
>>>    	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
>>> @@ -340,8 +341,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>>    		goto out_unlock;
>>>    	}
>>>    	/* Copies the parent layer stack and leaves a space for the new layer. */
>>> -	memcpy(child->fs_access_masks, parent->fs_access_masks,
>>> -	       flex_array_size(parent, fs_access_masks, parent->num_layers));
>>> +	memcpy(child->access_masks, parent->access_masks,
>>> +	       flex_array_size(parent, access_masks, parent->num_layers));
>>>
>>>    	if (WARN_ON_ONCE(!parent->hierarchy)) {
>>>    		err = -EINVAL;
>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>> index d43231b783e4..f2ad932d396c 100644
>>> --- a/security/landlock/ruleset.h
>>> +++ b/security/landlock/ruleset.h
>>> @@ -25,6 +25,11 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>>>    /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
>>>    static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>>>
>>> +/* Ruleset access masks. */
>>> +typedef u16 access_masks_t;
>>> +/* Makes sure all ruleset access rights can be stored. */
>>> +static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
>>> +
>>>    typedef u16 layer_mask_t;
>>>    /* Makes sure all layers can be checked. */
>>>    static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>>> @@ -110,7 +115,7 @@ struct landlock_ruleset {
>>>    		 * section.  This is only used by
>>>    		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>>>    		 * The fields @lock, @usage, @num_rules, @num_layers and
>>> -		 * @fs_access_masks are then unused.
>>> +		 * @access_masks are then unused.
>>>    		 */
>>>    		struct work_struct work_free;
>>>    		struct {
>>> @@ -137,7 +142,7 @@ struct landlock_ruleset {
>>>    			 */
>>>    			u32 num_layers;
>>>    			/**
>>> -			 * @fs_access_masks: Contains the subset of filesystem
>>> +			 * @access_masks: Contains the subset of filesystem
>>>    			 * actions that are restricted by a ruleset.  A domain
>>>    			 * saves all layers of merged rulesets in a stack
>>>    			 * (FAM), starting from the first layer to the last
>>> @@ -148,13 +153,13 @@ struct landlock_ruleset {
>>>    			 * layers are set once and never changed for the
>>>    			 * lifetime of the ruleset.
>>>    			 */
>>> -			access_mask_t fs_access_masks[];
>>> +			access_masks_t access_masks[];
>>>    		};
>>>    	};
>>>    };
>>>
>>>    struct landlock_ruleset *
>>> -landlock_create_ruleset(const access_mask_t fs_access_mask);
>>> +landlock_create_ruleset(const access_mask_t access_mask);
>>>
>>>    void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
>>>    void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
>>> @@ -177,4 +182,26 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>>>    		refcount_inc(&ruleset->usage);
>>>    }
>>>
>>> +static inline void
>>> +landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>>> +			    const access_mask_t fs_access_mask,
>>> +			    const u16 layer_level)
>>> +{
>>> +	access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
>>> +
>>> +	/* Should already be checked in sys_landlock_create_ruleset(). */
>>> +	WARN_ON_ONCE(fs_access_mask != fs_mask);
>>> +	// TODO: Add tests to check "|=" and not "="
>>
>> This todo should be done and removed. No more todos must remain.
>>
>     I delete it in 10/12 Patch when add network seltests.

You can delete it in this patch then. I'm reviewing the tests.

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-28  3:04         ` Konstantin Meskhidze (A)
@ 2022-11-28 20:23           ` Mickaël Salaün
  2022-12-02  2:50             ` Konstantin Meskhidze (A)
  2022-12-24  3:10             ` Konstantin Meskhidze (A)
  0 siblings, 2 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:23 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel


On 28/11/2022 04:04, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/18/2022 12:16 PM, Mickaël Salaün пишет:
>> Konstantin, this patch should apply cleanly just after "01/12 landlock:
>> Make ruleset's access masks more generic". You can easily get this patch
>> with https://git.kernel.org/pub/scm/utils/b4/b4.git/
>> Some adjustments are needed for the following patches. Feel free to
>> review this patch.
     Do you have this patch online? Can I fetch it from your repo?

You can cherry-pick from here: https://git.kernel.org/mic/c/439ea2d31e662

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-28  3:02       ` Konstantin Meskhidze (A)
@ 2022-11-28 20:25         ` Mickaël Salaün
  2022-12-02  2:51           ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:25 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel


On 28/11/2022 04:02, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:55 PM, Mickaël Salaün пишет:
>> Allow mount point and root directory changes when there is no filesystem
>> rule tied to the current Landlock domain.  This doesn't change anything
>> for now because a domain must have at least a (filesystem) rule, but
>> this will change when other rule types will come.  For instance, a
>> domain only restricting the network should have no impact on filesystem
>> restrictions.
>>
>> Add a new get_current_fs_domain() helper to quickly check filesystem
>> rule existence for all filesystem LSM hooks.
> 
>     Ok. I got it.
>     Do I need also to add a new network helper:
>     like landlock_get_raw_net_access_mask?

A get_raw helper would not be useful if there is not network access 
initially denied (like for FS_REFER).

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

* Re: [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks()
  2022-11-28  3:25     ` Konstantin Meskhidze (A)
@ 2022-11-28 20:25       ` Mickaël Salaün
  2022-12-02  2:52         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:25 UTC (permalink / raw)
  To: Konstantin Meskhidze (A),
	Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 28/11/2022 04:25, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:42 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> This patch moves unmask_layers() and init_layer_masks() helpers
>>> to ruleset.c to share with landlock network implementation in
>>
>> …to share them with the Landlock network implementation in
>>
>      Got it.
>>
>>> following commits.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---

[...]

>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>> index 608ab356bc3e..50baff4fcbb4 100644
>>> --- a/security/landlock/ruleset.h
>>> +++ b/security/landlock/ruleset.h
>>> @@ -34,6 +34,16 @@ typedef u16 layer_mask_t;
>>>    /* Makes sure all layers can be checked. */
>>>    static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>>>
>>> +/*
>>> + * All access rights that are denied by default whether they are handled or not
>>> + * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
>>> + * entries when we need to get the absolute handled access masks.
>>> + */
>>> +/* clang-format off */
>>> +#define ACCESS_INITIALLY_DENIED ( \
>>> +	LANDLOCK_ACCESS_FS_REFER)
>>> +/* clang-format on */
>>
>> This ACCESS_INITIALLY_DENIED definition must be moved, not copied. You
>> can rename ACCESS_INITIALLY_DENIED to ACCESS_FS_INITIALLY_DENIED and
>> move this hunk before the access_mask_t definition.
>>
>     Yep. Will be fixed.
>>
>>> +
>>>    /**
>>>     * struct landlock_layer - Access rights for a given layer
>>>     */
>>> @@ -246,4 +256,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>>>    		LANDLOCK_SHIFT_ACCESS_FS) &
>>>    	       LANDLOCK_MASK_ACCESS_FS;
>>>    }
>>> +
>>> +bool unmask_layers(const struct landlock_rule *const rule,
>>
>> All public Landlock helpers must be prefixed with "landlock_"
> 
>     Do you mean ones which are shared between fs and net parts?

All helpers that ends up in the exported ELF symbols, so all implemented 
in the .c files with their signature defined in .h files. The static 
inlined .h helpers don't need to have such prefix if there is no conflict.


>>
>>> +		   const access_mask_t access_request,
>>> +		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>>> +
>>> +access_mask_t
>>> +init_layer_masks(const struct landlock_ruleset *const domain,
>>> +		 const access_mask_t access_request,
>>> +		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>>
>> There is a warning generated by checkpatch.pl about this line:
>>      WARNING: function definition argument 'layer_mask_t' should also have
>> an identifier name
>>
>> I think this is a bug in checkpatch.pl
>>
>      I got this warn, but cant get rid of it.
>      Also think its a bug in checkpatck.pl

Please ignore it for now. It would be nice to have a checkpatch.pl fix 
though.

> 
>> Any though Andy, Joe, Dwaipayan or Lukas?

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2022-11-28  4:01     ` Konstantin Meskhidze (A)
@ 2022-11-28 20:26       ` Mickaël Salaün
  2022-12-02  2:54         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:26 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)


On 28/11/2022 05:01, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> This commit adds network rules support in internal landlock functions
>>> (presented in ruleset.c) and landlock_create_ruleset syscall.
>>
>> …in the ruleset management helpers and the landlock_create_ruleset syscall.
>>
>>
>>> Refactors user space API to support network actions. Adds new network
>>
>> Refactor…
>>
>>> access flags, network rule and network attributes. Increments Landlock
>>
>> Increment…
> 
>     The commit's message will be fixed. Thank you!
>>
>>> ABI version.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v7:
>>> * Squashes commits.
>>> * Increments ABI version to 4.
>>> * Refactors commit message.
>>> * Minor fixes.
>>>
>>> Changes since v6:
>>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>>     because it OR values.
>>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>>> * Refactors landlock_get_net_access_mask().
>>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>>     LANDLOCK_NUM_ACCESS_FS as value.
>>> * Updates access_masks_t to u32 to support network access actions.
>>> * Refactors landlock internal functions to support network actions with
>>>     landlock_key/key_type/id types.
>>>
>>> Changes since v5:
>>> * Gets rid of partial revert from landlock_add_rule
>>> syscall.
>>> * Formats code with clang-format-14.
>>>
>>> Changes since v4:
>>> * Refactors landlock_create_ruleset() - splits ruleset and
>>> masks checks.
>>> * Refactors landlock_create_ruleset() and landlock mask
>>> setters/getters to support two rule types.
>>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>>> function by factoring out get_ruleset_from_fd() and
>>> landlock_put_ruleset().
>>>
>>> Changes since v3:
>>> * Splits commit.
>>> * Adds network rule support for internal landlock functions.
>>> * Adds set_mask and get_mask for network.
>>> * Adds rb_root root_net_port.
>>>
>>> ---
>>>    include/uapi/linux/landlock.h                | 49 ++++++++++++++
>>>    security/landlock/limits.h                   |  6 +-
>>>    security/landlock/ruleset.c                  | 55 ++++++++++++++--
>>>    security/landlock/ruleset.h                  | 68 ++++++++++++++++----
>>>    security/landlock/syscalls.c                 | 13 +++-
>>>    tools/testing/selftests/landlock/base_test.c |  2 +-
>>>    6 files changed, 170 insertions(+), 23 deletions(-)
>>>
>>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>>> index f3223f964691..096b683c6ff3 100644
>>> --- a/include/uapi/linux/landlock.h
>>> +++ b/include/uapi/linux/landlock.h
>>> @@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
>>>    	 * this access right.
>>>    	 */
>>>    	__u64 handled_access_fs;
>>> +
>>> +	/**
>>> +	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
>>> +	 * that is handled by this ruleset and should then be forbidden if no
>>> +	 * rule explicitly allow them.
>>> +	 */
>>> +	__u64 handled_access_net;
>>>    };
>>>
>>>    /*
>>> @@ -54,6 +61,11 @@ enum landlock_rule_type {
>>>    	 * landlock_path_beneath_attr .
>>>    	 */
>>>    	LANDLOCK_RULE_PATH_BENEATH = 1,
>>> +	/**
>>> +	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
>>> +	 * landlock_net_service_attr .
>>> +	 */
>>> +	LANDLOCK_RULE_NET_SERVICE = 2,
>>>    };
>>>
>>>    /**
>>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>>    	 */
>>>    } __attribute__((packed));
>>>
>>> +/**
>>> + * struct landlock_net_service_attr - TCP subnet definition
>>> + *
>>> + * Argument of sys_landlock_add_rule().
>>> + */
>>> +struct landlock_net_service_attr {
>>> +	/**
>>> +	 * @allowed_access: Bitmask of allowed access network for services
>>> +	 * (cf. `Network flags`_).
>>> +	 */
>>> +	__u64 allowed_access;
>>> +	/**
>>> +	 * @port: Network port.
>>> +	 */
>>> +	__u16 port;
>>
>>    From an UAPI point of view, I think the port field should be __be16, as
>> for sockaddr_in->port and other network-related APIs. This will require
>> some kernel changes to please sparse: make C=2 security/landlock/ must
>> not print any warning.
> 
>     Is sparse a default checker?

You should be able to easily install it with your Linux distro.

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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2022-11-28  2:49     ` Konstantin Meskhidze (A)
@ 2022-11-28 20:26       ` Mickaël Salaün
  2022-12-02  2:48         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:26 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 28/11/2022 03:49, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/16/2022 5:25 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> This commit adds network demo. It's possible to allow a sandboxer to
>>> bind/connect to a list of particular ports restricting network
>>> actions to the rest of ports.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---

[...]

>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
>>> +	}
>>> +	/* Removes connect access attribute if not supported by a user. */
>>> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
>>> +	if (!env_port_name) {
>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>>> +	}
>>> +	ruleset_attr.handled_access_net &= access_net_tcp;
>>
>> There is no need for access_net_tcp.
> 
>     Do you mean to delete this var?

Yes

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

* Re: [PATCH v8 12/12] landlock: Document Landlock's network support
  2022-11-28  6:44     ` Konstantin Meskhidze (A)
@ 2022-11-28 20:26       ` Mickaël Salaün
  2022-12-02  3:14         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 20:26 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-doc


On 28/11/2022 07:44, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:44 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> Describes network access rules for TCP sockets. Adds network access
>>> example in the tutorial. Points out AF_UNSPEC socket family behaviour.
>>> Adds kernel configuration support for network.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v7:
>>> * Fixes documentaion logic errors and typos as Mickaёl suggested:
>>> https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/
>>>
>>> Changes since v6:
>>> * Adds network support documentaion.
>>>
>>> ---
>>>    Documentation/userspace-api/landlock.rst | 72 +++++++++++++++++++-----
>>>    1 file changed, 59 insertions(+), 13 deletions(-)
>>>
>>> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
>>> index d8cd8cd9ce25..d0610ec9ce05 100644
>>> --- a/Documentation/userspace-api/landlock.rst
>>> +++ b/Documentation/userspace-api/landlock.rst
>>> @@ -11,10 +11,10 @@ Landlock: unprivileged access control
>>>    :Date: October 2022
>>>
>>>    The goal of Landlock is to enable to restrict ambient rights (e.g. global
>>> -filesystem access) for a set of processes.  Because Landlock is a stackable
>>> -LSM, it makes possible to create safe security sandboxes as new security layers
>>> -in addition to the existing system-wide access-controls. This kind of sandbox
>>> -is expected to help mitigate the security impact of bugs or
>>> +filesystem or network access) for a set of processes.  Because Landlock
>>> +is a stackable LSM, it makes possible to create safe security sandboxes as new
>>> +security layers in addition to the existing system-wide access-controls. This
>>> +kind of sandbox is expected to help mitigate the security impact of bugs or
>>>    unexpected/malicious behaviors in user space applications.  Landlock empowers
>>>    any process, including unprivileged ones, to securely restrict themselves.
>>>
>>> @@ -30,18 +30,20 @@ Landlock rules
>>>
>>>    A Landlock rule describes an action on an object.  An object is currently a
>>>    file hierarchy, and the related filesystem actions are defined with `access
>>> -rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
>>> -the thread enforcing it, and its future children.
>>> +rights`_.  Since ABI version 4 a port data appears with related network actions
>>> +for TCP socket families.  A set of rules is aggregated in a ruleset, which
>>> +can then restrict the thread enforcing it, and its future children.
>>>
>>>    Defining and enforcing a security policy
>>>    ----------------------------------------
>>>
>>>    We first need to define the ruleset that will contain our rules.  For this
>>>    example, the ruleset will contain rules that only allow read actions, but write
>>> -actions will be denied.  The ruleset then needs to handle both of these kind of
>>> +actions will be denied. The ruleset then needs to handle both of these kind of
>>>    actions.  This is required for backward and forward compatibility (i.e. the
>>>    kernel and user space may not know each other's supported restrictions), hence
>>> -the need to be explicit about the denied-by-default access rights.
>>> +the need to be explicit about the denied-by-default access rights.  Also ruleset
>>> +will have network rules for specific ports, so it should handle network actions.
>>>
>>>    .. code-block:: c
>>>
>>> @@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
>>>                LANDLOCK_ACCESS_FS_MAKE_SYM |
>>>                LANDLOCK_ACCESS_FS_REFER |
>>>                LANDLOCK_ACCESS_FS_TRUNCATE,
>>> +        .handled_access_net =
>>> +            LANDLOCK_ACCESS_NET_BIND_TCP |
>>> +            LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>        };
>>>
>>>    Because we may not know on which kernel version an application will be
>>> @@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
>>>    using.  To avoid binary enforcement (i.e. either all security features or
>>>    none), we can leverage a dedicated Landlock command to get the current version
>>>    of the Landlock ABI and adapt the handled accesses.  Let's check if we should
>>> -remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
>>> -access rights, which are only supported starting with the second and third
>>> -version of the ABI.
>>> +remove the `LANDLOCK_ACCESS_FS_REFER` or `LANDLOCK_ACCESS_FS_TRUNCATE` or
>>> +network access rights, which are only supported starting with the second,
>>
>> This is a bad rebase.
> 
>     Sorry. Did not get it.

This hunk (and maybe others) changes unrelated things (e.g. back quotes).


>>
>>
>>> +third and fourth version of the ABI.
>>>
>>>    .. code-block:: c
>>>
>>>        int abi;
>>>
>>> +    #define ACCESS_NET_BIND_CONNECT ( \
>>> +    LANDLOCK_ACCESS_NET_BIND_TCP | \
>>> +    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>
>> Please add a 4-spaces prefix for these two lines.
> 
>     Like this??
> 	#define ACCESS_NET_BIND_CONNECT ( \
>               LANDLOCK_ACCESS_NET_BIND_TCP | \
>               LANDLOCK_ACCESS_NET_CONNECT_TCP)

Like for other indentations in the documentation (e.g. ruleset_attr 
definition):

#define ACCESS_NET_BIND_CONNECT ( \
     LANDLOCK_ACCESS_NET_BIND_TCP | \
     LANDLOCK_ACCESS_NET_CONNECT_TCP)

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-11-28  8:21     ` Konstantin Meskhidze (A)
@ 2022-11-28 21:00       ` Mickaël Salaün
  2022-12-02  3:13         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-11-28 21:00 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-api,
	Alejandro Colomar (man-pages)

The previous commit provides an interface to theoretically restrict 
network access (i.e. ruleset handled network accesses), but in fact this 
is not enforced until this commit. I like this split but to avoid any 
inconsistency, please squash this commit into the previous one: "7/12 
landlock: Add network rules support"
You should keep all the commit messages but maybe tweak them a bit.


On 28/11/2022 09:21, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>> It's possible to restrict binding and connecting of TCP sockets to
>>> particular ports.
>>
>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>> restrict TCP socket binding and connection to specific ports.
>>
>     Ok. Thanks.
>>
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---

[...]

>>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>>> +			       int addrlen)
>>> +{
>>> +	const struct landlock_ruleset *const dom =
>>> +		landlock_get_current_domain();
>>> +
>>> +	if (!dom)
>>> +		return 0;
>>> +
>>> +	/* Check if it's a TCP socket. */
>>> +	if (sock->type != SOCK_STREAM)
>>> +		return 0;
>>> +
>>> +	/* Check if the hook is AF_INET* socket's action. */
>>> +	switch (address->sa_family) {
>>> +	case AF_INET:
>>> +#if IS_ENABLED(CONFIG_IPV6)
>>> +	case AF_INET6:
>>> +#endif
>>> +		return check_socket_access(dom, get_port(address),
>>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>> +	case AF_UNSPEC: {
>>> +		u16 i;
>>
>> You can move "i" after the "dom" declaration to remove the extra braces.
>>
>     Ok. Thanks.
>>
>>> +
>>> +		/*
>>> +		 * If just in a layer a mask supports connect access,
>>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>>> +		 * must be banned. This prevents from disconnecting already
>>> +		 * connected sockets.
>>> +		 */
>>> +		for (i = 0; i < dom->num_layers; i++) {
>>> +			if (landlock_get_net_access_mask(dom, i) &
>>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>> +				return -EACCES;
>>
>> I'm wondering if this is the right error code for this case. EPERM may
>> be more appropriate.
> 
>     Ok. Will be refactored.
>>
>> Thinking more about this case, I don't understand what is the rationale
>> to deny such action. What would be the consequence to always allow
>> connection with AF_UNSPEC (i.e. to disconnect a socket)?
>>
>     I thought we have come to a conclusion about connect(...AF_UNSPEC..)
>    behaviour in the patchset V3:
> https://lore.kernel.org/linux-security-module/19ad3a01-d76e-0e73-7833-99acd4afd97e@huawei.com/

The conclusion was that AF_UNSPEC disconnects a socket, but I'm asking 
if this is a security issue. I don't think it is more dangerous than a 
new (unconnected) socket. Am I missing something? Which kind of rule 
could be bypassed? What are we protecting against by restricting AF_UNSPEC?

We could then reduce the hook codes to just:
return current_check_access_socket(sock, address, LANDLOCK_ACCESS_NET_*);

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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2022-11-28 20:26       ` Mickaël Salaün
@ 2022-12-02  2:48         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  2:48 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/28/2022 11:26 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 03:49, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/16/2022 5:25 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> This commit adds network demo. It's possible to allow a sandboxer to
>>>> bind/connect to a list of particular ports restricting network
>>>> actions to the rest of ports.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
> 
> [...]
> 
>>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
>>>> +	}
>>>> +	/* Removes connect access attribute if not supported by a user. */
>>>> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
>>>> +	if (!env_port_name) {
>>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>>>> +	}
>>>> +	ruleset_attr.handled_access_net &= access_net_tcp;
>>>
>>> There is no need for access_net_tcp.
>> 
>>     Do you mean to delete this var?
> 
> Yes

   Got it.
> .

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

* Re: [PATCH v8 01/12] landlock: Make ruleset's access masks more generic
  2022-11-28 20:22       ` Mickaël Salaün
@ 2022-12-02  2:49         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  2:49 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/28/2022 11:22 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 03:53, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:41 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> To support network type rules, this modification renames ruleset's
>>>> access masks and modifies it's type to access_masks_t. This patch
>>>> adds filesystem helper functions to add and get filesystem mask.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v7:
>>>> * Refactors commit message.
>>>>
>>>> Changes since v6:
>>>> * Adds a new access_masks_t for struct ruleset.
>>>> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>>>>     because it OR values.
>>>> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
>>>> * Refactors landlock_get_fs_access_mask().
>>>>
>>>> Changes since v6:
>>>> * Adds a new access_masks_t for struct ruleset.
>>>> * Renames landlock_set_fs_access_mask() to landlock_add_fs_access_mask()
>>>>     because it OR values.
>>>> * Makes landlock_add_fs_access_mask() more resilient incorrect values.
>>>> * Refactors landlock_get_fs_access_mask().
>>>>
>>>> Changes since v5:
>>>> * Changes access_mask_t to u32.
>>>> * Formats code with clang-format-14.
>>>>
>>>> Changes since v4:
>>>> * Deletes struct landlock_access_mask.
>>>>
>>>> Changes since v3:
>>>> * Splits commit.
>>>> * Adds get_mask, set_mask helpers for filesystem.
>>>> * Adds new struct landlock_access_mask.
>>>>
>>>> ---
>>>>    security/landlock/fs.c       | 10 +++++-----
>>>>    security/landlock/limits.h   |  1 +
>>>>    security/landlock/ruleset.c  | 17 +++++++++--------
>>>>    security/landlock/ruleset.h  | 35 +++++++++++++++++++++++++++++++----
>>>>    security/landlock/syscalls.c |  7 ++++---
>>>>    5 files changed, 50 insertions(+), 20 deletions(-)
>>>>
>>>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>>>> index adcea0fe7e68..0d57c6479d29 100644
>>>> --- a/security/landlock/fs.c
>>>> +++ b/security/landlock/fs.c
>>>> @@ -178,9 +178,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>>>    		return -EINVAL;
>>>>
>>>>    	/* Transforms relative access rights to absolute ones. */
>>>> -	access_rights |=
>>>> -		LANDLOCK_MASK_ACCESS_FS &
>>>> -		~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
>>>> +	access_rights |= LANDLOCK_MASK_ACCESS_FS &
>>>> +			 ~(landlock_get_fs_access_mask(ruleset, 0) |
>>>> +			   ACCESS_INITIALLY_DENIED);
>>>>    	object = get_inode_object(d_backing_inode(path->dentry));
>>>>    	if (IS_ERR(object))
>>>>    		return PTR_ERR(object);
>>>> @@ -294,7 +294,7 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
>>>>    	size_t layer_level;
>>>>
>>>>    	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
>>>> -		access_dom |= domain->fs_access_masks[layer_level];
>>>> +		access_dom |= landlock_get_fs_access_mask(domain, layer_level);
>>>>    	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>>>
>>> You can remove `& LANDLOCK_MASK_ACCESS_FS` here because it is now part
>>> of landlock_get_fs_access_mask().
>> 
>>     Ok. I got it. Thanks.
>>>
>>>
>>>>    }
>>>>
>>>> @@ -336,7 +336,7 @@ init_layer_masks(const struct landlock_ruleset *const domain,
>>>>    			 * access rights.
>>>>    			 */
>>>>    			if (BIT_ULL(access_bit) &
>>>> -			    (domain->fs_access_masks[layer_level] |
>>>> +			    (landlock_get_fs_access_mask(domain, layer_level) |
>>>>    			     ACCESS_INITIALLY_DENIED)) {
>>>>    				(*layer_masks)[access_bit] |=
>>>>    					BIT_ULL(layer_level);
>>>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>>>> index 82288f0e9e5e..bafb3b8dc677 100644
>>>> --- a/security/landlock/limits.h
>>>> +++ b/security/landlock/limits.h
>>>> @@ -21,6 +21,7 @@
>>>>    #define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_TRUNCATE
>>>>    #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
>>>>    #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>>> +#define LANDLOCK_SHIFT_ACCESS_FS	0
>>>>
>>>>    /* clang-format on */
>>>>
>>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>>> index 996484f98bfd..1f3188b4e313 100644
>>>> --- a/security/landlock/ruleset.c
>>>> +++ b/security/landlock/ruleset.c
>>>> @@ -29,7 +29,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>>>    	struct landlock_ruleset *new_ruleset;
>>>>
>>>>    	new_ruleset =
>>>> -		kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers),
>>>> +		kzalloc(struct_size(new_ruleset, access_masks, num_layers),
>>>>    			GFP_KERNEL_ACCOUNT);
>>>>    	if (!new_ruleset)
>>>>    		return ERR_PTR(-ENOMEM);
>>>> @@ -40,7 +40,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>>>    	/*
>>>>    	 * hierarchy = NULL
>>>>    	 * num_rules = 0
>>>> -	 * fs_access_masks[] = 0
>>>> +	 * access_masks[] = 0
>>>>    	 */
>>>>    	return new_ruleset;
>>>>    }
>>>> @@ -55,7 +55,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask)
>>>>    		return ERR_PTR(-ENOMSG);
>>>>    	new_ruleset = create_ruleset(1);
>>>>    	if (!IS_ERR(new_ruleset))
>>>> -		new_ruleset->fs_access_masks[0] = fs_access_mask;
>>>> +		landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
>>>>    	return new_ruleset;
>>>>    }
>>>>
>>>> @@ -117,11 +117,12 @@ static void build_check_ruleset(void)
>>>>    		.num_rules = ~0,
>>>>    		.num_layers = ~0,
>>>>    	};
>>>> -	typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0;
>>>> +	typeof(ruleset.access_masks[0]) access_masks = ~0;
>>>>
>>>>    	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>>>>    	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>>>> -	BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS);
>>>> +	BUILD_BUG_ON(access_masks <
>>>> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>>>>    }
>>>>
>>>>    /**
>>>> @@ -281,7 +282,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>>>    		err = -EINVAL;
>>>>    		goto out_unlock;
>>>>    	}
>>>> -	dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
>>>> +	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
>>>>
>>>>    	/* Merges the @src tree. */
>>>>    	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
>>>> @@ -340,8 +341,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>>>    		goto out_unlock;
>>>>    	}
>>>>    	/* Copies the parent layer stack and leaves a space for the new layer. */
>>>> -	memcpy(child->fs_access_masks, parent->fs_access_masks,
>>>> -	       flex_array_size(parent, fs_access_masks, parent->num_layers));
>>>> +	memcpy(child->access_masks, parent->access_masks,
>>>> +	       flex_array_size(parent, access_masks, parent->num_layers));
>>>>
>>>>    	if (WARN_ON_ONCE(!parent->hierarchy)) {
>>>>    		err = -EINVAL;
>>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>>> index d43231b783e4..f2ad932d396c 100644
>>>> --- a/security/landlock/ruleset.h
>>>> +++ b/security/landlock/ruleset.h
>>>> @@ -25,6 +25,11 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>>>>    /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
>>>>    static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>>>>
>>>> +/* Ruleset access masks. */
>>>> +typedef u16 access_masks_t;
>>>> +/* Makes sure all ruleset access rights can be stored. */
>>>> +static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
>>>> +
>>>>    typedef u16 layer_mask_t;
>>>>    /* Makes sure all layers can be checked. */
>>>>    static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>>>> @@ -110,7 +115,7 @@ struct landlock_ruleset {
>>>>    		 * section.  This is only used by
>>>>    		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>>>>    		 * The fields @lock, @usage, @num_rules, @num_layers and
>>>> -		 * @fs_access_masks are then unused.
>>>> +		 * @access_masks are then unused.
>>>>    		 */
>>>>    		struct work_struct work_free;
>>>>    		struct {
>>>> @@ -137,7 +142,7 @@ struct landlock_ruleset {
>>>>    			 */
>>>>    			u32 num_layers;
>>>>    			/**
>>>> -			 * @fs_access_masks: Contains the subset of filesystem
>>>> +			 * @access_masks: Contains the subset of filesystem
>>>>    			 * actions that are restricted by a ruleset.  A domain
>>>>    			 * saves all layers of merged rulesets in a stack
>>>>    			 * (FAM), starting from the first layer to the last
>>>> @@ -148,13 +153,13 @@ struct landlock_ruleset {
>>>>    			 * layers are set once and never changed for the
>>>>    			 * lifetime of the ruleset.
>>>>    			 */
>>>> -			access_mask_t fs_access_masks[];
>>>> +			access_masks_t access_masks[];
>>>>    		};
>>>>    	};
>>>>    };
>>>>
>>>>    struct landlock_ruleset *
>>>> -landlock_create_ruleset(const access_mask_t fs_access_mask);
>>>> +landlock_create_ruleset(const access_mask_t access_mask);
>>>>
>>>>    void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
>>>>    void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
>>>> @@ -177,4 +182,26 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>>>>    		refcount_inc(&ruleset->usage);
>>>>    }
>>>>
>>>> +static inline void
>>>> +landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>>>> +			    const access_mask_t fs_access_mask,
>>>> +			    const u16 layer_level)
>>>> +{
>>>> +	access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
>>>> +
>>>> +	/* Should already be checked in sys_landlock_create_ruleset(). */
>>>> +	WARN_ON_ONCE(fs_access_mask != fs_mask);
>>>> +	// TODO: Add tests to check "|=" and not "="
>>>
>>> This todo should be done and removed. No more todos must remain.
>>>
>>     I delete it in 10/12 Patch when add network seltests.
> 
> You can delete it in this patch then. I'm reviewing the tests.

   Ok. Thanks.
> .

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-28 20:23           ` Mickaël Salaün
@ 2022-12-02  2:50             ` Konstantin Meskhidze (A)
  2022-12-24  3:10             ` Konstantin Meskhidze (A)
  1 sibling, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  2:50 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



11/28/2022 11:23 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 04:04, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/18/2022 12:16 PM, Mickaël Salaün пишет:
>>> Konstantin, this patch should apply cleanly just after "01/12 landlock:
>>> Make ruleset's access masks more generic". You can easily get this patch
>>> with https://git.kernel.org/pub/scm/utils/b4/b4.git/
>>> Some adjustments are needed for the following patches. Feel free to
>>> review this patch.
>       Do you have this patch online? Can I fetch it from your repo?
> 
> You can cherry-pick from here: https://git.kernel.org/mic/c/439ea2d31e662

   Thank you.
> .

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-28 20:25         ` Mickaël Salaün
@ 2022-12-02  2:51           ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  2:51 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



11/28/2022 11:25 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 04:02, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:55 PM, Mickaël Salaün пишет:
>>> Allow mount point and root directory changes when there is no filesystem
>>> rule tied to the current Landlock domain.  This doesn't change anything
>>> for now because a domain must have at least a (filesystem) rule, but
>>> this will change when other rule types will come.  For instance, a
>>> domain only restricting the network should have no impact on filesystem
>>> restrictions.
>>>
>>> Add a new get_current_fs_domain() helper to quickly check filesystem
>>> rule existence for all filesystem LSM hooks.
>> 
>>     Ok. I got it.
>>     Do I need also to add a new network helper:
>>     like landlock_get_raw_net_access_mask?
> 
> A get_raw helper would not be useful if there is not network access
> initially denied (like for FS_REFER).

  Ok.
> .

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

* Re: [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks()
  2022-11-28 20:25       ` Mickaël Salaün
@ 2022-12-02  2:52         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  2:52 UTC (permalink / raw)
  To: Mickaël Salaün, Andy Whitcroft, Joe Perches,
	Dwaipayan Ray, Lukas Bulwahn
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/28/2022 11:25 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 04:25, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:42 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> This patch moves unmask_layers() and init_layer_masks() helpers
>>>> to ruleset.c to share with landlock network implementation in
>>>
>>> …to share them with the Landlock network implementation in
>>>
>>      Got it.
>>>
>>>> following commits.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
> 
> [...]
> 
>>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>>> index 608ab356bc3e..50baff4fcbb4 100644
>>>> --- a/security/landlock/ruleset.h
>>>> +++ b/security/landlock/ruleset.h
>>>> @@ -34,6 +34,16 @@ typedef u16 layer_mask_t;
>>>>    /* Makes sure all layers can be checked. */
>>>>    static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
>>>>
>>>> +/*
>>>> + * All access rights that are denied by default whether they are handled or not
>>>> + * by a ruleset/layer.  This must be ORed with all ruleset->fs_access_masks[]
>>>> + * entries when we need to get the absolute handled access masks.
>>>> + */
>>>> +/* clang-format off */
>>>> +#define ACCESS_INITIALLY_DENIED ( \
>>>> +	LANDLOCK_ACCESS_FS_REFER)
>>>> +/* clang-format on */
>>>
>>> This ACCESS_INITIALLY_DENIED definition must be moved, not copied. You
>>> can rename ACCESS_INITIALLY_DENIED to ACCESS_FS_INITIALLY_DENIED and
>>> move this hunk before the access_mask_t definition.
>>>
>>     Yep. Will be fixed.
>>>
>>>> +
>>>>    /**
>>>>     * struct landlock_layer - Access rights for a given layer
>>>>     */
>>>> @@ -246,4 +256,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>>>>    		LANDLOCK_SHIFT_ACCESS_FS) &
>>>>    	       LANDLOCK_MASK_ACCESS_FS;
>>>>    }
>>>> +
>>>> +bool unmask_layers(const struct landlock_rule *const rule,
>>>
>>> All public Landlock helpers must be prefixed with "landlock_"
>> 
>>     Do you mean ones which are shared between fs and net parts?
> 
> All helpers that ends up in the exported ELF symbols, so all implemented
> in the .c files with their signature defined in .h files. The static
> inlined .h helpers don't need to have such prefix if there is no conflict.

   Got it. Thanks.
> 
> 
>>>
>>>> +		   const access_mask_t access_request,
>>>> +		   layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>>>> +
>>>> +access_mask_t
>>>> +init_layer_masks(const struct landlock_ruleset *const domain,
>>>> +		 const access_mask_t access_request,
>>>> +		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>>>
>>> There is a warning generated by checkpatch.pl about this line:
>>>      WARNING: function definition argument 'layer_mask_t' should also have
>>> an identifier name
>>>
>>> I think this is a bug in checkpatch.pl
>>>
>>      I got this warn, but cant get rid of it.
>>      Also think its a bug in checkpatck.pl
> 
> Please ignore it for now. It would be nice to have a checkpatch.pl fix
> though.
> 
   Ok.
>> 
>>> Any though Andy, Joe, Dwaipayan or Lukas?
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2022-11-28 20:26       ` Mickaël Salaün
@ 2022-12-02  2:54         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  2:54 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)



11/28/2022 11:26 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 05:01, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> This commit adds network rules support in internal landlock functions
>>>> (presented in ruleset.c) and landlock_create_ruleset syscall.
>>>
>>> …in the ruleset management helpers and the landlock_create_ruleset syscall.
>>>
>>>
>>>> Refactors user space API to support network actions. Adds new network
>>>
>>> Refactor…
>>>
>>>> access flags, network rule and network attributes. Increments Landlock
>>>
>>> Increment…
>> 
>>     The commit's message will be fixed. Thank you!
>>>
>>>> ABI version.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v7:
>>>> * Squashes commits.
>>>> * Increments ABI version to 4.
>>>> * Refactors commit message.
>>>> * Minor fixes.
>>>>
>>>> Changes since v6:
>>>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>>>     because it OR values.
>>>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>>>> * Refactors landlock_get_net_access_mask().
>>>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>>>     LANDLOCK_NUM_ACCESS_FS as value.
>>>> * Updates access_masks_t to u32 to support network access actions.
>>>> * Refactors landlock internal functions to support network actions with
>>>>     landlock_key/key_type/id types.
>>>>
>>>> Changes since v5:
>>>> * Gets rid of partial revert from landlock_add_rule
>>>> syscall.
>>>> * Formats code with clang-format-14.
>>>>
>>>> Changes since v4:
>>>> * Refactors landlock_create_ruleset() - splits ruleset and
>>>> masks checks.
>>>> * Refactors landlock_create_ruleset() and landlock mask
>>>> setters/getters to support two rule types.
>>>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>>>> function by factoring out get_ruleset_from_fd() and
>>>> landlock_put_ruleset().
>>>>
>>>> Changes since v3:
>>>> * Splits commit.
>>>> * Adds network rule support for internal landlock functions.
>>>> * Adds set_mask and get_mask for network.
>>>> * Adds rb_root root_net_port.
>>>>
>>>> ---
>>>>    include/uapi/linux/landlock.h                | 49 ++++++++++++++
>>>>    security/landlock/limits.h                   |  6 +-
>>>>    security/landlock/ruleset.c                  | 55 ++++++++++++++--
>>>>    security/landlock/ruleset.h                  | 68 ++++++++++++++++----
>>>>    security/landlock/syscalls.c                 | 13 +++-
>>>>    tools/testing/selftests/landlock/base_test.c |  2 +-
>>>>    6 files changed, 170 insertions(+), 23 deletions(-)
>>>>
>>>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>>>> index f3223f964691..096b683c6ff3 100644
>>>> --- a/include/uapi/linux/landlock.h
>>>> +++ b/include/uapi/linux/landlock.h
>>>> @@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
>>>>    	 * this access right.
>>>>    	 */
>>>>    	__u64 handled_access_fs;
>>>> +
>>>> +	/**
>>>> +	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
>>>> +	 * that is handled by this ruleset and should then be forbidden if no
>>>> +	 * rule explicitly allow them.
>>>> +	 */
>>>> +	__u64 handled_access_net;
>>>>    };
>>>>
>>>>    /*
>>>> @@ -54,6 +61,11 @@ enum landlock_rule_type {
>>>>    	 * landlock_path_beneath_attr .
>>>>    	 */
>>>>    	LANDLOCK_RULE_PATH_BENEATH = 1,
>>>> +	/**
>>>> +	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
>>>> +	 * landlock_net_service_attr .
>>>> +	 */
>>>> +	LANDLOCK_RULE_NET_SERVICE = 2,
>>>>    };
>>>>
>>>>    /**
>>>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>>>    	 */
>>>>    } __attribute__((packed));
>>>>
>>>> +/**
>>>> + * struct landlock_net_service_attr - TCP subnet definition
>>>> + *
>>>> + * Argument of sys_landlock_add_rule().
>>>> + */
>>>> +struct landlock_net_service_attr {
>>>> +	/**
>>>> +	 * @allowed_access: Bitmask of allowed access network for services
>>>> +	 * (cf. `Network flags`_).
>>>> +	 */
>>>> +	__u64 allowed_access;
>>>> +	/**
>>>> +	 * @port: Network port.
>>>> +	 */
>>>> +	__u16 port;
>>>
>>>    From an UAPI point of view, I think the port field should be __be16, as
>>> for sockaddr_in->port and other network-related APIs. This will require
>>> some kernel changes to please sparse: make C=2 security/landlock/ must
>>> not print any warning.
>> 
>>     Is sparse a default checker?
> 
> You should be able to easily install it with your Linux distro.

  Ok. Thank you.
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-11-28 21:00       ` Mickaël Salaün
@ 2022-12-02  3:13         ` Konstantin Meskhidze (A)
  2022-12-02 13:01           ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  3:13 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-api,
	Alejandro Colomar (man-pages)



11/29/2022 12:00 AM, Mickaël Salaün пишет:
> The previous commit provides an interface to theoretically restrict
> network access (i.e. ruleset handled network accesses), but in fact this
> is not enforced until this commit. I like this split but to avoid any
> inconsistency, please squash this commit into the previous one: "7/12
> landlock: Add network rules support"
> You should keep all the commit messages but maybe tweak them a bit.
> 
   Ok. Will be squashed.
> 
> On 28/11/2022 09:21, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>> particular ports.
>>>
>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>> restrict TCP socket binding and connection to specific ports.
>>>
>>     Ok. Thanks.
>>>
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
> 
> [...]
> 
>>>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>>>> +			       int addrlen)
>>>> +{
>>>> +	const struct landlock_ruleset *const dom =
>>>> +		landlock_get_current_domain();
>>>> +
>>>> +	if (!dom)
>>>> +		return 0;
>>>> +
>>>> +	/* Check if it's a TCP socket. */
>>>> +	if (sock->type != SOCK_STREAM)
>>>> +		return 0;
>>>> +
>>>> +	/* Check if the hook is AF_INET* socket's action. */
>>>> +	switch (address->sa_family) {
>>>> +	case AF_INET:
>>>> +#if IS_ENABLED(CONFIG_IPV6)
>>>> +	case AF_INET6:
>>>> +#endif
>>>> +		return check_socket_access(dom, get_port(address),
>>>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>>> +	case AF_UNSPEC: {
>>>> +		u16 i;
>>>
>>> You can move "i" after the "dom" declaration to remove the extra braces.
>>>
>>     Ok. Thanks.
>>>
>>>> +
>>>> +		/*
>>>> +		 * If just in a layer a mask supports connect access,
>>>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>>>> +		 * must be banned. This prevents from disconnecting already
>>>> +		 * connected sockets.
>>>> +		 */
>>>> +		for (i = 0; i < dom->num_layers; i++) {
>>>> +			if (landlock_get_net_access_mask(dom, i) &
>>>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>>> +				return -EACCES;
>>>
>>> I'm wondering if this is the right error code for this case. EPERM may
>>> be more appropriate.
>> 
>>     Ok. Will be refactored.
>>>
>>> Thinking more about this case, I don't understand what is the rationale
>>> to deny such action. What would be the consequence to always allow
>>> connection with AF_UNSPEC (i.e. to disconnect a socket)?
>>>
>>     I thought we have come to a conclusion about connect(...AF_UNSPEC..)
>>    behaviour in the patchset V3:
>> https://lore.kernel.org/linux-security-module/19ad3a01-d76e-0e73-7833-99acd4afd97e@huawei.com/
> 
> The conclusion was that AF_UNSPEC disconnects a socket, but I'm asking
> if this is a security issue. I don't think it is more dangerous than a
> new (unconnected) socket. Am I missing something? Which kind of rule
> could be bypassed? What are we protecting against by restricting AF_UNSPEC?

I just follow Willem de Bruijn concerns about this issue:

quote: "It is valid to pass an address with AF_UNSPEC to a PF_INET(6) 
socket. And there are legitimate reasons to want to deny this. Such as 
passing a connection to a unprivileged process and disallow it from 
disconnect and opening a different new connection."

https://lore.kernel.org/linux-security-module/CA+FuTSf4EjgjBCCOiu-PHJcTMia41UkTh8QJ0+qdxL_J8445EA@mail.gmail.com/


quote: "The intended use-case is for a privileged process to open a 
connection (i.e., bound and connected socket) and pass that to a 
restricted process. The intent is for that process to only be allowed to
communicate over this pre-established channel.

In practice, it is able to disconnect (while staying bound) and
elevate its privileges to that of a listening server: ..."

https://lore.kernel.org/linux-security-module/CA+FuTScaoby-=xRKf_Dz3koSYHqrMN0cauCg4jMmy_nDxwPADA@mail.gmail.com/

Looks like it's a security issue here.

> 
> We could then reduce the hook codes to just:
> return current_check_access_socket(sock, address, LANDLOCK_ACCESS_NET_*);
> .

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

* Re: [PATCH v8 12/12] landlock: Document Landlock's network support
  2022-11-28 20:26       ` Mickaël Salaün
@ 2022-12-02  3:14         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-02  3:14 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-doc



11/28/2022 11:26 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 07:44, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:44 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> Describes network access rules for TCP sockets. Adds network access
>>>> example in the tutorial. Points out AF_UNSPEC socket family behaviour.
>>>> Adds kernel configuration support for network.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v7:
>>>> * Fixes documentaion logic errors and typos as Mickaёl suggested:
>>>> https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/
>>>>
>>>> Changes since v6:
>>>> * Adds network support documentaion.
>>>>
>>>> ---
>>>>    Documentation/userspace-api/landlock.rst | 72 +++++++++++++++++++-----
>>>>    1 file changed, 59 insertions(+), 13 deletions(-)
>>>>
>>>> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
>>>> index d8cd8cd9ce25..d0610ec9ce05 100644
>>>> --- a/Documentation/userspace-api/landlock.rst
>>>> +++ b/Documentation/userspace-api/landlock.rst
>>>> @@ -11,10 +11,10 @@ Landlock: unprivileged access control
>>>>    :Date: October 2022
>>>>
>>>>    The goal of Landlock is to enable to restrict ambient rights (e.g. global
>>>> -filesystem access) for a set of processes.  Because Landlock is a stackable
>>>> -LSM, it makes possible to create safe security sandboxes as new security layers
>>>> -in addition to the existing system-wide access-controls. This kind of sandbox
>>>> -is expected to help mitigate the security impact of bugs or
>>>> +filesystem or network access) for a set of processes.  Because Landlock
>>>> +is a stackable LSM, it makes possible to create safe security sandboxes as new
>>>> +security layers in addition to the existing system-wide access-controls. This
>>>> +kind of sandbox is expected to help mitigate the security impact of bugs or
>>>>    unexpected/malicious behaviors in user space applications.  Landlock empowers
>>>>    any process, including unprivileged ones, to securely restrict themselves.
>>>>
>>>> @@ -30,18 +30,20 @@ Landlock rules
>>>>
>>>>    A Landlock rule describes an action on an object.  An object is currently a
>>>>    file hierarchy, and the related filesystem actions are defined with `access
>>>> -rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
>>>> -the thread enforcing it, and its future children.
>>>> +rights`_.  Since ABI version 4 a port data appears with related network actions
>>>> +for TCP socket families.  A set of rules is aggregated in a ruleset, which
>>>> +can then restrict the thread enforcing it, and its future children.
>>>>
>>>>    Defining and enforcing a security policy
>>>>    ----------------------------------------
>>>>
>>>>    We first need to define the ruleset that will contain our rules.  For this
>>>>    example, the ruleset will contain rules that only allow read actions, but write
>>>> -actions will be denied.  The ruleset then needs to handle both of these kind of
>>>> +actions will be denied. The ruleset then needs to handle both of these kind of
>>>>    actions.  This is required for backward and forward compatibility (i.e. the
>>>>    kernel and user space may not know each other's supported restrictions), hence
>>>> -the need to be explicit about the denied-by-default access rights.
>>>> +the need to be explicit about the denied-by-default access rights.  Also ruleset
>>>> +will have network rules for specific ports, so it should handle network actions.
>>>>
>>>>    .. code-block:: c
>>>>
>>>> @@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
>>>>                LANDLOCK_ACCESS_FS_MAKE_SYM |
>>>>                LANDLOCK_ACCESS_FS_REFER |
>>>>                LANDLOCK_ACCESS_FS_TRUNCATE,
>>>> +        .handled_access_net =
>>>> +            LANDLOCK_ACCESS_NET_BIND_TCP |
>>>> +            LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>        };
>>>>
>>>>    Because we may not know on which kernel version an application will be
>>>> @@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
>>>>    using.  To avoid binary enforcement (i.e. either all security features or
>>>>    none), we can leverage a dedicated Landlock command to get the current version
>>>>    of the Landlock ABI and adapt the handled accesses.  Let's check if we should
>>>> -remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
>>>> -access rights, which are only supported starting with the second and third
>>>> -version of the ABI.
>>>> +remove the `LANDLOCK_ACCESS_FS_REFER` or `LANDLOCK_ACCESS_FS_TRUNCATE` or
>>>> +network access rights, which are only supported starting with the second,
>>>
>>> This is a bad rebase.
>> 
>>     Sorry. Did not get it.
> 
> This hunk (and maybe others) changes unrelated things (e.g. back quotes).

   Ok. Got it. Thanks.
> 
> 
>>>
>>>
>>>> +third and fourth version of the ABI.
>>>>
>>>>    .. code-block:: c
>>>>
>>>>        int abi;
>>>>
>>>> +    #define ACCESS_NET_BIND_CONNECT ( \
>>>> +    LANDLOCK_ACCESS_NET_BIND_TCP | \
>>>> +    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>>
>>> Please add a 4-spaces prefix for these two lines.
>> 
>>     Like this??
>> 	#define ACCESS_NET_BIND_CONNECT ( \
>>               LANDLOCK_ACCESS_NET_BIND_TCP | \
>>               LANDLOCK_ACCESS_NET_CONNECT_TCP)
> 
> Like for other indentations in the documentation (e.g. ruleset_attr
> definition):
> 
> #define ACCESS_NET_BIND_CONNECT ( \
>       LANDLOCK_ACCESS_NET_BIND_TCP | \
>       LANDLOCK_ACCESS_NET_CONNECT_TCP)

  Ok. Will be fixed.
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-12-02  3:13         ` Konstantin Meskhidze (A)
@ 2022-12-02 13:01           ` Mickaël Salaün
  2022-12-05  2:55             ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-12-02 13:01 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), willemdebruijn.kernel
  Cc: gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, linux-api, Alejandro Colomar (man-pages)


On 02/12/2022 04:13, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/29/2022 12:00 AM, Mickaël Salaün пишет:
>> The previous commit provides an interface to theoretically restrict
>> network access (i.e. ruleset handled network accesses), but in fact this
>> is not enforced until this commit. I like this split but to avoid any
>> inconsistency, please squash this commit into the previous one: "7/12
>> landlock: Add network rules support"
>> You should keep all the commit messages but maybe tweak them a bit.
>>
>     Ok. Will be squashed.
>>
>> On 28/11/2022 09:21, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>
>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>> particular ports.
>>>>
>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>> restrict TCP socket binding and connection to specific ports.
>>>>
>>>      Ok. Thanks.
>>>>
>>>>>
>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>> ---
>>
>> [...]
>>
>>>>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>>>>> +			       int addrlen)
>>>>> +{
>>>>> +	const struct landlock_ruleset *const dom =
>>>>> +		landlock_get_current_domain();
>>>>> +
>>>>> +	if (!dom)
>>>>> +		return 0;
>>>>> +
>>>>> +	/* Check if it's a TCP socket. */
>>>>> +	if (sock->type != SOCK_STREAM)
>>>>> +		return 0;
>>>>> +
>>>>> +	/* Check if the hook is AF_INET* socket's action. */
>>>>> +	switch (address->sa_family) {
>>>>> +	case AF_INET:
>>>>> +#if IS_ENABLED(CONFIG_IPV6)
>>>>> +	case AF_INET6:
>>>>> +#endif
>>>>> +		return check_socket_access(dom, get_port(address),
>>>>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>>>> +	case AF_UNSPEC: {
>>>>> +		u16 i;
>>>>
>>>> You can move "i" after the "dom" declaration to remove the extra braces.
>>>>
>>>      Ok. Thanks.
>>>>
>>>>> +
>>>>> +		/*
>>>>> +		 * If just in a layer a mask supports connect access,
>>>>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>>>>> +		 * must be banned. This prevents from disconnecting already
>>>>> +		 * connected sockets.
>>>>> +		 */
>>>>> +		for (i = 0; i < dom->num_layers; i++) {
>>>>> +			if (landlock_get_net_access_mask(dom, i) &
>>>>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>>>> +				return -EACCES;
>>>>
>>>> I'm wondering if this is the right error code for this case. EPERM may
>>>> be more appropriate.
>>>
>>>      Ok. Will be refactored.
>>>>
>>>> Thinking more about this case, I don't understand what is the rationale
>>>> to deny such action. What would be the consequence to always allow
>>>> connection with AF_UNSPEC (i.e. to disconnect a socket)?
>>>>
>>>      I thought we have come to a conclusion about connect(...AF_UNSPEC..)
>>>     behaviour in the patchset V3:
>>> https://lore.kernel.org/linux-security-module/19ad3a01-d76e-0e73-7833-99acd4afd97e@huawei.com/
>>
>> The conclusion was that AF_UNSPEC disconnects a socket, but I'm asking
>> if this is a security issue. I don't think it is more dangerous than a
>> new (unconnected) socket. Am I missing something? Which kind of rule
>> could be bypassed? What are we protecting against by restricting AF_UNSPEC?
> 
> I just follow Willem de Bruijn concerns about this issue:
> 
> quote: "It is valid to pass an address with AF_UNSPEC to a PF_INET(6)
> socket. And there are legitimate reasons to want to deny this. Such as
> passing a connection to a unprivileged process and disallow it from
> disconnect and opening a different new connection."
> 
> https://lore.kernel.org/linux-security-module/CA+FuTSf4EjgjBCCOiu-PHJcTMia41UkTh8QJ0+qdxL_J8445EA@mail.gmail.com/

I agree with the fact that we want to deny this, but in this example the 
new connection should still be restricted by the Landlock domain. Using 
AF_UNSPEC on a connected socket should not make this socket allowed to 
create any connection if the process is restricted with TCP_CONNECT. 
Being allowed to close a connection should not be an issue, and any new 
connection must be vetted by Landlock.

> 
> 
> quote: "The intended use-case is for a privileged process to open a
> connection (i.e., bound and connected socket) and pass that to a
> restricted process. The intent is for that process to only be allowed to
> communicate over this pre-established channel.
> 
> In practice, it is able to disconnect (while staying bound) and
> elevate its privileges to that of a listening server: ..."
> 
> https://lore.kernel.org/linux-security-module/CA+FuTScaoby-=xRKf_Dz3koSYHqrMN0cauCg4jMmy_nDxwPADA@mail.gmail.com/
> 
> Looks like it's a security issue here.

It the provided example, if child_process() is restricted with 
TCP_CONNECT and TCP_BIND, any call to connect() or bind() will return an 
access error. listen() and accept() would work if the socket is bound, 
which is the case here, and then implicitly allowed by the parent 
process. I don' see any security issue. Am I missing something?

In fact, connect with AF_UNSPEC should always be allowed to be 
consistent with close(2), which is a way to drop privileges.


What Willem said:
> It would be good to also
> ensure that a now-bound socket cannot call listen.

This is not relevant for Landlock because the security model is to check 
process's requests to get new accesses (e.g. create a new file 
descriptor), but not to check passed accesses (e.g. inherited from a 
parent process, or pass through a unix socket) which are delegated to 
the sender/parent. The goal of a sandbox is to limit the set of new 
access requested (to the kernel) from within this sandbox. All already 
opened file descriptors were previously vetted by Landlock (and other 
access control systems).

> 
>>
>> We could then reduce the hook codes to just:
>> return current_check_access_socket(sock, address, LANDLOCK_ACCESS_NET_*);
>> .

As for SELinux, the connect hook should first do this check (with an 
appropriate comment):
if (address->sa_family == AF_UNSPEC)
	return 0;

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-12-02 13:01           ` Mickaël Salaün
@ 2022-12-05  2:55             ` Konstantin Meskhidze (A)
  2022-12-05 13:18               ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-05  2:55 UTC (permalink / raw)
  To: Mickaël Salaün, willemdebruijn.kernel
  Cc: gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, linux-api, Alejandro Colomar (man-pages)



12/2/2022 4:01 PM, Mickaël Salaün пишет:
> 
> On 02/12/2022 04:13, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/29/2022 12:00 AM, Mickaël Salaün пишет:
>>> The previous commit provides an interface to theoretically restrict
>>> network access (i.e. ruleset handled network accesses), but in fact this
>>> is not enforced until this commit. I like this split but to avoid any
>>> inconsistency, please squash this commit into the previous one: "7/12
>>> landlock: Add network rules support"
>>> You should keep all the commit messages but maybe tweak them a bit.
>>>
>>     Ok. Will be squashed.
>>>
>>> On 28/11/2022 09:21, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>>
>>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>>> particular ports.
>>>>>
>>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>>> restrict TCP socket binding and connection to specific ports.
>>>>>
>>>>      Ok. Thanks.
>>>>>
>>>>>>
>>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>>> ---
>>>
>>> [...]
>>>
>>>>>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>>>>>> +			       int addrlen)
>>>>>> +{
>>>>>> +	const struct landlock_ruleset *const dom =
>>>>>> +		landlock_get_current_domain();
>>>>>> +
>>>>>> +	if (!dom)
>>>>>> +		return 0;
>>>>>> +
>>>>>> +	/* Check if it's a TCP socket. */
>>>>>> +	if (sock->type != SOCK_STREAM)
>>>>>> +		return 0;
>>>>>> +
>>>>>> +	/* Check if the hook is AF_INET* socket's action. */
>>>>>> +	switch (address->sa_family) {
>>>>>> +	case AF_INET:
>>>>>> +#if IS_ENABLED(CONFIG_IPV6)
>>>>>> +	case AF_INET6:
>>>>>> +#endif
>>>>>> +		return check_socket_access(dom, get_port(address),
>>>>>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>>>>> +	case AF_UNSPEC: {
>>>>>> +		u16 i;
>>>>>
>>>>> You can move "i" after the "dom" declaration to remove the extra braces.
>>>>>
>>>>      Ok. Thanks.
>>>>>
>>>>>> +
>>>>>> +		/*
>>>>>> +		 * If just in a layer a mask supports connect access,
>>>>>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>>>>>> +		 * must be banned. This prevents from disconnecting already
>>>>>> +		 * connected sockets.
>>>>>> +		 */
>>>>>> +		for (i = 0; i < dom->num_layers; i++) {
>>>>>> +			if (landlock_get_net_access_mask(dom, i) &
>>>>>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>>>>> +				return -EACCES;
>>>>>
>>>>> I'm wondering if this is the right error code for this case. EPERM may
>>>>> be more appropriate.
>>>>
>>>>      Ok. Will be refactored.
>>>>>
>>>>> Thinking more about this case, I don't understand what is the rationale
>>>>> to deny such action. What would be the consequence to always allow
>>>>> connection with AF_UNSPEC (i.e. to disconnect a socket)?
>>>>>
>>>>      I thought we have come to a conclusion about connect(...AF_UNSPEC..)
>>>>     behaviour in the patchset V3:
>>>> https://lore.kernel.org/linux-security-module/19ad3a01-d76e-0e73-7833-99acd4afd97e@huawei.com/
>>>
>>> The conclusion was that AF_UNSPEC disconnects a socket, but I'm asking
>>> if this is a security issue. I don't think it is more dangerous than a
>>> new (unconnected) socket. Am I missing something? Which kind of rule
>>> could be bypassed? What are we protecting against by restricting AF_UNSPEC?
>> 
>> I just follow Willem de Bruijn concerns about this issue:
>> 
>> quote: "It is valid to pass an address with AF_UNSPEC to a PF_INET(6)
>> socket. And there are legitimate reasons to want to deny this. Such as
>> passing a connection to a unprivileged process and disallow it from
>> disconnect and opening a different new connection."
>> 
>> https://lore.kernel.org/linux-security-module/CA+FuTSf4EjgjBCCOiu-PHJcTMia41UkTh8QJ0+qdxL_J8445EA@mail.gmail.com/
> 
> I agree with the fact that we want to deny this, but in this example the
> new connection should still be restricted by the Landlock domain. Using
> AF_UNSPEC on a connected socket should not make this socket allowed to
> create any connection if the process is restricted with TCP_CONNECT.
> Being allowed to close a connection should not be an issue, and any new
> connection must be vetted by Landlock.
> 

   You are right. This makes sense. Thanks for the comment.
>> 
>> 
>> quote: "The intended use-case is for a privileged process to open a
>> connection (i.e., bound and connected socket) and pass that to a
>> restricted process. The intent is for that process to only be allowed to
>> communicate over this pre-established channel.
>> 
>> In practice, it is able to disconnect (while staying bound) and
>> elevate its privileges to that of a listening server: ..."
>> 
>> https://lore.kernel.org/linux-security-module/CA+FuTScaoby-=xRKf_Dz3koSYHqrMN0cauCg4jMmy_nDxwPADA@mail.gmail.com/
>> 
>> Looks like it's a security issue here.
> 
> It the provided example, if child_process() is restricted with
> TCP_CONNECT and TCP_BIND, any call to connect() or bind() will return an
> access error. listen() and accept() would work if the socket is bound,
> which is the case here, and then implicitly allowed by the parent
> process. I don' see any security issue. Am I missing something?
> 
> In fact, connect with AF_UNSPEC should always be allowed to be
> consistent with close(2), which is a way to drop privileges.
> 

  It should be allowed with checking:
"return check_socket_access(dom, get_port(address),
                                  LANDLOCK_ACCESS_NET_CONNECT_TCP);
> 
> What Willem said:
>> It would be good to also
>> ensure that a now-bound socket cannot call listen.
> 
> This is not relevant for Landlock because the security model is to check
> process's requests to get new accesses (e.g. create a new file
> descriptor), but not to check passed accesses (e.g. inherited from a
> parent process, or pass through a unix socket) which are delegated to
> the sender/parent. The goal of a sandbox is to limit the set of new
> access requested (to the kernel) from within this sandbox. All already
> opened file descriptors were previously vetted by Landlock (and other
> access control systems).

    I got your point. Thanks.
> 
>> 
>>>
>>> We could then reduce the hook codes to just:
>>> return current_check_access_socket(sock, address, LANDLOCK_ACCESS_NET_*);
>>> .
> 
> As for SELinux, the connect hook should first do this check (with an
> appropriate comment):
> if (address->sa_family == AF_UNSPEC)
> 	return 0;

   In case of Landlock it looks like a landlocked process could connnect 
to the ports it's not allowed to connect to.
So we need just to return check_socket_access(dom, get_port(address),
				   LANDLOCK_ACCESS_NET_CONNECT_TCP);
I'm I correct? Did I miss something?
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-12-05  2:55             ` Konstantin Meskhidze (A)
@ 2022-12-05 13:18               ` Mickaël Salaün
  0 siblings, 0 replies; 87+ messages in thread
From: Mickaël Salaün @ 2022-12-05 13:18 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), willemdebruijn.kernel
  Cc: gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, linux-api, Alejandro Colomar (man-pages),
	Paul Moore


On 05/12/2022 03:55, Konstantin Meskhidze (A) wrote:
> 
> 
> 12/2/2022 4:01 PM, Mickaël Salaün пишет:
>>
>> On 02/12/2022 04:13, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 11/29/2022 12:00 AM, Mickaël Salaün пишет:
>>>> The previous commit provides an interface to theoretically restrict
>>>> network access (i.e. ruleset handled network accesses), but in fact this
>>>> is not enforced until this commit. I like this split but to avoid any
>>>> inconsistency, please squash this commit into the previous one: "7/12
>>>> landlock: Add network rules support"
>>>> You should keep all the commit messages but maybe tweak them a bit.
>>>>
>>>      Ok. Will be squashed.
>>>>
>>>> On 28/11/2022 09:21, Konstantin Meskhidze (A) wrote:
>>>>>
>>>>>
>>>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>>>
>>>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>>>> particular ports.
>>>>>>
>>>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>>>> restrict TCP socket binding and connection to specific ports.
>>>>>>
>>>>>       Ok. Thanks.
>>>>>>
>>>>>>>
>>>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>>>> ---
>>>>
>>>> [...]
>>>>
>>>>>>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>>>>>>> +			       int addrlen)
>>>>>>> +{
>>>>>>> +	const struct landlock_ruleset *const dom =
>>>>>>> +		landlock_get_current_domain();
>>>>>>> +
>>>>>>> +	if (!dom)
>>>>>>> +		return 0;
>>>>>>> +
>>>>>>> +	/* Check if it's a TCP socket. */
>>>>>>> +	if (sock->type != SOCK_STREAM)
>>>>>>> +		return 0;
>>>>>>> +
>>>>>>> +	/* Check if the hook is AF_INET* socket's action. */
>>>>>>> +	switch (address->sa_family) {
>>>>>>> +	case AF_INET:
>>>>>>> +#if IS_ENABLED(CONFIG_IPV6)
>>>>>>> +	case AF_INET6:
>>>>>>> +#endif
>>>>>>> +		return check_socket_access(dom, get_port(address),
>>>>>>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>>>>>> +	case AF_UNSPEC: {
>>>>>>> +		u16 i;
>>>>>>
>>>>>> You can move "i" after the "dom" declaration to remove the extra braces.
>>>>>>
>>>>>       Ok. Thanks.
>>>>>>
>>>>>>> +
>>>>>>> +		/*
>>>>>>> +		 * If just in a layer a mask supports connect access,
>>>>>>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>>>>>>> +		 * must be banned. This prevents from disconnecting already
>>>>>>> +		 * connected sockets.
>>>>>>> +		 */
>>>>>>> +		for (i = 0; i < dom->num_layers; i++) {
>>>>>>> +			if (landlock_get_net_access_mask(dom, i) &
>>>>>>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>>>>>> +				return -EACCES;
>>>>>>
>>>>>> I'm wondering if this is the right error code for this case. EPERM may
>>>>>> be more appropriate.
>>>>>
>>>>>       Ok. Will be refactored.
>>>>>>
>>>>>> Thinking more about this case, I don't understand what is the rationale
>>>>>> to deny such action. What would be the consequence to always allow
>>>>>> connection with AF_UNSPEC (i.e. to disconnect a socket)?
>>>>>>
>>>>>       I thought we have come to a conclusion about connect(...AF_UNSPEC..)
>>>>>      behaviour in the patchset V3:
>>>>> https://lore.kernel.org/linux-security-module/19ad3a01-d76e-0e73-7833-99acd4afd97e@huawei.com/
>>>>
>>>> The conclusion was that AF_UNSPEC disconnects a socket, but I'm asking
>>>> if this is a security issue. I don't think it is more dangerous than a
>>>> new (unconnected) socket. Am I missing something? Which kind of rule
>>>> could be bypassed? What are we protecting against by restricting AF_UNSPEC?
>>>
>>> I just follow Willem de Bruijn concerns about this issue:
>>>
>>> quote: "It is valid to pass an address with AF_UNSPEC to a PF_INET(6)
>>> socket. And there are legitimate reasons to want to deny this. Such as
>>> passing a connection to a unprivileged process and disallow it from
>>> disconnect and opening a different new connection."
>>>
>>> https://lore.kernel.org/linux-security-module/CA+FuTSf4EjgjBCCOiu-PHJcTMia41UkTh8QJ0+qdxL_J8445EA@mail.gmail.com/
>>
>> I agree with the fact that we want to deny this, but in this example the
>> new connection should still be restricted by the Landlock domain. Using
>> AF_UNSPEC on a connected socket should not make this socket allowed to
>> create any connection if the process is restricted with TCP_CONNECT.
>> Being allowed to close a connection should not be an issue, and any new
>> connection must be vetted by Landlock.
>>
> 
>     You are right. This makes sense. Thanks for the comment.
>>>
>>>
>>> quote: "The intended use-case is for a privileged process to open a
>>> connection (i.e., bound and connected socket) and pass that to a
>>> restricted process. The intent is for that process to only be allowed to
>>> communicate over this pre-established channel.
>>>
>>> In practice, it is able to disconnect (while staying bound) and
>>> elevate its privileges to that of a listening server: ..."
>>>
>>> https://lore.kernel.org/linux-security-module/CA+FuTScaoby-=xRKf_Dz3koSYHqrMN0cauCg4jMmy_nDxwPADA@mail.gmail.com/
>>>
>>> Looks like it's a security issue here.
>>
>> It the provided example, if child_process() is restricted with
>> TCP_CONNECT and TCP_BIND, any call to connect() or bind() will return an
>> access error. listen() and accept() would work if the socket is bound,
>> which is the case here, and then implicitly allowed by the parent
>> process. I don' see any security issue. Am I missing something?
>>
>> In fact, connect with AF_UNSPEC should always be allowed to be
>> consistent with close(2), which is a way to drop privileges.
>>
> 
>    It should be allowed with checking:
> "return check_socket_access(dom, get_port(address),
>                                    LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>
>> What Willem said:
>>> It would be good to also
>>> ensure that a now-bound socket cannot call listen.
>>
>> This is not relevant for Landlock because the security model is to check
>> process's requests to get new accesses (e.g. create a new file
>> descriptor), but not to check passed accesses (e.g. inherited from a
>> parent process, or pass through a unix socket) which are delegated to
>> the sender/parent. The goal of a sandbox is to limit the set of new
>> access requested (to the kernel) from within this sandbox. All already
>> opened file descriptors were previously vetted by Landlock (and other
>> access control systems).
> 
>      I got your point. Thanks.
>>
>>>
>>>>
>>>> We could then reduce the hook codes to just:
>>>> return current_check_access_socket(sock, address, LANDLOCK_ACCESS_NET_*);

This current_check_access_socket() helper should contain all the access 
control code.

>>>> .
>>
>> As for SELinux, the connect hook should first do this check (with an
>> appropriate comment):
>> if (address->sa_family == AF_UNSPEC)
>> 	return 0;
> 
>     In case of Landlock it looks like a landlocked process could connnect
> to the ports it's not allowed to connect to.
> So we need just to return check_socket_access(dom, get_port(address),
> 				   LANDLOCK_ACCESS_NET_CONNECT_TCP);
> I'm I correct? Did I miss something?

Using AF_UNSPEC with connect(2) doesn't connect the socket to a port, 
and in fact completely ignore the port. We can move the AF_UNSPEC check 
to the current_check_access_socket() helper:

  	switch (address->sa_family) {
  	case AF_UNSPEC:
+		/*
+		 * Connecting to an address with AF_UNSPEC dissolves the TCP
+		 * association, which have the same effect as closing the
+		 * connection while retaining the socket object (i.e., the file
+		 * descriptor).  As for dropping privileges, closing
+		 * connections is always allowed.
+		 */
+		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
+			return 0;
+
+		/*
+		 * For compatibility reason, accept AF_UNSPEC for bind
+		 * accesses (mapped to AF_INET) only if the address is
+		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
+		 * required to not wrongfully return -EACCES instead of
+		 * -EAFNOSUPPORT.
+		 */
+		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+			const struct sockaddr_in *const sockaddr =
+				(struct sockaddr_in *)address;
+
+			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
+				return -EAFNOSUPPORT;
+		}
+
+		fallthrough;
  	case AF_INET:
  #if IS_ENABLED(CONFIG_IPV6)
  	case AF_INET6:


I also added another check (copied from SELinux) with the appropriate 
explanation. All this needs dedicated tests to make sure everything is 
covered.

We also need to add extra checks (and related tests) for addrlen as do 
other LSMs.

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-11-28 20:23           ` Mickaël Salaün
  2022-12-02  2:50             ` Konstantin Meskhidze (A)
@ 2022-12-24  3:10             ` Konstantin Meskhidze (A)
  2022-12-26 21:24               ` Mickaël Salaün
  1 sibling, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-24  3:10 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



11/28/2022 11:23 PM, Mickaël Salaün пишет:
> 
> On 28/11/2022 04:04, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/18/2022 12:16 PM, Mickaël Salaün пишет:
>>> Konstantin, this patch should apply cleanly just after "01/12 landlock:
>>> Make ruleset's access masks more generic". You can easily get this patch
>>> with https://git.kernel.org/pub/scm/utils/b4/b4.git/
>>> Some adjustments are needed for the following patches. Feel free to
>>> review this patch.
>       Do you have this patch online? Can I fetch it from your repo?
> 
> You can cherry-pick from here: https://git.kernel.org/mic/c/439ea2d31e662

Hi Mickaёl.

Sorry for the delay. I was a bit busy with another task. Now I'm 
preparing a new patch.

I tried to apply your one but I got an error opening this the link : Bad 
object id: 439ea2d31e662.

Could please check it?

Best regards.
> .

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-12-24  3:10             ` Konstantin Meskhidze (A)
@ 2022-12-26 21:24               ` Mickaël Salaün
  2022-12-27  1:47                 ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2022-12-26 21:24 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



On 24/12/2022 04:10, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/28/2022 11:23 PM, Mickaël Salaün пишет:
>>
>> On 28/11/2022 04:04, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 11/18/2022 12:16 PM, Mickaël Salaün пишет:
>>>> Konstantin, this patch should apply cleanly just after "01/12 landlock:
>>>> Make ruleset's access masks more generic". You can easily get this patch
>>>> with https://git.kernel.org/pub/scm/utils/b4/b4.git/
>>>> Some adjustments are needed for the following patches. Feel free to
>>>> review this patch.
>>        Do you have this patch online? Can I fetch it from your repo?
>>
>> You can cherry-pick from here: https://git.kernel.org/mic/c/439ea2d31e662
> 
> Hi Mickaёl.
> 
> Sorry for the delay. I was a bit busy with another task. Now I'm
> preparing a new patch.
> 
> I tried to apply your one but I got an error opening this the link : Bad
> object id: 439ea2d31e662.
> 
> Could please check it?

Try this link: 
https://git.kernel.org/mic/c/439ea2d31e662e586db659a9f01b7dd55848c035
I pushed it to the landlock-net-v8.1 branch.

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

* Re: [PATCH] landlock: Allow filesystem layout changes for domains without such rule type
  2022-12-26 21:24               ` Mickaël Salaün
@ 2022-12-27  1:47                 ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2022-12-27  1:47 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: artem.kuzin, gnoack3000, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel



12/27/2022 12:24 AM, Mickaël Salaün пишет:
> 
> 
> On 24/12/2022 04:10, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/28/2022 11:23 PM, Mickaël Salaün пишет:
>>>
>>> On 28/11/2022 04:04, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 11/18/2022 12:16 PM, Mickaël Salaün пишет:
>>>>> Konstantin, this patch should apply cleanly just after "01/12 landlock:
>>>>> Make ruleset's access masks more generic". You can easily get this patch
>>>>> with https://git.kernel.org/pub/scm/utils/b4/b4.git/
>>>>> Some adjustments are needed for the following patches. Feel free to
>>>>> review this patch.
>>>        Do you have this patch online? Can I fetch it from your repo?
>>>
>>> You can cherry-pick from here: https://git.kernel.org/mic/c/439ea2d31e662
>> 
>> Hi Mickaёl.
>> 
>> Sorry for the delay. I was a bit busy with another task. Now I'm
>> preparing a new patch.
>> 
>> I tried to apply your one but I got an error opening this the link : Bad
>> object id: 439ea2d31e662.
>> 
>> Could please check it?
> 
> Try this link:
> https://git.kernel.org/mic/c/439ea2d31e662e586db659a9f01b7dd55848c035
> I pushed it to the landlock-net-v8.1 branch.

   Thank you so much!!
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2022-11-17 18:43   ` Mickaël Salaün
  2022-11-28  4:01     ` Konstantin Meskhidze (A)
@ 2023-01-03 12:44     ` Konstantin Meskhidze (A)
  2023-01-04 11:41     ` Konstantin Meskhidze (A)
  2 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-03 12:44 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)



11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This commit adds network rules support in internal landlock functions
>> (presented in ruleset.c) and landlock_create_ruleset syscall.
> 
> …in the ruleset management helpers and the landlock_create_ruleset syscall.
> 
> 
>> Refactors user space API to support network actions. Adds new network
> 
> Refactor…
> 
>> access flags, network rule and network attributes. Increments Landlock
> 
> Increment…
> 
>> ABI version.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Squashes commits.
>> * Increments ABI version to 4.
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>    because it OR values.
>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_net_access_mask().
>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>    LANDLOCK_NUM_ACCESS_FS as value.
>> * Updates access_masks_t to u32 to support network access actions.
>> * Refactors landlock internal functions to support network actions with
>>    landlock_key/key_type/id types.
>> 
>> Changes since v5:
>> * Gets rid of partial revert from landlock_add_rule
>> syscall.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactors landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds network rule support for internal landlock functions.
>> * Adds set_mask and get_mask for network.
>> * Adds rb_root root_net_port.
>> 
>> ---
>>   include/uapi/linux/landlock.h                | 49 ++++++++++++++
>>   security/landlock/limits.h                   |  6 +-
>>   security/landlock/ruleset.c                  | 55 ++++++++++++++--
>>   security/landlock/ruleset.h                  | 68 ++++++++++++++++----
>>   security/landlock/syscalls.c                 | 13 +++-
>>   tools/testing/selftests/landlock/base_test.c |  2 +-
>>   6 files changed, 170 insertions(+), 23 deletions(-)
>> 
>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>> index f3223f964691..096b683c6ff3 100644
>> --- a/include/uapi/linux/landlock.h
>> +++ b/include/uapi/linux/landlock.h
>> @@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
>>   	 * this access right.
>>   	 */
>>   	__u64 handled_access_fs;
>> +
>> +	/**
>> +	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
>> +	 * that is handled by this ruleset and should then be forbidden if no
>> +	 * rule explicitly allow them.
>> +	 */
>> +	__u64 handled_access_net;
>>   };
>> 
>>   /*
>> @@ -54,6 +61,11 @@ enum landlock_rule_type {
>>   	 * landlock_path_beneath_attr .
>>   	 */
>>   	LANDLOCK_RULE_PATH_BENEATH = 1,
>> +	/**
>> +	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
>> +	 * landlock_net_service_attr .
>> +	 */
>> +	LANDLOCK_RULE_NET_SERVICE = 2,
>>   };
>> 
>>   /**
>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>   	 */
>>   } __attribute__((packed));
>> 
>> +/**
>> + * struct landlock_net_service_attr - TCP subnet definition
>> + *
>> + * Argument of sys_landlock_add_rule().
>> + */
>> +struct landlock_net_service_attr {
>> +	/**
>> +	 * @allowed_access: Bitmask of allowed access network for services
>> +	 * (cf. `Network flags`_).
>> +	 */
>> +	__u64 allowed_access;
>> +	/**
>> +	 * @port: Network port.
>> +	 */
>> +	__u16 port;
> 
>   From an UAPI point of view, I think the port field should be __be16, as
> for sockaddr_in->port and other network-related APIs. This will require
> some kernel changes to please sparse: make C=2 security/landlock/ must
> not print any warning.
> 
> Using big-endian values as keys (casted to uintptr_t, not strictly
> __be16) in the rb-tree should not be an issue because there is no port
> range ordering (for now).
> 
> A dedicated test should check that endianness is correct, e.g. by using
> different port encoding. This should include passing and failing tests,
> but they should work on all architectures (i.e. big or little endian).
> 
   Hi Mickaёl.
   Could you please give me a piece of advice about these kind of tests?
   I have not entirely understood this point.
> 
>> +
>> +} __attribute__((packed));
>> +
>>   /**
>>    * DOC: fs_access
>>    *
>> @@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
>>   #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
>>   /* clang-format on */
>> 
>> +/**
>> + * DOC: net_access
>> + *
>> + * Network flags
>> + * ~~~~~~~~~~~~~~~~
>> + *
>> + * These flags enable to restrict a sandboxed process to a set of network
>> + * actions.
>> + *
>> + * TCP sockets with allowed actions:
>> + *
>> + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
>> + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
>> + *   a remote port.
>> + */
>> +/* clang-format off */
>> +#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
>> +#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
>> +/* clang-format on */
>>   #endif /* _UAPI_LINUX_LANDLOCK_H */
>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>> index bafb3b8dc677..8a1a6463c64e 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -23,6 +23,10 @@
>>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>   #define LANDLOCK_SHIFT_ACCESS_FS	0
>> 
>> -/* clang-format on */
>> +#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
>> +#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
>> +#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>> +#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
>> 
>> +/* clang-format on */
>>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index c7cf54ba4f6d..9277c1295114 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -36,6 +36,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   	refcount_set(&new_ruleset->usage, 1);
>>   	mutex_init(&new_ruleset->lock);
>>   	new_ruleset->root_inode = RB_ROOT;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	new_ruleset->root_net_port = RB_ROOT;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	new_ruleset->num_layers = num_layers;
>>   	/*
>>   	 * hierarchy = NULL
>> @@ -46,16 +49,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   }
>> 
>>   struct landlock_ruleset *
>> -landlock_create_ruleset(const access_mask_t fs_access_mask)
>> +landlock_create_ruleset(const access_mask_t fs_access_mask,
>> +			const access_mask_t net_access_mask)
>>   {
>>   	struct landlock_ruleset *new_ruleset;
>> 
>>   	/* Informs about useless ruleset. */
>> -	if (!fs_access_mask)
>> +	if (!fs_access_mask && !net_access_mask)
>>   		return ERR_PTR(-ENOMSG);
>>   	new_ruleset = create_ruleset(1);
>> -	if (!IS_ERR(new_ruleset))
>> +	if (IS_ERR(new_ruleset))
>> +		return new_ruleset;
>> +	if (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);
>>   	return new_ruleset;
>>   }
>> 
>> @@ -73,6 +81,10 @@ static inline bool is_object_pointer(const enum landlock_key_type key_type)
>>   	switch (key_type) {
>>   	case LANDLOCK_KEY_INODE:
>>   		return true;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		return false;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	}
>>   	WARN_ON_ONCE(1);
>>   	return false;
>> @@ -126,6 +138,11 @@ static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
>>   	case LANDLOCK_KEY_INODE:
>>   		root = &ruleset->root_inode;
>>   		break;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		root = &ruleset->root_net_port;
>> +		break;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	}
>>   	if (WARN_ON_ONCE(!root))
>>   		return ERR_PTR(-EINVAL);
>> @@ -154,7 +171,9 @@ static void build_check_ruleset(void)
>>   	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>>   	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>>   	BUILD_BUG_ON(access_masks <
>> -		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) +
> 
> This is correct but because we are dealing with bitmasks I would prefer
> to use "|" instead of "+".
> 
> 
>> +			     (LANDLOCK_MASK_ACCESS_NET
>> +			      << LANDLOCK_SHIFT_ACCESS_NET));
>>   }
>> 
>>   /**
>> @@ -370,7 +389,12 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
>>   	if (err)
>>   		goto out_unlock;
>> -
> 
> Please keep this newline.
> 
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/* Merges the @src network port tree. */
>> +	err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
>> +	if (err)
>> +		goto out_unlock;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   out_unlock:
>>   	mutex_unlock(&src->lock);
>>   	mutex_unlock(&dst->lock);
>> @@ -426,7 +450,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>   	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
>>   	if (err)
>>   		goto out_unlock;
>> -
> 
> newline
> 
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/* Copies the @parent network port tree. */
>> +	err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
>> +	if (err)
>> +		goto out_unlock;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
>>   		err = -EINVAL;
>>   		goto out_unlock;
>> @@ -459,6 +488,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
>>   	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
>>   					     node)
>>   		free_rule(freeme, LANDLOCK_KEY_INODE);
>> +#if IS_ENABLED(CONFIG_INET)
>> +	rbtree_postorder_for_each_entry_safe(freeme, next,
>> +					     &ruleset->root_net_port, node)
>> +		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	put_hierarchy(ruleset->hierarchy);
>>   	kfree(ruleset);
>>   }
>> @@ -637,6 +671,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
>>    * Populates @layer_masks such that for each access right in @access_request,
>>    * the bits for all the layers are set where this access right is handled.
>>    *
>> + * @layer_masks must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET
>> + * elements according to @key_type.
> 
> Please include this sentence in the @layer_masks description below.
> 
>> + *
>>    * @domain: The domain that defines the current restrictions.
>>    * @access_request: The requested access rights to check.
>>    * @layer_masks: The layer masks to populate.
> 
> "It must contain…"
> 
> 
>> @@ -659,6 +696,12 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>>   		get_access_mask = landlock_get_fs_access_mask;
>>   		num_access = LANDLOCK_NUM_ACCESS_FS;
>>   		break;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		get_access_mask = landlock_get_net_access_mask;
>> +		num_access = LANDLOCK_NUM_ACCESS_NET;
>> +		break;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	default:
>>   		WARN_ON_ONCE(1);
>>   		return 0;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index d9eb79ea9a89..f272d2cd518c 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -19,16 +19,20 @@
>>   #include "limits.h"
>>   #include "object.h"
>> 
>> +/* Rule access mask. */
>>   typedef u16 access_mask_t;
>>   /* Makes sure all filesystem access rights can be stored. */
>>   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 for_each_set_bit() and for_each_clear_bit() calls are OK. */
>>   static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>> 
>>   /* Ruleset access masks. */
>> -typedef u16 access_masks_t;
>> +typedef u32 access_masks_t;
> 
> This type change need to be explained in the commit message.
> 
> 
>>   /* Makes sure all ruleset access rights can be stored. */
>> -static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
>> +static_assert(BITS_PER_TYPE(access_masks_t) >=
>> +	      LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
>> 
>>   typedef u16 layer_mask_t;
>>   /* Makes sure all layers can be checked. */
>> @@ -82,6 +86,13 @@ enum landlock_key_type {
>>   	 * keys.
>>   	 */
>>   	LANDLOCK_KEY_INODE = 1,
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/**
>> +	 * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
>> +	 * node keys.
>> +	 */
>> +	LANDLOCK_KEY_NET_PORT = 2,
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   };
>> 
>>   /**
>> @@ -156,6 +167,15 @@ struct landlock_ruleset {
>>   	 * reaches zero.
>>   	 */
>>   	struct rb_root root_inode;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/**
>> +	 * @root_net_port: Root of a red-black tree containing &struct
>> +	 * landlock_rule nodes with network port. Once a ruleset is tied to a
>> +	 * process (i.e. as a domain), this tree is immutable until @usage
>> +	 * reaches zero.
>> +	 */
>> +	struct rb_root root_net_port;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	/**
>>   	 * @hierarchy: Enables hierarchy identification even when a parent
>>   	 * domain vanishes.  This is needed for the ptrace protection.
>> @@ -166,8 +186,8 @@ struct landlock_ruleset {
>>   		 * @work_free: Enables to free a ruleset within a lockless
>>   		 * section.  This is only used by
>>   		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>> -		 * The fields @lock, @usage, @num_rules, @num_layers and
>> -		 * @access_masks are then unused.
>> +		 * The fields @lock, @usage, @num_rules, @num_layers,
>> +		 * @net_access_mask and @access_masks are then unused.
> 
> There is no net_access_mask anymore.
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2022-11-17 18:43   ` Mickaël Salaün
  2022-11-28  4:01     ` Konstantin Meskhidze (A)
  2023-01-03 12:44     ` Konstantin Meskhidze (A)
@ 2023-01-04 11:41     ` Konstantin Meskhidze (A)
  2023-01-06 19:22       ` Mickaël Salaün
  2 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-04 11:41 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)



11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This commit adds network rules support in internal landlock functions
>> (presented in ruleset.c) and landlock_create_ruleset syscall.
> 
> …in the ruleset management helpers and the landlock_create_ruleset syscall.
> 
> 
>> Refactors user space API to support network actions. Adds new network
> 
> Refactor…
> 
>> access flags, network rule and network attributes. Increments Landlock
> 
> Increment…
> 
>> ABI version.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Squashes commits.
>> * Increments ABI version to 4.
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>    because it OR values.
>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_net_access_mask().
>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>    LANDLOCK_NUM_ACCESS_FS as value.
>> * Updates access_masks_t to u32 to support network access actions.
>> * Refactors landlock internal functions to support network actions with
>>    landlock_key/key_type/id types.
>> 
>> Changes since v5:
>> * Gets rid of partial revert from landlock_add_rule
>> syscall.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactors landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds network rule support for internal landlock functions.
>> * Adds set_mask and get_mask for network.
>> * Adds rb_root root_net_port.
>> 
>> ---
>>   include/uapi/linux/landlock.h                | 49 ++++++++++++++
>>   security/landlock/limits.h                   |  6 +-
>>   security/landlock/ruleset.c                  | 55 ++++++++++++++--
>>   security/landlock/ruleset.h                  | 68 ++++++++++++++++----
>>   security/landlock/syscalls.c                 | 13 +++-
>>   tools/testing/selftests/landlock/base_test.c |  2 +-
>>   6 files changed, 170 insertions(+), 23 deletions(-)
>> 
>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>> index f3223f964691..096b683c6ff3 100644
>> --- a/include/uapi/linux/landlock.h
>> +++ b/include/uapi/linux/landlock.h
>> @@ -31,6 +31,13 @@ struct landlock_ruleset_attr {
>>   	 * this access right.
>>   	 */
>>   	__u64 handled_access_fs;
>> +
>> +	/**
>> +	 * @handled_access_net: Bitmask of actions (cf. `Network flags`_)
>> +	 * that is handled by this ruleset and should then be forbidden if no
>> +	 * rule explicitly allow them.
>> +	 */
>> +	__u64 handled_access_net;
>>   };
>> 
>>   /*
>> @@ -54,6 +61,11 @@ enum landlock_rule_type {
>>   	 * landlock_path_beneath_attr .
>>   	 */
>>   	LANDLOCK_RULE_PATH_BENEATH = 1,
>> +	/**
>> +	 * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct
>> +	 * landlock_net_service_attr .
>> +	 */
>> +	LANDLOCK_RULE_NET_SERVICE = 2,
>>   };
>> 
>>   /**
>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>   	 */
>>   } __attribute__((packed));
>> 
>> +/**
>> + * struct landlock_net_service_attr - TCP subnet definition
>> + *
>> + * Argument of sys_landlock_add_rule().
>> + */
>> +struct landlock_net_service_attr {
>> +	/**
>> +	 * @allowed_access: Bitmask of allowed access network for services
>> +	 * (cf. `Network flags`_).
>> +	 */
>> +	__u64 allowed_access;
>> +	/**
>> +	 * @port: Network port.
>> +	 */
>> +	__u16 port;
> 
>   From an UAPI point of view, I think the port field should be __be16, as
> for sockaddr_in->port and other network-related APIs. This will require
> some kernel changes to please sparse: make C=2 security/landlock/ must
> not print any warning.

   I have this errors trying to launch sparse checking:

   DESCEND objtool
   DESCEND bpf/resolve_btfids
   CALL    scripts/checksyscalls.sh
   CHK     kernel/kheaders_data.tar.xz
   CC      security/landlock/setup.o
   CHECK   security/landlock/setup.c
./include/asm-generic/rwonce.h:67:16: error: typename in expression
./include/asm-generic/rwonce.h:67:16: error: Expected ) in function call
./include/asm-generic/rwonce.h:67:16: error: got :
./include/linux/list.h:292:16: error: typename in expression
./include/linux/list.h:292:16: error: Expected ) in function call
./include/linux/list.h:292:16: error: got :

....

./include/linux/seqlock.h:682:16: error: Expected ) in function call
./include/linux/seqlock.h:682:16: error: got :
./include/linux/seqlock.h:695:16: error: typename in expression
./include/linux/seqlock.h:695:16: error: Expected ) in function call
./include/linux/seqlock.h:695:16: error: too many errors
Segmentation fault (core dumped)
make[3]: *** [scripts/Makefile.build:250: security/landlock/setup.o] 
Error 139
make[3]: *** Deleting file 'security/landlock/setup.o'
make[3]: *** Waiting for unfinished jobs....
Segmentation fault (core dumped)
make[3]: *** [scripts/Makefile.build:250: security/landlock/syscalls.o] 
Error 139
make[3]: *** Deleting file 'security/landlock/syscalls.o'
make[2]: *** [scripts/Makefile.build:502: security/landlock] Error 2
make[1]: *** [scripts/Makefile.build:502: security] Error 2
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:1994: .] Error 2



> 
> Using big-endian values as keys (casted to uintptr_t, not strictly
> __be16) in the rb-tree should not be an issue because there is no port
> range ordering (for now).
> 
> A dedicated test should check that endianness is correct, e.g. by using
> different port encoding. This should include passing and failing tests,
> but they should work on all architectures (i.e. big or little endian).
> 
> 
>> +
>> +} __attribute__((packed));
>> +
>>   /**
>>    * DOC: fs_access
>>    *
>> @@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
>>   #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
>>   /* clang-format on */
>> 
>> +/**
>> + * DOC: net_access
>> + *
>> + * Network flags
>> + * ~~~~~~~~~~~~~~~~
>> + *
>> + * These flags enable to restrict a sandboxed process to a set of network
>> + * actions.
>> + *
>> + * TCP sockets with allowed actions:
>> + *
>> + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
>> + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
>> + *   a remote port.
>> + */
>> +/* clang-format off */
>> +#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
>> +#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
>> +/* clang-format on */
>>   #endif /* _UAPI_LINUX_LANDLOCK_H */
>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>> index bafb3b8dc677..8a1a6463c64e 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -23,6 +23,10 @@
>>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>   #define LANDLOCK_SHIFT_ACCESS_FS	0
>> 
>> -/* clang-format on */
>> +#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
>> +#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
>> +#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>> +#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
>> 
>> +/* clang-format on */
>>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index c7cf54ba4f6d..9277c1295114 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -36,6 +36,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   	refcount_set(&new_ruleset->usage, 1);
>>   	mutex_init(&new_ruleset->lock);
>>   	new_ruleset->root_inode = RB_ROOT;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	new_ruleset->root_net_port = RB_ROOT;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	new_ruleset->num_layers = num_layers;
>>   	/*
>>   	 * hierarchy = NULL
>> @@ -46,16 +49,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>   }
>> 
>>   struct landlock_ruleset *
>> -landlock_create_ruleset(const access_mask_t fs_access_mask)
>> +landlock_create_ruleset(const access_mask_t fs_access_mask,
>> +			const access_mask_t net_access_mask)
>>   {
>>   	struct landlock_ruleset *new_ruleset;
>> 
>>   	/* Informs about useless ruleset. */
>> -	if (!fs_access_mask)
>> +	if (!fs_access_mask && !net_access_mask)
>>   		return ERR_PTR(-ENOMSG);
>>   	new_ruleset = create_ruleset(1);
>> -	if (!IS_ERR(new_ruleset))
>> +	if (IS_ERR(new_ruleset))
>> +		return new_ruleset;
>> +	if (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);
>>   	return new_ruleset;
>>   }
>> 
>> @@ -73,6 +81,10 @@ static inline bool is_object_pointer(const enum landlock_key_type key_type)
>>   	switch (key_type) {
>>   	case LANDLOCK_KEY_INODE:
>>   		return true;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		return false;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	}
>>   	WARN_ON_ONCE(1);
>>   	return false;
>> @@ -126,6 +138,11 @@ static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset,
>>   	case LANDLOCK_KEY_INODE:
>>   		root = &ruleset->root_inode;
>>   		break;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		root = &ruleset->root_net_port;
>> +		break;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	}
>>   	if (WARN_ON_ONCE(!root))
>>   		return ERR_PTR(-EINVAL);
>> @@ -154,7 +171,9 @@ static void build_check_ruleset(void)
>>   	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
>>   	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
>>   	BUILD_BUG_ON(access_masks <
>> -		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
>> +		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) +
> 
> This is correct but because we are dealing with bitmasks I would prefer
> to use "|" instead of "+".
> 
> 
>> +			     (LANDLOCK_MASK_ACCESS_NET
>> +			      << LANDLOCK_SHIFT_ACCESS_NET));
>>   }
>> 
>>   /**
>> @@ -370,7 +389,12 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>>   	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
>>   	if (err)
>>   		goto out_unlock;
>> -
> 
> Please keep this newline.
> 
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/* Merges the @src network port tree. */
>> +	err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
>> +	if (err)
>> +		goto out_unlock;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   out_unlock:
>>   	mutex_unlock(&src->lock);
>>   	mutex_unlock(&dst->lock);
>> @@ -426,7 +450,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>>   	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
>>   	if (err)
>>   		goto out_unlock;
>> -
> 
> newline
> 
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/* Copies the @parent network port tree. */
>> +	err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
>> +	if (err)
>> +		goto out_unlock;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
>>   		err = -EINVAL;
>>   		goto out_unlock;
>> @@ -459,6 +488,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
>>   	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
>>   					     node)
>>   		free_rule(freeme, LANDLOCK_KEY_INODE);
>> +#if IS_ENABLED(CONFIG_INET)
>> +	rbtree_postorder_for_each_entry_safe(freeme, next,
>> +					     &ruleset->root_net_port, node)
>> +		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	put_hierarchy(ruleset->hierarchy);
>>   	kfree(ruleset);
>>   }
>> @@ -637,6 +671,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
>>    * Populates @layer_masks such that for each access right in @access_request,
>>    * the bits for all the layers are set where this access right is handled.
>>    *
>> + * @layer_masks must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET
>> + * elements according to @key_type.
> 
> Please include this sentence in the @layer_masks description below.
> 
>> + *
>>    * @domain: The domain that defines the current restrictions.
>>    * @access_request: The requested access rights to check.
>>    * @layer_masks: The layer masks to populate.
> 
> "It must contain…"
> 
> 
>> @@ -659,6 +696,12 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>>   		get_access_mask = landlock_get_fs_access_mask;
>>   		num_access = LANDLOCK_NUM_ACCESS_FS;
>>   		break;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	case LANDLOCK_KEY_NET_PORT:
>> +		get_access_mask = landlock_get_net_access_mask;
>> +		num_access = LANDLOCK_NUM_ACCESS_NET;
>> +		break;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	default:
>>   		WARN_ON_ONCE(1);
>>   		return 0;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index d9eb79ea9a89..f272d2cd518c 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -19,16 +19,20 @@
>>   #include "limits.h"
>>   #include "object.h"
>> 
>> +/* Rule access mask. */
>>   typedef u16 access_mask_t;
>>   /* Makes sure all filesystem access rights can be stored. */
>>   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 for_each_set_bit() and for_each_clear_bit() calls are OK. */
>>   static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>> 
>>   /* Ruleset access masks. */
>> -typedef u16 access_masks_t;
>> +typedef u32 access_masks_t;
> 
> This type change need to be explained in the commit message.
> 
> 
>>   /* Makes sure all ruleset access rights can be stored. */
>> -static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
>> +static_assert(BITS_PER_TYPE(access_masks_t) >=
>> +	      LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
>> 
>>   typedef u16 layer_mask_t;
>>   /* Makes sure all layers can be checked. */
>> @@ -82,6 +86,13 @@ enum landlock_key_type {
>>   	 * keys.
>>   	 */
>>   	LANDLOCK_KEY_INODE = 1,
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/**
>> +	 * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
>> +	 * node keys.
>> +	 */
>> +	LANDLOCK_KEY_NET_PORT = 2,
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   };
>> 
>>   /**
>> @@ -156,6 +167,15 @@ struct landlock_ruleset {
>>   	 * reaches zero.
>>   	 */
>>   	struct rb_root root_inode;
>> +#if IS_ENABLED(CONFIG_INET)
>> +	/**
>> +	 * @root_net_port: Root of a red-black tree containing &struct
>> +	 * landlock_rule nodes with network port. Once a ruleset is tied to a
>> +	 * process (i.e. as a domain), this tree is immutable until @usage
>> +	 * reaches zero.
>> +	 */
>> +	struct rb_root root_net_port;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>>   	/**
>>   	 * @hierarchy: Enables hierarchy identification even when a parent
>>   	 * domain vanishes.  This is needed for the ptrace protection.
>> @@ -166,8 +186,8 @@ struct landlock_ruleset {
>>   		 * @work_free: Enables to free a ruleset within a lockless
>>   		 * section.  This is only used by
>>   		 * landlock_put_ruleset_deferred() when @usage reaches zero.
>> -		 * The fields @lock, @usage, @num_rules, @num_layers and
>> -		 * @access_masks are then unused.
>> +		 * The fields @lock, @usage, @num_rules, @num_layers,
>> +		 * @net_access_mask and @access_masks are then unused.
> 
> There is no net_access_mask anymore.
> .

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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2022-11-16 14:25   ` Mickaël Salaün
  2022-11-28  2:49     ` Konstantin Meskhidze (A)
@ 2023-01-05  3:46     ` Konstantin Meskhidze (A)
  2023-01-06 19:34       ` Mickaël Salaün
  1 sibling, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-05  3:46 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



11/16/2022 5:25 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This commit adds network demo. It's possible to allow a sandboxer to
>> bind/connect to a list of particular ports restricting network
>> actions to the rest of ports.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Removes network support if ABI < 4.
>> * Removes network support if not set by a user.
>> 
>> Changes since v6:
>> * Removes network support if ABI < 3.
>> 
>> Changes since v5:
>> * Makes network ports sandboxing optional.
>> * Fixes some logic errors.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Adds ENV_TCP_BIND_NAME "LL_TCP_BIND" and
>> ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" variables
>> to insert TCP ports.
>> * Renames populate_ruleset() to populate_ruleset_fs().
>> * Adds populate_ruleset_net() and parse_port_num() helpers.
>> * Refactors main() to support network sandboxing.
>> 
>> ---
>>   samples/landlock/sandboxer.c | 129 +++++++++++++++++++++++++++++++----
>>   1 file changed, 116 insertions(+), 13 deletions(-)
>> 
>> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
>> index fd4237c64fb2..68582b0d7c85 100644
>> --- a/samples/landlock/sandboxer.c
>> +++ b/samples/landlock/sandboxer.c
>> @@ -51,6 +51,8 @@ static inline int landlock_restrict_self(const int ruleset_fd,
>> 
>>   #define ENV_FS_RO_NAME "LL_FS_RO"
>>   #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_PATH_TOKEN ":"
>> 
>>   static int parse_path(char *env_path, const char ***const path_list)
>> @@ -71,6 +73,20 @@ static int parse_path(char *env_path, const char ***const path_list)
>>   	return num_paths;
>>   }
>> 
>> +static int parse_port_num(char *env_port)
>> +{
>> +	int i, num_ports = 0;
>> +
>> +	if (env_port) {
>> +		num_ports++;
>> +		for (i = 0; env_port[i]; i++) {
>> +			if (env_port[i] == ENV_PATH_TOKEN[0])
>> +				num_ports++;
>> +		}
>> +	}
>> +	return num_ports;
>> +}
>> +
>>   /* clang-format off */
>> 
>>   #define ACCESS_FILE ( \
>> @@ -81,8 +97,8 @@ static int parse_path(char *env_path, const char ***const path_list)
>> 
>>   /* clang-format on */
>> 
>> -static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>> -			    const __u64 allowed_access)
>> +static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
>> +			       const __u64 allowed_access)
>>   {
>>   	int num_paths, i, ret = 1;
>>   	char *env_path_name;
>> @@ -143,6 +159,48 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>>   	return ret;
>>   }
>> 
>> +static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>> +				const __u64 allowed_access)
>> +{
>> +	int num_ports, i, ret = 1;
>> +	char *env_port_name;
>> +	struct landlock_net_service_attr net_service = {
>> +		.allowed_access = 0,
>> +		.port = 0,
>> +	};
>> +
>> +	env_port_name = getenv(env_var);
>> +	if (!env_port_name) {
>> +		ret = 0;
>> +		goto out_free_name;
> 
> This is a bug because env_port_name is not allocated. This should simply
> return 0.
> 
> 
>> +	}
>> +	env_port_name = strdup(env_port_name);
>> +	unsetenv(env_var);
>> +	num_ports = parse_port_num(env_port_name);
>> +
>> +	if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) {
>> +		ret = 0;
>> +		goto out_free_name;
>> +	}
>> +
>> +	for (i = 0; i < num_ports; i++) {
>> +		net_service.allowed_access = allowed_access;
>> +		net_service.port = atoi(strsep(&env_port_name, ENV_PATH_TOKEN));
>> +		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +				      &net_service, 0)) {
>> +			fprintf(stderr,
>> +				"Failed to update the ruleset with port \"%d\": %s\n",
>> +				net_service.port, strerror(errno));
>> +			goto out_free_name;
>> +		}
>> +	}
>> +	ret = 0;
>> +
>> +out_free_name:
>> +	free(env_port_name);
>> +	return ret;
>> +}
>> +
>>   /* clang-format off */
>> 
>>   #define ACCESS_FS_ROUGHLY_READ ( \
>> @@ -164,41 +222,63 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
>>   	LANDLOCK_ACCESS_FS_REFER | \
>>   	LANDLOCK_ACCESS_FS_TRUNCATE)
>> 
>> +#define ACCESS_NET_BIND_CONNECT ( \
>> +	LANDLOCK_ACCESS_NET_BIND_TCP | \
>> +	LANDLOCK_ACCESS_NET_CONNECT_TCP)
> 
> You can remove ACCESS_NET_BIND_CONNECT and make the underlying access
> rights explicit.
> 
> 
>> +
>>   /* clang-format on */
>> 
>> -#define LANDLOCK_ABI_LAST 3
>> +#define LANDLOCK_ABI_LAST 4
>> 
>>   int main(const int argc, char *const argv[], char *const *const envp)
>>   {
>>   	const char *cmd_path;
>>   	char *const *cmd_argv;
>>   	int ruleset_fd, abi;
>> +	char *env_port_name;
>>   	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
>> -	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
>> +	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE,
>> +	      access_net_tcp = ACCESS_NET_BIND_CONNECT;
>>   	struct landlock_ruleset_attr ruleset_attr = {
>>   		.handled_access_fs = access_fs_rw,
>> +		.handled_access_net = access_net_tcp,
>>   	};
>> 
>>   	if (argc < 2) {
>>   		fprintf(stderr,
>> -			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
>> -			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
>> +			"usage: %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]);
>>   		fprintf(stderr,
>>   			"Launch a command in a restricted environment.\n\n");
>> -		fprintf(stderr, "Environment variables containing paths, "
>> -				"each separated by a colon:\n");
>> +		fprintf(stderr,
>> +			"Environment variables containing paths and ports "
>> +			"each separated by a colon:\n");
>>   		fprintf(stderr,
>>   			"* %s: list of paths allowed to be used in a read-only way.\n",
>>   			ENV_FS_RO_NAME);
>>   		fprintf(stderr,
>> -			"* %s: list of paths allowed to be used in a read-write way.\n",
>> +			"* %s: list of paths allowed to be used in a read-write way.\n\n",
>>   			ENV_FS_RW_NAME);
>> +		fprintf(stderr,
>> +			"Environment variables containing ports are optional "
>> +			"and could be skipped.\n");
>> +		fprintf(stderr,
>> +			"* %s: list of ports allowed to bind (server).\n",
>> +			ENV_TCP_BIND_NAME);
>> +		fprintf(stderr,
>> +			"* %s: list of ports allowed to connect (client).\n",
>> +			ENV_TCP_CONNECT_NAME);
>>   		fprintf(stderr,
>>   			"\nexample:\n"
>>   			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
>>   			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
>> +			"%s=\"9418\" "
>> +			"%s=\"80:443\" "
>>   			"%s bash -i\n\n",
>> -			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
>> +			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
>> +			ENV_TCP_CONNECT_NAME, argv[0]);
>>   		fprintf(stderr,
>>   			"This sandboxer can use Landlock features "
>>   			"up to ABI version %d.\n",
>> @@ -240,7 +320,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>   	case 2:
>>   		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>>   		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
>> -
>> +		__attribute__((fallthrough));
>> +	case 3:
>> +		/* Removes network support for ABI < 4 */
>> +		ruleset_attr.handled_access_net &= ~ACCESS_NET_BIND_CONNECT;
> 
> You can check the TCP environment variables here and error out if one is
> set.
> 
> Please keep the newline here.
> 
> 
>>   		fprintf(stderr,
>>   			"Hint: You should update the running kernel "
>>   			"to leverage Landlock features "
>> @@ -259,16 +342,36 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>   	access_fs_ro &= ruleset_attr.handled_access_fs;
>>   	access_fs_rw &= ruleset_attr.handled_access_fs;
>> 
>> +	/* Removes bind access attribute if not supported by a user. */
>> +	env_port_name = getenv(ENV_TCP_BIND_NAME);
>> +	if (!env_port_name) {
> 
> You can move this logic at the populate_ruleset_net() call site and
> update this helper to not call getenv() twice for the same variable.

   But here I exclude ruleset attributes, not rule itself. It will break
   the logic: creating a ruleset then applying rules.
   I suggest to leave here as its.
> 
> 
>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
>> +	}
>> +	/* Removes connect access attribute if not supported by a user. */
>> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
>> +	if (!env_port_name) {
>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>> +	}
>> +	ruleset_attr.handled_access_net &= access_net_tcp;
> 
> There is no need for access_net_tcp.
> 
>> +
>>   	ruleset_fd =
>>   		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>>   	if (ruleset_fd < 0) {
>>   		perror("Failed to create a ruleset");
>>   		return 1;
>>   	}
> 
> newline
> 
>> -	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
>> +	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
>> +		goto err_close_ruleset;
>> +	}
>> +	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
>> +		goto err_close_ruleset;
>> +	}
> 
> newline
> 
>> +	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
>> +				 LANDLOCK_ACCESS_NET_BIND_TCP)) {
>>   		goto err_close_ruleset;
>>   	}
>> -	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
>> +	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
>> +				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
>>   		goto err_close_ruleset;
>>   	}
> 
> newline
> 
>>   	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2022-11-17 18:43   ` Mickaël Salaün
  2022-11-28  8:21     ` Konstantin Meskhidze (A)
@ 2023-01-05  8:57     ` Konstantin Meskhidze (A)
  2023-01-06 19:30       ` Mickaël Salaün
  1 sibling, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-05  8:57 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, linux-api,
	Alejandro Colomar (man-pages)



11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> This patch adds support of socket_bind() and socket_connect() hooks.
>> It's possible to restrict binding and connecting of TCP sockets to
>> particular ports.
> 
> Implement socket_bind() and socket_connect LSM hooks, which enable to
> restrict TCP socket binding and connection to specific ports.
> 
> 
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Minor fixes.
>> * Refactors commit message.
>> 
>> Changes since v6:
>> * Updates copyright.
>> * Refactors landlock_append_net_rule() and check_socket_access()
>>    functions with landlock_id type.
>> 
>> Changes since v5:
>> * Fixes some logic errors.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Factors out CONFIG_INET into make file.
>> * Refactors check_socket_access().
>> * Adds helper get_port().
>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>> functions to support AF_INET6 family.
>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>> functions.
>> * Refactors add_rule_net_service() and landlock_add_rule
>> syscall to support network rule inserting.
>> * Refactors init_layer_masks() to support network rules.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds SECURITY_NETWORK in config.
>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>> * Adds hook_socket_bind and hook_socket_connect hooks.
>> 
>> ---
>>   security/landlock/Kconfig    |   1 +
>>   security/landlock/Makefile   |   2 +
>>   security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>   security/landlock/net.h      |  26 ++++++
>>   security/landlock/setup.c    |   2 +
>>   security/landlock/syscalls.c |  59 ++++++++++++-
>>   6 files changed, 251 insertions(+), 3 deletions(-)
>>   create mode 100644 security/landlock/net.c
>>   create mode 100644 security/landlock/net.h
>> 
>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>> index 8e33c4e8ffb8..10c099097533 100644
>> --- a/security/landlock/Kconfig
>> +++ b/security/landlock/Kconfig
>> @@ -3,6 +3,7 @@
>>   config SECURITY_LANDLOCK
>>   	bool "Landlock support"
>>   	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>> +	select SECURITY_NETWORK
>>   	select SECURITY_PATH
>>   	help
>>   	  Landlock is a sandboxing mechanism that enables processes to restrict
>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>> index 7bbd2f413b3e..53d3c92ae22e 100644
>> --- a/security/landlock/Makefile
>> +++ b/security/landlock/Makefile
>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>> 
>>   landlock-y := setup.o syscalls.o object.o ruleset.o \
>>   	cred.o ptrace.o fs.o
>> +
>> +landlock-$(CONFIG_INET) += net.o
>> \ No newline at end of file
>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>> new file mode 100644
>> index 000000000000..39e8a156a1f4
>> --- /dev/null
>> +++ b/security/landlock/net.c
>> @@ -0,0 +1,164 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>> + * Copyright © 2022 Microsoft Corporation
>> + */
>> +
>> +#include <linux/in.h>
>> +#include <linux/net.h>
>> +#include <linux/socket.h>
>> +#include <net/ipv6.h>
>> +
>> +#include "common.h"
>> +#include "cred.h"
>> +#include "limits.h"
>> +#include "net.h"
>> +#include "ruleset.h"
>> +
>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>> +			     const u16 port, access_mask_t access_rights)
>> +{
>> +	int err;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>> +
>> +	/* Transforms relative access rights to absolute ones. */
>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>> +
>> +	mutex_lock(&ruleset->lock);
>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>> +	mutex_unlock(&ruleset->lock);
>> +
>> +	return err;
>> +}
>> +
>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>> +			       u16 port, access_mask_t access_request)
>> +{
>> +	bool allowed = false;
>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>> +	const struct landlock_rule *rule;
>> +	access_mask_t handled_access;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +
>> +	if (WARN_ON_ONCE(!domain))
>> +		return 0;
>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>> +		return -EACCES;
>> +
>> +	rule = landlock_find_rule(domain, id);
>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>> +					  LANDLOCK_KEY_NET_PORT);
>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>> +				ARRAY_SIZE(layer_masks));
>> +
>> +	return allowed ? 0 : -EACCES;
>> +}
>> +
>> +static u16 get_port(const struct sockaddr *const address)
> 
> get_port() should return a __be16 type. This enables to avoid converting
> port when checking a rule.

   In this case a user must do a coverting port into __be16:

   struct landlock_net_service_attr net_service = {
                 .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,

                 .port = htons(sock_port),
         };
  I think that a user should not think about this conversion cause it 
makes UAPI more complex to use. Lets do this under kernel's hood and let 
it as it is now -> u16 port.

What do you think?


> 
> make C=2 security/landlock/ must not print any warning.
> 
> 
>> +{
>> +	/* Gets port value in host byte order. */
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +	case AF_INET: {
>> +		const struct sockaddr_in *const sockaddr =
>> +			(struct sockaddr_in *)address;
>> +		return ntohs(sockaddr->sin_port);
>> +	}
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6: {
>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>> +			(struct sockaddr_in6 *)address;
>> +		return ntohs(sockaddr_ip6->sin6_port);
>> +	}
>> +#endif
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>> +			    int addrlen)
>> +{
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
>> +
>> +	if (!dom)
>> +		return 0;
>> +
>> +	/* Check if it's a TCP socket. */
>> +	if (sock->type != SOCK_STREAM)
>> +		return 0;
>> +
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		return check_socket_access(dom, get_port(address),
>> +					   LANDLOCK_ACCESS_NET_BIND_TCP);
>> +	default:
>> +		return 0;
> 
> You can remove this default case and move the return 0 at the end of the
> function.
> 
> 
>> +	}
>> +}
>> +
>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>> +			       int addrlen)
>> +{
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
>> +
>> +	if (!dom)
>> +		return 0;
>> +
>> +	/* Check if it's a TCP socket. */
>> +	if (sock->type != SOCK_STREAM)
>> +		return 0;
>> +
>> +	/* Check if the hook is AF_INET* socket's action. */
>> +	switch (address->sa_family) {
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		return check_socket_access(dom, get_port(address),
>> +					   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>> +	case AF_UNSPEC: {
>> +		u16 i;
> 
> You can move "i" after the "dom" declaration to remove the extra braces.
> 
> 
>> +
>> +		/*
>> +		 * If just in a layer a mask supports connect access,
>> +		 * the socket_connect() hook with AF_UNSPEC family flag
>> +		 * must be banned. This prevents from disconnecting already
>> +		 * connected sockets.
>> +		 */
>> +		for (i = 0; i < dom->num_layers; i++) {
>> +			if (landlock_get_net_access_mask(dom, i) &
>> +			    LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> +				return -EACCES;
> 
> I'm wondering if this is the right error code for this case. EPERM may
> be more appropriate.
> 
> Thinking more about this case, I don't understand what is the rationale
> to deny such action. What would be the consequence to always allow
> connection with AF_UNSPEC (i.e. to disconnect a socket)?
> 
> 
>> +		}
>> +	}
>> +	}
>> +	return 0;
>> +}
>> +
>> +static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
>> +	LSM_HOOK_INIT(socket_bind, hook_socket_bind),
>> +	LSM_HOOK_INIT(socket_connect, hook_socket_connect),
>> +};
>> +
>> +__init void landlock_add_net_hooks(void)
>> +{
>> +	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
>> +			   LANDLOCK_NAME);
>> +}
>> diff --git a/security/landlock/net.h b/security/landlock/net.h
>> new file mode 100644
>> index 000000000000..0da1d9dff5ab
>> --- /dev/null
>> +++ b/security/landlock/net.h
>> @@ -0,0 +1,26 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>> + */
>> +
>> +#ifndef _SECURITY_LANDLOCK_NET_H
>> +#define _SECURITY_LANDLOCK_NET_H
>> +
>> +#include "common.h"
>> +#include "ruleset.h"
>> +#include "setup.h"
>> +
>> +#if IS_ENABLED(CONFIG_INET)
>> +__init void landlock_add_net_hooks(void);
>> +
>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>> +			     const u16 port, access_mask_t access_rights);
>> +#else /* IS_ENABLED(CONFIG_INET) */
>> +static inline void landlock_add_net_hooks(void)
>> +{
>> +}
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>> +
>> +#endif /* _SECURITY_LANDLOCK_NET_H */
>> diff --git a/security/landlock/setup.c b/security/landlock/setup.c
>> index 3f196d2ce4f9..7e4a598177b8 100644
>> --- a/security/landlock/setup.c
>> +++ b/security/landlock/setup.c
>> @@ -14,6 +14,7 @@
>>   #include "fs.h"
>>   #include "ptrace.h"
>>   #include "setup.h"
>> +#include "net.h"
>> 
>>   bool landlock_initialized __lsm_ro_after_init = false;
>> 
>> @@ -29,6 +30,7 @@ static int __init landlock_init(void)
>>   	landlock_add_cred_hooks();
>>   	landlock_add_ptrace_hooks();
>>   	landlock_add_fs_hooks();
>> +	landlock_add_net_hooks();
>>   	landlock_initialized = true;
>>   	pr_info("Up and running.\n");
>>   	return 0;
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index c5a6ad4e2fca..7853f32e8325 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -29,6 +29,7 @@
>>   #include "cred.h"
>>   #include "fs.h"
>>   #include "limits.h"
>> +#include "net.h"
>>   #include "ruleset.h"
>>   #include "setup.h"
>> 
>> @@ -74,7 +75,8 @@ static void build_check_abi(void)
>>   {
>>   	struct landlock_ruleset_attr ruleset_attr;
>>   	struct landlock_path_beneath_attr path_beneath_attr;
>> -	size_t ruleset_size, path_beneath_size;
>> +	struct landlock_net_service_attr net_service_attr;
>> +	size_t ruleset_size, path_beneath_size, net_service_size;
>> 
>>   	/*
>>   	 * For each user space ABI structures, first checks that there is no
>> @@ -90,6 +92,11 @@ static void build_check_abi(void)
>>   	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
>>   	BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
>>   	BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
>> +
>> +	net_service_size = sizeof(net_service_attr.allowed_access);
>> +	net_service_size += sizeof(net_service_attr.port);
>> +	BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size);
>> +	BUILD_BUG_ON(sizeof(net_service_attr) != 10);
>>   }
>> 
>>   /* Ruleset handling */
>> @@ -322,13 +329,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>>   	return err;
>>   }
>> 
>> +static int add_rule_net_service(struct landlock_ruleset *ruleset,
>> +				const void __user *const rule_attr)
>> +{
>> +#if IS_ENABLED(CONFIG_INET)
>> +	struct landlock_net_service_attr net_service_attr;
>> +	int res;
>> +	u32 mask;
> 
> access_mask_t mask;
> 
> 
>> +
>> +	/* Copies raw user space buffer, only one type for now. */
>> +	res = copy_from_user(&net_service_attr, rule_attr,
>> +			     sizeof(net_service_attr));
>> +	if (res)
>> +		return -EFAULT;
>> +
>> +	/*
>> +	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
>> +	 * are ignored by network actions.
>> +	 */
>> +	if (!net_service_attr.allowed_access)
>> +		return -ENOMSG;
>> +
>> +	/*
>> +	 * Checks that allowed_access matches the @ruleset constraints
>> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>> +	 */
>> +	mask = landlock_get_net_access_mask(ruleset, 0);
>> +	if ((net_service_attr.allowed_access | mask) != mask)
>> +		return -EINVAL;
>> +
>> +	/* Denies inserting a rule with port 0. */
>> +	if (net_service_attr.port == 0)
>> +		return -EINVAL;
>> +
>> +	/* Imports the new rule. */
>> +	return landlock_append_net_rule(ruleset, net_service_attr.port,
>> +					net_service_attr.allowed_access);
>> +#else /* IS_ENABLED(CONFIG_INET) */
>> +	return -EAFNOSUPPORT;
>> +#endif /* IS_ENABLED(CONFIG_INET) */
>> +}
>> +
>>   /**
>>    * sys_landlock_add_rule - Add a new rule to a ruleset
>>    *
>>    * @ruleset_fd: File descriptor tied to the ruleset that should be extended
>>    *		with the new rule.
>> - * @rule_type: Identify the structure type pointed to by @rule_attr (only
>> - *             %LANDLOCK_RULE_PATH_BENEATH for now).
>> + * @rule_type: Identify the structure type pointed to by @rule_attr:
>> + *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_SERVICE.
>>    * @rule_attr: Pointer to a rule (only of type &struct
>>    *             landlock_path_beneath_attr for now).
>>    * @flags: Must be 0.
>> @@ -339,6 +387,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>>    * Possible returned errors are:
>>    *
>>    * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
>> + * - %EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not
> 
> %LANDLOCK_RULE_NET_SERVICE
> 
> 
>> + *   supported by the running kernel;
>>    * - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
>>    *   &landlock_path_beneath_attr.allowed_access is not a subset of the
>>    *   ruleset handled accesses);
>> @@ -373,6 +423,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>>   	case LANDLOCK_RULE_PATH_BENEATH:
>>   		err = add_rule_path_beneath(ruleset, rule_attr);
>>   		break;
>> +	case LANDLOCK_RULE_NET_SERVICE:
>> +		err = add_rule_net_service(ruleset, rule_attr);
>> +		break;
>>   	default:
>>   		err = -EINVAL;
>>   		break;
>> --
>> 2.25.1
>> 
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-04 11:41     ` Konstantin Meskhidze (A)
@ 2023-01-06 19:22       ` Mickaël Salaün
  2023-01-09  7:59         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-06 19:22 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), linux-sparse
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)


On 04/01/2023 12:41, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:43 PM, Mickaël Salaün пишет:

[...]

>>>    /**
>>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>>    	 */
>>>    } __attribute__((packed));
>>>
>>> +/**
>>> + * struct landlock_net_service_attr - TCP subnet definition
>>> + *
>>> + * Argument of sys_landlock_add_rule().
>>> + */
>>> +struct landlock_net_service_attr {
>>> +	/**
>>> +	 * @allowed_access: Bitmask of allowed access network for services
>>> +	 * (cf. `Network flags`_).
>>> +	 */
>>> +	__u64 allowed_access;
>>> +	/**
>>> +	 * @port: Network port.
>>> +	 */
>>> +	__u16 port;
>>
>>    From an UAPI point of view, I think the port field should be __be16, as
>> for sockaddr_in->port and other network-related APIs. This will require
>> some kernel changes to please sparse: make C=2 security/landlock/ must
>> not print any warning.
> 
>     I have this errors trying to launch sparse checking:
> 
>     DESCEND objtool
>     DESCEND bpf/resolve_btfids
>     CALL    scripts/checksyscalls.sh
>     CHK     kernel/kheaders_data.tar.xz
>     CC      security/landlock/setup.o
>     CHECK   security/landlock/setup.c
> ./include/asm-generic/rwonce.h:67:16: error: typename in expression
> ./include/asm-generic/rwonce.h:67:16: error: Expected ) in function call
> ./include/asm-generic/rwonce.h:67:16: error: got :
> ./include/linux/list.h:292:16: error: typename in expression
> ./include/linux/list.h:292:16: error: Expected ) in function call
> ./include/linux/list.h:292:16: error: got :
> 
> ....
> 
> ./include/linux/seqlock.h:682:16: error: Expected ) in function call
> ./include/linux/seqlock.h:682:16: error: got :
> ./include/linux/seqlock.h:695:16: error: typename in expression
> ./include/linux/seqlock.h:695:16: error: Expected ) in function call
> ./include/linux/seqlock.h:695:16: error: too many errors
> Segmentation fault (core dumped)
> make[3]: *** [scripts/Makefile.build:250: security/landlock/setup.o]
> Error 139
> make[3]: *** Deleting file 'security/landlock/setup.o'
> make[3]: *** Waiting for unfinished jobs....
> Segmentation fault (core dumped)
> make[3]: *** [scripts/Makefile.build:250: security/landlock/syscalls.o]
> Error 139
> make[3]: *** Deleting file 'security/landlock/syscalls.o'
> make[2]: *** [scripts/Makefile.build:502: security/landlock] Error 2
> make[1]: *** [scripts/Makefile.build:502: security] Error 2
> make[1]: *** Waiting for unfinished jobs....
> make: *** [Makefile:1994: .] Error 2

I don't know about this error. Did you follow the documentation?
https://docs.kernel.org/dev-tools/sparse.html#getting-sparse



>>
>> Using big-endian values as keys (casted to uintptr_t, not strictly
>> __be16) in the rb-tree should not be an issue because there is no port
>> range ordering (for now).
>>
>> A dedicated test should check that endianness is correct, e.g. by using
>> different port encoding. This should include passing and failing tests,
>> but they should work on all architectures (i.e. big or little endian).

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2023-01-05  8:57     ` Konstantin Meskhidze (A)
@ 2023-01-06 19:30       ` Mickaël Salaün
  2023-01-09  8:07         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-06 19:30 UTC (permalink / raw)
  To: Konstantin Meskhidze (A),
	netdev, linux-api, Alejandro Colomar (man-pages)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module,
	netfilter-devel, artem.kuzin


On 05/01/2023 09:57, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>> It's possible to restrict binding and connecting of TCP sockets to
>>> particular ports.
>>
>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>> restrict TCP socket binding and connection to specific ports.
>>
>>
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v7:
>>> * Minor fixes.
>>> * Refactors commit message.
>>>
>>> Changes since v6:
>>> * Updates copyright.
>>> * Refactors landlock_append_net_rule() and check_socket_access()
>>>     functions with landlock_id type.
>>>
>>> Changes since v5:
>>> * Fixes some logic errors.
>>> * Formats code with clang-format-14.
>>>
>>> Changes since v4:
>>> * Factors out CONFIG_INET into make file.
>>> * Refactors check_socket_access().
>>> * Adds helper get_port().
>>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>>> functions to support AF_INET6 family.
>>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>>> functions.
>>> * Refactors add_rule_net_service() and landlock_add_rule
>>> syscall to support network rule inserting.
>>> * Refactors init_layer_masks() to support network rules.
>>>
>>> Changes since v3:
>>> * Splits commit.
>>> * Adds SECURITY_NETWORK in config.
>>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>>> * Adds hook_socket_bind and hook_socket_connect hooks.
>>>
>>> ---
>>>    security/landlock/Kconfig    |   1 +
>>>    security/landlock/Makefile   |   2 +
>>>    security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>>    security/landlock/net.h      |  26 ++++++
>>>    security/landlock/setup.c    |   2 +
>>>    security/landlock/syscalls.c |  59 ++++++++++++-
>>>    6 files changed, 251 insertions(+), 3 deletions(-)
>>>    create mode 100644 security/landlock/net.c
>>>    create mode 100644 security/landlock/net.h
>>>
>>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>>> index 8e33c4e8ffb8..10c099097533 100644
>>> --- a/security/landlock/Kconfig
>>> +++ b/security/landlock/Kconfig
>>> @@ -3,6 +3,7 @@
>>>    config SECURITY_LANDLOCK
>>>    	bool "Landlock support"
>>>    	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>>> +	select SECURITY_NETWORK
>>>    	select SECURITY_PATH
>>>    	help
>>>    	  Landlock is a sandboxing mechanism that enables processes to restrict
>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>> index 7bbd2f413b3e..53d3c92ae22e 100644
>>> --- a/security/landlock/Makefile
>>> +++ b/security/landlock/Makefile
>>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>>>
>>>    landlock-y := setup.o syscalls.o object.o ruleset.o \
>>>    	cred.o ptrace.o fs.o
>>> +
>>> +landlock-$(CONFIG_INET) += net.o
>>> \ No newline at end of file
>>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>>> new file mode 100644
>>> index 000000000000..39e8a156a1f4
>>> --- /dev/null
>>> +++ b/security/landlock/net.c
>>> @@ -0,0 +1,164 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Landlock LSM - Network management and hooks
>>> + *
>>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>>> + * Copyright © 2022 Microsoft Corporation
>>> + */
>>> +
>>> +#include <linux/in.h>
>>> +#include <linux/net.h>
>>> +#include <linux/socket.h>
>>> +#include <net/ipv6.h>
>>> +
>>> +#include "common.h"
>>> +#include "cred.h"
>>> +#include "limits.h"
>>> +#include "net.h"
>>> +#include "ruleset.h"
>>> +
>>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>>> +			     const u16 port, access_mask_t access_rights)
>>> +{
>>> +	int err;
>>> +	const struct landlock_id id = {
>>> +		.key.data = port,
>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>> +	};
>>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>>> +
>>> +	/* Transforms relative access rights to absolute ones. */
>>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>>> +
>>> +	mutex_lock(&ruleset->lock);
>>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>> +	mutex_unlock(&ruleset->lock);
>>> +
>>> +	return err;
>>> +}
>>> +
>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>> +			       u16 port, access_mask_t access_request)
>>> +{
>>> +	bool allowed = false;
>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>> +	const struct landlock_rule *rule;
>>> +	access_mask_t handled_access;
>>> +	const struct landlock_id id = {
>>> +		.key.data = port,
>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>> +	};
>>> +
>>> +	if (WARN_ON_ONCE(!domain))
>>> +		return 0;
>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>> +		return -EACCES;
>>> +
>>> +	rule = landlock_find_rule(domain, id);
>>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>>> +					  LANDLOCK_KEY_NET_PORT);
>>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>>> +				ARRAY_SIZE(layer_masks));
>>> +
>>> +	return allowed ? 0 : -EACCES;
>>> +}
>>> +
>>> +static u16 get_port(const struct sockaddr *const address)
>>
>> get_port() should return a __be16 type. This enables to avoid converting
>> port when checking a rule.
> 
>     In this case a user must do a coverting port into __be16:
> 
>     struct landlock_net_service_attr net_service = {
>                   .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> 
>                   .port = htons(sock_port),
>           };
>    I think that a user should not think about this conversion cause it
> makes UAPI more complex to use. Lets do this under kernel's hood and let
> it as it is now -> u16 port.
> 
> What do you think?

BE and LE conversions may be error prone without strong typing, but the 
current Linux network UAPI uses this convention (see related syscalls), 
so developers already use htons() in their applications. I think it is 
less hazardous to use the same convention. It would be nice to have the 
point of view of network and API folks though.

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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2023-01-05  3:46     ` Konstantin Meskhidze (A)
@ 2023-01-06 19:34       ` Mickaël Salaün
  2023-01-09  7:57         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-06 19:34 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 05/01/2023 04:46, Konstantin Meskhidze (A) wrote:
> 
> 
> 11/16/2022 5:25 PM, Mickaël Salaün пишет:

[...]

>>
>>>    		fprintf(stderr,
>>>    			"Hint: You should update the running kernel "
>>>    			"to leverage Landlock features "
>>> @@ -259,16 +342,36 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>>    	access_fs_ro &= ruleset_attr.handled_access_fs;
>>>    	access_fs_rw &= ruleset_attr.handled_access_fs;
>>>
>>> +	/* Removes bind access attribute if not supported by a user. */
>>> +	env_port_name = getenv(ENV_TCP_BIND_NAME);
>>> +	if (!env_port_name) {
>>
>> You can move this logic at the populate_ruleset_net() call site and
>> update this helper to not call getenv() twice for the same variable.
> 
>     But here I exclude ruleset attributes, not rule itself. It will break
>     the logic: creating a ruleset then applying rules.
>     I suggest to leave here as its.

Right, but you can still avoid the duplicate getenv() calls.


>>
>>
>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
>>> +	}
>>> +	/* Removes connect access attribute if not supported by a user. */
>>> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
>>> +	if (!env_port_name) {
>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>>> +	}
>>> +	ruleset_attr.handled_access_net &= access_net_tcp;

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

* Re: [PATCH v8 11/12] samples/landlock: Add network demo
  2023-01-06 19:34       ` Mickaël Salaün
@ 2023-01-09  7:57         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-09  7:57 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



1/6/2023 10:34 PM, Mickaël Salaün пишет:
> 
> On 05/01/2023 04:46, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/16/2022 5:25 PM, Mickaël Salaün пишет:
> 
> [...]
> 
>>>
>>>>    		fprintf(stderr,
>>>>    			"Hint: You should update the running kernel "
>>>>    			"to leverage Landlock features "
>>>> @@ -259,16 +342,36 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>>>    	access_fs_ro &= ruleset_attr.handled_access_fs;
>>>>    	access_fs_rw &= ruleset_attr.handled_access_fs;
>>>>
>>>> +	/* Removes bind access attribute if not supported by a user. */
>>>> +	env_port_name = getenv(ENV_TCP_BIND_NAME);
>>>> +	if (!env_port_name) {
>>>
>>> You can move this logic at the populate_ruleset_net() call site and
>>> update this helper to not call getenv() twice for the same variable.
>> 
>>     But here I exclude ruleset attributes, not rule itself. It will break
>>     the logic: creating a ruleset then applying rules.
>>     I suggest to leave here as its.
> 
> Right, but you can still avoid the duplicate getenv() calls.

   OK. Will fix it.
> 
> 
>>>
>>>
>>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_BIND_TCP;
>>>> +	}
>>>> +	/* Removes connect access attribute if not supported by a user. */
>>>> +	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
>>>> +	if (!env_port_name) {
>>>> +		access_net_tcp &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>>>> +	}
>>>> +	ruleset_attr.handled_access_net &= access_net_tcp;
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-06 19:22       ` Mickaël Salaün
@ 2023-01-09  7:59         ` Konstantin Meskhidze (A)
  2023-01-09  8:58           ` Dan Carpenter
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-09  7:59 UTC (permalink / raw)
  To: Mickaël Salaün, linux-sparse
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin, Linux API,
	Alejandro Colomar (man-pages)



1/6/2023 10:22 PM, Mickaël Salaün пишет:
> 
> On 04/01/2023 12:41, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
> 
> [...]
> 
>>>>    /**
>>>> @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr {
>>>>    	 */
>>>>    } __attribute__((packed));
>>>>
>>>> +/**
>>>> + * struct landlock_net_service_attr - TCP subnet definition
>>>> + *
>>>> + * Argument of sys_landlock_add_rule().
>>>> + */
>>>> +struct landlock_net_service_attr {
>>>> +	/**
>>>> +	 * @allowed_access: Bitmask of allowed access network for services
>>>> +	 * (cf. `Network flags`_).
>>>> +	 */
>>>> +	__u64 allowed_access;
>>>> +	/**
>>>> +	 * @port: Network port.
>>>> +	 */
>>>> +	__u16 port;
>>>
>>>    From an UAPI point of view, I think the port field should be __be16, as
>>> for sockaddr_in->port and other network-related APIs. This will require
>>> some kernel changes to please sparse: make C=2 security/landlock/ must
>>> not print any warning.
>> 
>>     I have this errors trying to launch sparse checking:
>> 
>>     DESCEND objtool
>>     DESCEND bpf/resolve_btfids
>>     CALL    scripts/checksyscalls.sh
>>     CHK     kernel/kheaders_data.tar.xz
>>     CC      security/landlock/setup.o
>>     CHECK   security/landlock/setup.c
>> ./include/asm-generic/rwonce.h:67:16: error: typename in expression
>> ./include/asm-generic/rwonce.h:67:16: error: Expected ) in function call
>> ./include/asm-generic/rwonce.h:67:16: error: got :
>> ./include/linux/list.h:292:16: error: typename in expression
>> ./include/linux/list.h:292:16: error: Expected ) in function call
>> ./include/linux/list.h:292:16: error: got :
>> 
>> ....
>> 
>> ./include/linux/seqlock.h:682:16: error: Expected ) in function call
>> ./include/linux/seqlock.h:682:16: error: got :
>> ./include/linux/seqlock.h:695:16: error: typename in expression
>> ./include/linux/seqlock.h:695:16: error: Expected ) in function call
>> ./include/linux/seqlock.h:695:16: error: too many errors
>> Segmentation fault (core dumped)
>> make[3]: *** [scripts/Makefile.build:250: security/landlock/setup.o]
>> Error 139
>> make[3]: *** Deleting file 'security/landlock/setup.o'
>> make[3]: *** Waiting for unfinished jobs....
>> Segmentation fault (core dumped)
>> make[3]: *** [scripts/Makefile.build:250: security/landlock/syscalls.o]
>> Error 139
>> make[3]: *** Deleting file 'security/landlock/syscalls.o'
>> make[2]: *** [scripts/Makefile.build:502: security/landlock] Error 2
>> make[1]: *** [scripts/Makefile.build:502: security] Error 2
>> make[1]: *** Waiting for unfinished jobs....
>> make: *** [Makefile:1994: .] Error 2
> 
> I don't know about this error. Did you follow the documentation?
> https://docs.kernel.org/dev-tools/sparse.html#getting-sparse
> 
   Yes, I did as in the documentation. that's strange.
If you dont mind can you please check it when I sent a new patch?

> 
> 
>>>
>>> Using big-endian values as keys (casted to uintptr_t, not strictly
>>> __be16) in the rb-tree should not be an issue because there is no port
>>> range ordering (for now).
>>>
>>> A dedicated test should check that endianness is correct, e.g. by using
>>> different port encoding. This should include passing and failing tests,
>>> but they should work on all architectures (i.e. big or little endian).
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2023-01-06 19:30       ` Mickaël Salaün
@ 2023-01-09  8:07         ` Konstantin Meskhidze (A)
  2023-01-09 12:38           ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-09  8:07 UTC (permalink / raw)
  To: Mickaël Salaün, netdev, linux-api,
	Alejandro Colomar (man-pages)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module,
	netfilter-devel, artem.kuzin



1/6/2023 10:30 PM, Mickaël Salaün пишет:
> 
> On 05/01/2023 09:57, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>> particular ports.
>>>
>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>> restrict TCP socket binding and connection to specific ports.
>>>
>>>
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v7:
>>>> * Minor fixes.
>>>> * Refactors commit message.
>>>>
>>>> Changes since v6:
>>>> * Updates copyright.
>>>> * Refactors landlock_append_net_rule() and check_socket_access()
>>>>     functions with landlock_id type.
>>>>
>>>> Changes since v5:
>>>> * Fixes some logic errors.
>>>> * Formats code with clang-format-14.
>>>>
>>>> Changes since v4:
>>>> * Factors out CONFIG_INET into make file.
>>>> * Refactors check_socket_access().
>>>> * Adds helper get_port().
>>>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>>>> functions to support AF_INET6 family.
>>>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>>>> functions.
>>>> * Refactors add_rule_net_service() and landlock_add_rule
>>>> syscall to support network rule inserting.
>>>> * Refactors init_layer_masks() to support network rules.
>>>>
>>>> Changes since v3:
>>>> * Splits commit.
>>>> * Adds SECURITY_NETWORK in config.
>>>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>>>> * Adds hook_socket_bind and hook_socket_connect hooks.
>>>>
>>>> ---
>>>>    security/landlock/Kconfig    |   1 +
>>>>    security/landlock/Makefile   |   2 +
>>>>    security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>>>    security/landlock/net.h      |  26 ++++++
>>>>    security/landlock/setup.c    |   2 +
>>>>    security/landlock/syscalls.c |  59 ++++++++++++-
>>>>    6 files changed, 251 insertions(+), 3 deletions(-)
>>>>    create mode 100644 security/landlock/net.c
>>>>    create mode 100644 security/landlock/net.h
>>>>
>>>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>>>> index 8e33c4e8ffb8..10c099097533 100644
>>>> --- a/security/landlock/Kconfig
>>>> +++ b/security/landlock/Kconfig
>>>> @@ -3,6 +3,7 @@
>>>>    config SECURITY_LANDLOCK
>>>>    	bool "Landlock support"
>>>>    	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>>>> +	select SECURITY_NETWORK
>>>>    	select SECURITY_PATH
>>>>    	help
>>>>    	  Landlock is a sandboxing mechanism that enables processes to restrict
>>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>>> index 7bbd2f413b3e..53d3c92ae22e 100644
>>>> --- a/security/landlock/Makefile
>>>> +++ b/security/landlock/Makefile
>>>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>>>>
>>>>    landlock-y := setup.o syscalls.o object.o ruleset.o \
>>>>    	cred.o ptrace.o fs.o
>>>> +
>>>> +landlock-$(CONFIG_INET) += net.o
>>>> \ No newline at end of file
>>>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>>>> new file mode 100644
>>>> index 000000000000..39e8a156a1f4
>>>> --- /dev/null
>>>> +++ b/security/landlock/net.c
>>>> @@ -0,0 +1,164 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +/*
>>>> + * Landlock LSM - Network management and hooks
>>>> + *
>>>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>>>> + * Copyright © 2022 Microsoft Corporation
>>>> + */
>>>> +
>>>> +#include <linux/in.h>
>>>> +#include <linux/net.h>
>>>> +#include <linux/socket.h>
>>>> +#include <net/ipv6.h>
>>>> +
>>>> +#include "common.h"
>>>> +#include "cred.h"
>>>> +#include "limits.h"
>>>> +#include "net.h"
>>>> +#include "ruleset.h"
>>>> +
>>>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>>>> +			     const u16 port, access_mask_t access_rights)
>>>> +{
>>>> +	int err;
>>>> +	const struct landlock_id id = {
>>>> +		.key.data = port,
>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>> +	};
>>>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>>>> +
>>>> +	/* Transforms relative access rights to absolute ones. */
>>>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>>>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>>>> +
>>>> +	mutex_lock(&ruleset->lock);
>>>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>>> +	mutex_unlock(&ruleset->lock);
>>>> +
>>>> +	return err;
>>>> +}
>>>> +
>>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>>> +			       u16 port, access_mask_t access_request)
>>>> +{
>>>> +	bool allowed = false;
>>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>>> +	const struct landlock_rule *rule;
>>>> +	access_mask_t handled_access;
>>>> +	const struct landlock_id id = {
>>>> +		.key.data = port,
>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>> +	};
>>>> +
>>>> +	if (WARN_ON_ONCE(!domain))
>>>> +		return 0;
>>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>>> +		return -EACCES;
>>>> +
>>>> +	rule = landlock_find_rule(domain, id);
>>>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>>>> +					  LANDLOCK_KEY_NET_PORT);
>>>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>>>> +				ARRAY_SIZE(layer_masks));
>>>> +
>>>> +	return allowed ? 0 : -EACCES;
>>>> +}
>>>> +
>>>> +static u16 get_port(const struct sockaddr *const address)
>>>
>>> get_port() should return a __be16 type. This enables to avoid converting
>>> port when checking a rule.
>> 
>>     In this case a user must do a coverting port into __be16:
>> 
>>     struct landlock_net_service_attr net_service = {
>>                   .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> 
>>                   .port = htons(sock_port),
>>           };
>>    I think that a user should not think about this conversion cause it
>> makes UAPI more complex to use. Lets do this under kernel's hood and let
>> it as it is now -> u16 port.
>> 
>> What do you think?
> 
> BE and LE conversions may be error prone without strong typing, but the
> current Linux network UAPI uses this convention (see related syscalls),
> so developers already use htons() in their applications. I think it is
> less hazardous to use the same convention. It would be nice to have the
> point of view of network and API folks though.

   Ok. Thanks. Let ports be in BE format like in network packets.

   What should a selftest with port conversion be like?

   1. Set a port with a Landlock rule with no conversion. get an error 
wit bind/connect actions.
   2. Convert a port with htons(sock_port). get no error.

   What do you think?
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-09  7:59         ` Konstantin Meskhidze (A)
@ 2023-01-09  8:58           ` Dan Carpenter
  2023-01-09  9:26             ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Dan Carpenter @ 2023-01-09  8:58 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: Mickaël Salaün, linux-sparse, willemdebruijn.kernel,
	gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, Linux API, Alejandro Colomar (man-pages)

These warnings seem like something I have seen before.  Maybe it was an
issue with _Generic() support?

Are you really sure you're running the latest git version of Sparse?

I tested this patch with the latest version of Sparse on my system and
it worked fine.

regards,
dan carpenter


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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-09  8:58           ` Dan Carpenter
@ 2023-01-09  9:26             ` Konstantin Meskhidze (A)
  2023-01-09 10:20               ` Dan Carpenter
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-09  9:26 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: Mickaël Salaün, linux-sparse, willemdebruijn.kernel,
	gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, Linux API, Alejandro Colomar (man-pages)



1/9/2023 11:58 AM, Dan Carpenter пишет:
> These warnings seem like something I have seen before.  Maybe it was an
> issue with _Generic() support?
> 
> Are you really sure you're running the latest git version of Sparse?
> 
> I tested this patch with the latest version of Sparse on my system and
> it worked fine.

  Hi Dan,

  git is on the master branch now - hash ce1a6720 (dated 27 June 2022)

  Is this correct version?

  regards,
  Konstantin.
> 
> regards,
> dan carpenter
> 
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-09  9:26             ` Konstantin Meskhidze (A)
@ 2023-01-09 10:20               ` Dan Carpenter
  2023-01-09 11:39                 ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Dan Carpenter @ 2023-01-09 10:20 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: Mickaël Salaün, linux-sparse, willemdebruijn.kernel,
	gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, Linux API, Alejandro Colomar (man-pages)

On Mon, Jan 09, 2023 at 12:26:52PM +0300, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/9/2023 11:58 AM, Dan Carpenter пишет:
> > These warnings seem like something I have seen before.  Maybe it was an
> > issue with _Generic() support?
> > 
> > Are you really sure you're running the latest git version of Sparse?
> > 
> > I tested this patch with the latest version of Sparse on my system and
> > it worked fine.
> 
>  Hi Dan,
> 
>  git is on the master branch now - hash ce1a6720 (dated 27 June 2022)
> 
>  Is this correct version?

Yes, that's correct.  What is your .config?

regards,
dan carpenter


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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-09 10:20               ` Dan Carpenter
@ 2023-01-09 11:39                 ` Konstantin Meskhidze (A)
  2023-01-09 11:53                   ` Dan Carpenter
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-09 11:39 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: Mickaël Salaün, linux-sparse, willemdebruijn.kernel,
	gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, Linux API, Alejandro Colomar (man-pages)



1/9/2023 1:20 PM, Dan Carpenter пишет:
> On Mon, Jan 09, 2023 at 12:26:52PM +0300, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/9/2023 11:58 AM, Dan Carpenter пишет:
>> > These warnings seem like something I have seen before.  Maybe it was an
>> > issue with _Generic() support?
>> > 
>> > Are you really sure you're running the latest git version of Sparse?
>> > 
>> > I tested this patch with the latest version of Sparse on my system and
>> > it worked fine.
>> 
>>  Hi Dan,
>> 
>>  git is on the master branch now - hash ce1a6720 (dated 27 June 2022)
>> 
>>  Is this correct version?
> 
> Yes, that's correct.  What is your .config?

   What parameters do I need to check in .config?
> 
> regards,
> dan carpenter
> 
> .

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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-09 11:39                 ` Konstantin Meskhidze (A)
@ 2023-01-09 11:53                   ` Dan Carpenter
  2023-01-09 12:18                     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Dan Carpenter @ 2023-01-09 11:53 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: Mickaël Salaün, linux-sparse, willemdebruijn.kernel,
	gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, Linux API, Alejandro Colomar (man-pages)

On Mon, Jan 09, 2023 at 02:39:36PM +0300, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/9/2023 1:20 PM, Dan Carpenter пишет:
> > On Mon, Jan 09, 2023 at 12:26:52PM +0300, Konstantin Meskhidze (A) wrote:
> > > 
> > > 
> > > 1/9/2023 11:58 AM, Dan Carpenter пишет:
> > > > These warnings seem like something I have seen before.  Maybe it was an
> > > > issue with _Generic() support?
> > > > > Are you really sure you're running the latest git version of
> > > Sparse?
> > > > > I tested this patch with the latest version of Sparse on my
> > > system and
> > > > it worked fine.
> > > 
> > >  Hi Dan,
> > > 
> > >  git is on the master branch now - hash ce1a6720 (dated 27 June 2022)
> > > 
> > >  Is this correct version?
> > 
> > Yes, that's correct.  What is your .config?
> 
>   What parameters do I need to check in .config?

I don't know.  I was hoping you could just email me the whole thing
and/or the results from make security/landlock/ruleset.i.  That way
we could see what line was making Sparse complain.

regards,
dan carpenter


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

* Re: [PATCH v8 07/12] landlock: Add network rules support
  2023-01-09 11:53                   ` Dan Carpenter
@ 2023-01-09 12:18                     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-09 12:18 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: Mickaël Salaün, linux-sparse, willemdebruijn.kernel,
	gnoack3000, linux-security-module, netdev, netfilter-devel,
	artem.kuzin, Linux API, Alejandro Colomar (man-pages)


1/9/2023 2:53 PM, Dan Carpenter пишет:
> On Mon, Jan 09, 2023 at 02:39:36PM +0300, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/9/2023 1:20 PM, Dan Carpenter пишет:
>> > On Mon, Jan 09, 2023 at 12:26:52PM +0300, Konstantin Meskhidze (A) wrote:
>> > > 
>> > > 
>> > > 1/9/2023 11:58 AM, Dan Carpenter пишет:
>> > > > These warnings seem like something I have seen before.  Maybe it was an
>> > > > issue with _Generic() support?
>> > > > > Are you really sure you're running the latest git version of
>> > > Sparse?
>> > > > > I tested this patch with the latest version of Sparse on my
>> > > system and
>> > > > it worked fine.
>> > > 
>> > >  Hi Dan,
>> > > 
>> > >  git is on the master branch now - hash ce1a6720 (dated 27 June 2022)
>> > > 
>> > >  Is this correct version?
>> > 
>> > Yes, that's correct.  What is your .config?
>> 
>>   What parameters do I need to check in .config?
> 
> I don't know.  I was hoping you could just email me the whole thing
> and/or the results from make security/landlock/ruleset.i.  That way
> we could see what line was making Sparse complain.

   here is the whole error message:

   make C=2 security/landlock/
   CHECK   scripts/mod/empty.c
   CALL    scripts/checksyscalls.sh
   DESCEND objtool
   DESCEND bpf/resolve_btfids
   CHECK   security/landlock/setup.c
./include/asm-generic/rwonce.h:67:16: error: typename in expression
./include/asm-generic/rwonce.h:67:16: error: Expected ) in function call
./include/asm-generic/rwonce.h:67:16: error: got :
./include/linux/list.h:292:16: error: typename in expression
./include/linux/list.h:292:16: error: Expected ) in function call
./include/linux/list.h:292:16: error: got :
./include/linux/list.h:328:34: error: typename in expression
./include/linux/list.h:328:34: error: Expected ) in function call
./include/linux/list.h:328:34: error: got :
./include/linux/list.h:329:53: error: typename in expression
./include/linux/list.h:329:53: error: Expected ) in function call
./include/linux/list.h:329:53: error: got :
./include/linux/list.h:867:17: error: typename in expression
./include/linux/list.h:867:17: error: Expected ) in function call
./include/linux/list.h:867:17: error: got :
./include/linux/list.h:876:17: error: typename in expression
./include/linux/list.h:876:17: error: Expected ) in function call
./include/linux/list.h:876:17: error: got :
./arch/x86/include/asm/atomic.h:29:16: error: typename in expression
./arch/x86/include/asm/atomic.h:29:16: error: Expected ) in function call
./arch/x86/include/asm/atomic.h:29:16: error: got :
./arch/x86/include/asm/atomic64_64.h:22:16: error: typename in expression
./arch/x86/include/asm/atomic64_64.h:22:16: error: Expected ) in 
function call
./arch/x86/include/asm/atomic64_64.h:22:16: error: got :
./include/linux/atomic/atomic-arch-fallback.h:227:23: error: typename in 
expression
./include/linux/atomic/atomic-arch-fallback.h:227:23: error: Expected ) 
in function call
./include/linux/atomic/atomic-arch-fallback.h:227:23: error: got :
./include/linux/atomic/atomic-arch-fallback.h:1348:23: error: typename 
in expression
./include/linux/atomic/atomic-arch-fallback.h:1348:23: error: Expected ) 
in function call
./include/linux/atomic/atomic-arch-fallback.h:1348:23: error: got :
./include/linux/jump_label.h:286:9: error: Expected ; at end of statement
./include/linux/jump_label.h:286:9: error: got __flags
./include/linux/jump_label.h:302:9: error: Expected ; at end of statement
./include/linux/jump_label.h:302:9: error: got __flags
./include/linux/jump_label.h:319:9: error: Expected ; at end of statement
./include/linux/jump_label.h:319:9: error: got __flags
./include/linux/jump_label.h:322:17: error: Expected ; at end of statement
./include/linux/jump_label.h:322:17: error: got __flags
./include/linux/jump_label.h:330:9: error: Expected ; at end of statement
./include/linux/jump_label.h:330:9: error: got __flags
./include/linux/jump_label.h:333:17: error: Expected ; at end of statement
./include/linux/jump_label.h:333:17: error: got __flags
./include/asm-generic/bitops/generic-non-atomic.h:140:23: error: 
typename in expression
./include/asm-generic/bitops/generic-non-atomic.h:140:23: error: 
Expected ) in function call
./include/asm-generic/bitops/generic-non-atomic.h:140:23: error: got :
./include/linux/bitmap.h:268:17: error: Expected ; at end of statement
./include/linux/bitmap.h:268:17: error: got __flags
./include/linux/thread_info.h:127:16: error: typename in expression
./include/linux/thread_info.h:127:16: error: Expected ) in function call
./include/linux/thread_info.h:127:16: error: got :
./include/linux/thread_info.h:233:13: error: Expected ; at end of statement
./include/linux/thread_info.h:233:13: error: got __flags
./include/linux/llist.h:191:16: error: typename in expression
./include/linux/llist.h:191:16: error: Expected ) in function call
./include/linux/llist.h:191:16: error: got :
./include/linux/rcupdate.h:1073:31: error: typename in expression
./include/linux/rcupdate.h:1073:31: error: Expected ) in function call
./include/linux/rcupdate.h:1073:31: error: got :
./include/linux/rcupdate.h:1077:9: error: Expected ; at end of statement
./include/linux/rcupdate.h:1077:9: error: got __flags
./include/linux/key.h:453:16: error: typename in expression
./include/linux/key.h:453:16: error: Expected ) in function call
./include/linux/key.h:453:16: error: got :
./include/linux/list_bl.h:74:33: error: typename in expression
./include/linux/list_bl.h:74:33: error: Expected ) in function call
./include/linux/list_bl.h:74:33: error: got :
./include/linux/rculist_bl.h:24:33: error: typename in expression
./include/linux/rculist_bl.h:24:33: error: Expected ) in function call
./include/linux/rculist_bl.h:24:33: error: got :
./include/linux/seqlock.h:259:16: error: typename in expression
./include/linux/seqlock.h:259:16: error: Expected ) in function call
./include/linux/seqlock.h:259:16: error: got :
./include/linux/seqlock.h:274:1: error: typename in expression
./include/linux/seqlock.h:274:1: error: Expected ) in function call
./include/linux/seqlock.h:274:1: error: got :
./include/linux/seqlock.h:274:1: error: typename in expression
./include/linux/seqlock.h:274:1: error: Expected ) in function call
./include/linux/seqlock.h:274:1: error: got :
./include/linux/seqlock.h:275:1: error: typename in expression
./include/linux/seqlock.h:275:1: error: Expected ) in function call
./include/linux/seqlock.h:275:1: error: got :
./include/linux/seqlock.h:275:1: error: typename in expression
./include/linux/seqlock.h:275:1: error: Expected ) in function call
./include/linux/seqlock.h:275:1: error: got :
./include/linux/seqlock.h:276:1: error: typename in expression
./include/linux/seqlock.h:276:1: error: Expected ) in function call
./include/linux/seqlock.h:276:1: error: got :
./include/linux/seqlock.h:276:1: error: typename in expression
./include/linux/seqlock.h:276:1: error: Expected ) in function call
./include/linux/seqlock.h:276:1: error: got :
./include/linux/seqlock.h:277:1: error: typename in expression
./include/linux/seqlock.h:277:1: error: Expected ) in function call
./include/linux/seqlock.h:277:1: error: got :
./include/linux/seqlock.h:277:1: error: typename in expression
./include/linux/seqlock.h:277:1: error: Expected ) in function call
./include/linux/seqlock.h:277:1: error: got :
./include/linux/seqlock.h:429:16: error: typename in expression
./include/linux/seqlock.h:429:16: error: Expected ) in function call
./include/linux/seqlock.h:429:16: error: got :
./include/linux/seqlock.h:682:16: error: typename in expression
./include/linux/seqlock.h:682:16: error: Expected ) in function call
./include/linux/seqlock.h:682:16: error: too many errors
Segmentation fault (core dumped)
make[3]: *** [scripts/Makefile.build:251: security/landlock/setup.o] 
Error 139
make[2]: *** [scripts/Makefile.build:502: security/landlock] Error 2
make[1]: *** [scripts/Makefile.build:502: security] Error 2
make: *** [Makefile:1994: .] Error 2

Please tell me if you need some more info.

regards,
Konstantin

> 
> regards,
> dan carpenter
> 
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2023-01-09  8:07         ` Konstantin Meskhidze (A)
@ 2023-01-09 12:38           ` Mickaël Salaün
  2023-01-10  4:45             ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-09 12:38 UTC (permalink / raw)
  To: Konstantin Meskhidze (A),
	netdev, linux-api, Alejandro Colomar (man-pages)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module,
	netfilter-devel, artem.kuzin


On 09/01/2023 09:07, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/6/2023 10:30 PM, Mickaël Salaün пишет:
>>
>> On 05/01/2023 09:57, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>
>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>> particular ports.
>>>>
>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>> restrict TCP socket binding and connection to specific ports.
>>>>
>>>>
>>>>>
>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>> ---
>>>>>
>>>>> Changes since v7:
>>>>> * Minor fixes.
>>>>> * Refactors commit message.
>>>>>
>>>>> Changes since v6:
>>>>> * Updates copyright.
>>>>> * Refactors landlock_append_net_rule() and check_socket_access()
>>>>>      functions with landlock_id type.
>>>>>
>>>>> Changes since v5:
>>>>> * Fixes some logic errors.
>>>>> * Formats code with clang-format-14.
>>>>>
>>>>> Changes since v4:
>>>>> * Factors out CONFIG_INET into make file.
>>>>> * Refactors check_socket_access().
>>>>> * Adds helper get_port().
>>>>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>>>>> functions to support AF_INET6 family.
>>>>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>>>>> functions.
>>>>> * Refactors add_rule_net_service() and landlock_add_rule
>>>>> syscall to support network rule inserting.
>>>>> * Refactors init_layer_masks() to support network rules.
>>>>>
>>>>> Changes since v3:
>>>>> * Splits commit.
>>>>> * Adds SECURITY_NETWORK in config.
>>>>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>>>>> * Adds hook_socket_bind and hook_socket_connect hooks.
>>>>>
>>>>> ---
>>>>>     security/landlock/Kconfig    |   1 +
>>>>>     security/landlock/Makefile   |   2 +
>>>>>     security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>>>>     security/landlock/net.h      |  26 ++++++
>>>>>     security/landlock/setup.c    |   2 +
>>>>>     security/landlock/syscalls.c |  59 ++++++++++++-
>>>>>     6 files changed, 251 insertions(+), 3 deletions(-)
>>>>>     create mode 100644 security/landlock/net.c
>>>>>     create mode 100644 security/landlock/net.h
>>>>>
>>>>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>>>>> index 8e33c4e8ffb8..10c099097533 100644
>>>>> --- a/security/landlock/Kconfig
>>>>> +++ b/security/landlock/Kconfig
>>>>> @@ -3,6 +3,7 @@
>>>>>     config SECURITY_LANDLOCK
>>>>>     	bool "Landlock support"
>>>>>     	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>>>>> +	select SECURITY_NETWORK
>>>>>     	select SECURITY_PATH
>>>>>     	help
>>>>>     	  Landlock is a sandboxing mechanism that enables processes to restrict
>>>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>>>> index 7bbd2f413b3e..53d3c92ae22e 100644
>>>>> --- a/security/landlock/Makefile
>>>>> +++ b/security/landlock/Makefile
>>>>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>>>>>
>>>>>     landlock-y := setup.o syscalls.o object.o ruleset.o \
>>>>>     	cred.o ptrace.o fs.o
>>>>> +
>>>>> +landlock-$(CONFIG_INET) += net.o
>>>>> \ No newline at end of file
>>>>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>>>>> new file mode 100644
>>>>> index 000000000000..39e8a156a1f4
>>>>> --- /dev/null
>>>>> +++ b/security/landlock/net.c
>>>>> @@ -0,0 +1,164 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>>> +/*
>>>>> + * Landlock LSM - Network management and hooks
>>>>> + *
>>>>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>>>>> + * Copyright © 2022 Microsoft Corporation
>>>>> + */
>>>>> +
>>>>> +#include <linux/in.h>
>>>>> +#include <linux/net.h>
>>>>> +#include <linux/socket.h>
>>>>> +#include <net/ipv6.h>
>>>>> +
>>>>> +#include "common.h"
>>>>> +#include "cred.h"
>>>>> +#include "limits.h"
>>>>> +#include "net.h"
>>>>> +#include "ruleset.h"
>>>>> +
>>>>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>>>>> +			     const u16 port, access_mask_t access_rights)
>>>>> +{
>>>>> +	int err;
>>>>> +	const struct landlock_id id = {
>>>>> +		.key.data = port,
>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>> +	};
>>>>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>>>>> +
>>>>> +	/* Transforms relative access rights to absolute ones. */
>>>>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>>>>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>>>>> +
>>>>> +	mutex_lock(&ruleset->lock);
>>>>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>>>> +	mutex_unlock(&ruleset->lock);
>>>>> +
>>>>> +	return err;
>>>>> +}
>>>>> +
>>>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>>>> +			       u16 port, access_mask_t access_request)
>>>>> +{
>>>>> +	bool allowed = false;
>>>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>>>> +	const struct landlock_rule *rule;
>>>>> +	access_mask_t handled_access;
>>>>> +	const struct landlock_id id = {
>>>>> +		.key.data = port,
>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>> +	};
>>>>> +
>>>>> +	if (WARN_ON_ONCE(!domain))
>>>>> +		return 0;
>>>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>>>> +		return -EACCES;
>>>>> +
>>>>> +	rule = landlock_find_rule(domain, id);
>>>>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>>>>> +					  LANDLOCK_KEY_NET_PORT);
>>>>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>>>>> +				ARRAY_SIZE(layer_masks));
>>>>> +
>>>>> +	return allowed ? 0 : -EACCES;
>>>>> +}
>>>>> +
>>>>> +static u16 get_port(const struct sockaddr *const address)
>>>>
>>>> get_port() should return a __be16 type. This enables to avoid converting
>>>> port when checking a rule.
>>>
>>>      In this case a user must do a coverting port into __be16:
>>>
>>>      struct landlock_net_service_attr net_service = {
>>>                    .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>
>>>                    .port = htons(sock_port),
>>>            };
>>>     I think that a user should not think about this conversion cause it
>>> makes UAPI more complex to use. Lets do this under kernel's hood and let
>>> it as it is now -> u16 port.
>>>
>>> What do you think?
>>
>> BE and LE conversions may be error prone without strong typing, but the
>> current Linux network UAPI uses this convention (see related syscalls),
>> so developers already use htons() in their applications. I think it is
>> less hazardous to use the same convention. It would be nice to have the
>> point of view of network and API folks though.
> 
>     Ok. Thanks. Let ports be in BE format like in network packets.
> 
>     What should a selftest with port conversion be like?
> 
>     1. Set a port with a Landlock rule with no conversion. get an error
> wit bind/connect actions.
>     2. Convert a port with htons(sock_port). get no error.
> 
>     What do you think?

Right, you can do both on a LE architecture (that must be checked in the 
test or it should be skipped), test with a port value that has different 
representation in LE and BE.

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

* Re: [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2022-10-21 15:26 ` [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
@ 2023-01-09 12:46   ` Mickaël Salaün
  2023-01-10  5:03     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-09 12:46 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 21/10/2022 17:26, Konstantin Meskhidze wrote:
> These test suites try to check edge cases for TCP sockets
> bind() and connect() actions.
> 
> socket:
> * bind_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
> * bind_with_restrictions: Tests with mixed landlock rules for ipv4 and
> ipv6 sockets.
> * connect_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
> * connect_with_restrictions: Tests with mixed landlock rules for ipv4 and
> ipv6 sockets.
> * connect_afunspec_no_restrictions: Tests with no landlock restrictions
> allowing to disconnect already connected socket with AF_UNSPEC socket
> family.
> * connect_afunspec_with_restrictions: Tests with landlocked process
> refusing to disconnect already connected socket.
> * ruleset_overlap: Tests with overlapping rules for one port.
> * ruleset_expanding: Tests with expanding rulesets in which rules are
> gradually added one by one, restricting sockets' connections.
> * inval: Tests with invalid user space supplied data:
>      - out of range ruleset attribute;
>      - unhandled allowed access;
>      - zero port value;
>      - zero access value;
>      - legitimate access values;
> 
> layout1:
> * with_net: Tests with network bind() socket action within
> filesystem directory access test.
> 
> Test coverage for security/landlock is 94.3% of 920 lines according
> to gcc/gcov-11.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v7:
> * Squashes all selftest commits.
> * Adds fs test with network bind() socket action.
> * Minor fixes.
> 
> ---
>   security/landlock/ruleset.h                 |   2 -
>   tools/testing/selftests/landlock/config     |   4 +
>   tools/testing/selftests/landlock/fs_test.c  |  65 ++
>   tools/testing/selftests/landlock/net_test.c | 823 ++++++++++++++++++++
>   4 files changed, 892 insertions(+), 2 deletions(-)
>   create mode 100644 tools/testing/selftests/landlock/net_test.c
> 
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index f272d2cd518c..ee1a02a404ce 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -264,7 +264,6 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
> 
>   	/* Should already be checked in sys_landlock_create_ruleset(). */
>   	WARN_ON_ONCE(fs_access_mask != fs_mask);
> -	// TODO: Add tests to check "|=" and not "="
>   	ruleset->access_masks[layer_level] |=
>   		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
>   }
> @@ -278,7 +277,6 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
> 
>   	/* Should already be checked in sys_landlock_create_ruleset(). */
>   	WARN_ON_ONCE(net_access_mask != net_mask);
> -	// TODO: Add tests to check "|=" and not "="
>   	ruleset->access_masks[layer_level] |=
>   		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
>   }
> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
> index 0f0a65287bac..71f7e9a8a64c 100644
> --- a/tools/testing/selftests/landlock/config
> +++ b/tools/testing/selftests/landlock/config
> @@ -1,3 +1,7 @@
> +CONFIG_INET=y
> +CONFIG_IPV6=y
> +CONFIG_NET=y
> +CONFIG_NET_NS=y
>   CONFIG_OVERLAY_FS=y
>   CONFIG_SECURITY_LANDLOCK=y
>   CONFIG_SECURITY_PATH=y
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 20c1ac8485f1..5c52da1a5a69 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -8,14 +8,17 @@
>    */
> 
>   #define _GNU_SOURCE
> +#include <arpa/inet.h>
>   #include <fcntl.h>
>   #include <linux/landlock.h>
> +#include <netinet/in.h>
>   #include <sched.h>
>   #include <string.h>
>   #include <sys/capability.h>
>   #include <sys/mount.h>
>   #include <sys/prctl.h>
>   #include <sys/sendfile.h>
> +#include <sys/socket.h>
>   #include <sys/stat.h>
>   #include <sys/sysmacros.h>
>   #include <unistd.h>
> @@ -4366,4 +4369,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>   	}
>   }
> 
> +#define IP_ADDRESS "127.0.0.1"
> +
> +TEST_F_FORK(layout1, with_net)
> +{
> +	int sockfd;
> +	int sock_port = 15000;
> +	struct sockaddr_in addr4;
> +
> +	addr4.sin_family = AF_INET;
> +	addr4.sin_port = htons(sock_port);
> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
> +	memset(&addr4.sin_zero, '\0', 8);
> +
> +	const struct rule rules[] = {
> +		{
> +			.path = dir_s1d2,
> +			.access = ACCESS_RO,
> +		},
> +		{},
> +	};
> +
> +	struct landlock_ruleset_attr ruleset_attr_net = {
> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +	};
> +	struct landlock_net_service_attr net_service = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> +
> +		.port = sock_port,
> +	};
> +
> +	/* Creates ruleset for network access. */
> +	const int ruleset_fd_net = landlock_create_ruleset(
> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
> +	ASSERT_LE(0, ruleset_fd_net);
> +
> +	/* Adds a network rule. */
> +	ASSERT_EQ(0,
> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
> +				    &net_service, 0));
> +
> +	enforce_ruleset(_metadata, ruleset_fd_net);
> +	ASSERT_EQ(0, close(ruleset_fd_net));
> +
> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
> +	ASSERT_LE(0, ruleset_fd);
> +	enforce_ruleset(_metadata, ruleset_fd);
> +	ASSERT_EQ(0, close(ruleset_fd));
> +
> +	/* Tests on a directory with the network rule loaded. */
> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
> +
> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port 15000. */
> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
> +
> +	/* Closes bounded socket. */
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
>   TEST_HARNESS_MAIN
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> new file mode 100644
> index 000000000000..d1548bd7ab60
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -0,0 +1,823 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock tests - Network
> + *
> + * Copyright (C) 2022 Huawei Tech. Co., Ltd.
> + */
> +
> +#define _GNU_SOURCE
> +#include <arpa/inet.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <linux/landlock.h>
> +#include <netinet/in.h>
> +#include <sched.h>
> +#include <string.h>
> +#include <sys/prctl.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +
> +#include "common.h"
> +
> +#define MAX_SOCKET_NUM 10
> +
> +#define SOCK_PORT_START 3470
> +#define SOCK_PORT_ADD 10
> +
> +#define IP_ADDRESS "127.0.0.1"
> +
> +/* Number pending connections queue to be hold. */
> +#define BACKLOG 10
> +
> +const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
> +
> +/* Invalid attribute, out of landlock network access range. */
> +#define LANDLOCK_INVAL_ATTR 7
> +
> +FIXTURE(socket)
> +{
> +	uint port[MAX_SOCKET_NUM];
> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
> +};
> +
> +/* struct _fixture_variant_socket */
> +FIXTURE_VARIANT(socket)
> +{
> +	const bool is_ipv4;
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket, ipv4) {
> +	/* clang-format on */
> +	.is_ipv4 = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket, ipv6) {
> +	/* clang-format on */
> +	.is_ipv4 = false,
> +};
> +
> +static int
> +create_socket_variant(const struct _fixture_variant_socket *const variant,
> +		      const int type)
> +{
> +	if (variant->is_ipv4)
> +		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
> +	else
> +		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
> +}
> +
> +static int bind_variant(const struct _fixture_variant_socket *const variant,
> +			const int sockfd,
> +			const struct _test_data_socket *const self,
> +			const size_t index)
> +{
> +	if (variant->is_ipv4)
> +		return bind(sockfd, &self->addr4[index],
> +			    sizeof(self->addr4[index]));
> +	else
> +		return bind(sockfd, &self->addr6[index],
> +			    sizeof(self->addr6[index]));
> +}
> +
> +static int connect_variant(const struct _fixture_variant_socket *const variant,
> +			   const int sockfd,
> +			   const struct _test_data_socket *const self,
> +			   const size_t index)
> +{
> +	if (variant->is_ipv4)
> +		return connect(sockfd, &self->addr4[index],
> +			       sizeof(self->addr4[index]));
> +	else
> +		return connect(sockfd, &self->addr6[index],
> +			       sizeof(self->addr6[index]));
> +}
> +
> +FIXTURE_SETUP(socket)
> +{
> +	int i;
> +
> +	/* Creates IPv4 socket addresses. */
> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
> +		self->addr4[i].sin_family = AF_INET;
> +		self->addr4[i].sin_port = htons(self->port[i]);
> +		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS);
> +		memset(&(self->addr4[i].sin_zero), '\0', 8);
> +	}
> +
> +	/* Creates IPv6 socket addresses. */
> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
> +		self->addr6[i].sin6_family = AF_INET6;
> +		self->addr6[i].sin6_port = htons(self->port[i]);
> +		inet_pton(AF_INET6, IP_ADDRESS, &(self->addr6[i].sin6_addr));
> +	}
> +
> +	set_cap(_metadata, CAP_SYS_ADMIN);
> +	ASSERT_EQ(0, unshare(CLONE_NEWNET));
> +	ASSERT_EQ(0, system("ip link set dev lo up"));
> +	clear_cap(_metadata, CAP_SYS_ADMIN);
> +}
> +
> +FIXTURE_TEARDOWN(socket)
> +{
> +}
> +
> +TEST_F_FORK(socket, bind_no_restrictions)
> +{
> +	int sockfd;
> +
> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
> +	ASSERT_LE(0, sockfd);
> +
> +	/* Binds a socket to port[0]. */
> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
> +
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
> +TEST_F_FORK(socket, bind_with_restrictions)
> +{
> +	int sockfd;
> +
> +	struct landlock_ruleset_attr ruleset_attr = {
> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +	};
> +	struct landlock_net_service_attr net_service_1 = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +		.port = self->port[0],
> +	};
> +	struct landlock_net_service_attr net_service_2 = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +		.port = self->port[1],
> +	};
> +	struct landlock_net_service_attr net_service_3 = {
> +		.allowed_access = 0,
> +		.port = self->port[2],
> +	};
> +
> +	const int ruleset_fd =
> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> +	ASSERT_LE(0, ruleset_fd);
> +
> +	/* Allows connect and bind operations to the port[0] socket. */
> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
> +				       &net_service_1, 0));
> +	/* Allows connect and deny bind operations to the port[1] socket. */
> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
> +				       &net_service_2, 0));
> +	/*
> +	 * Empty allowed_access (i.e. deny rules) are ignored in network actions
> +	 * for port[2] socket.
> +	 */
> +	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
> +					&net_service_3, 0));
> +	ASSERT_EQ(ENOMSG, errno);
> +
> +	/* Enforces the ruleset. */
> +	enforce_ruleset(_metadata, ruleset_fd);
> +
> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[0]. */
> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
> +
> +	/* Closes bounded socket. */
> +	ASSERT_EQ(0, close(sockfd));
> +
> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[1]. */
> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 1));
> +	ASSERT_EQ(EACCES, errno);
> +
> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[2]. */
> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2));
> +	ASSERT_EQ(EACCES, errno);

This is inconsistent with the bind_no_restrictions test. If you 
deduplicate the tests with and without restrictions (i.e. only one 
"bind" test, and another "connect"…), you can extend 
FIXTURE_VARIANT(socket) with a new const bool enforce_landlock, and 
check that in all tests to either do Landlock syscalls or not. You can 
still initialize most variable whatever Landlock should be enforced or 
not (e.g. ruleset_attr, net_service_1…) to make it easiear to read.


> +}
> +
> +TEST_F_FORK(socket, connect_no_restrictions)

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2023-01-09 12:38           ` Mickaël Salaün
@ 2023-01-10  4:45             ` Konstantin Meskhidze (A)
  2023-01-10 17:24               ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-10  4:45 UTC (permalink / raw)
  To: Mickaël Salaün, netdev, linux-api,
	Alejandro Colomar (man-pages)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module,
	netfilter-devel, artem.kuzin



1/9/2023 3:38 PM, Mickaël Salaün пишет:
> 
> On 09/01/2023 09:07, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/6/2023 10:30 PM, Mickaël Salaün пишет:
>>>
>>> On 05/01/2023 09:57, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>>
>>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>>> particular ports.
>>>>>
>>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>>> restrict TCP socket binding and connection to specific ports.
>>>>>
>>>>>
>>>>>>
>>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>>> ---
>>>>>>
>>>>>> Changes since v7:
>>>>>> * Minor fixes.
>>>>>> * Refactors commit message.
>>>>>>
>>>>>> Changes since v6:
>>>>>> * Updates copyright.
>>>>>> * Refactors landlock_append_net_rule() and check_socket_access()
>>>>>>      functions with landlock_id type.
>>>>>>
>>>>>> Changes since v5:
>>>>>> * Fixes some logic errors.
>>>>>> * Formats code with clang-format-14.
>>>>>>
>>>>>> Changes since v4:
>>>>>> * Factors out CONFIG_INET into make file.
>>>>>> * Refactors check_socket_access().
>>>>>> * Adds helper get_port().
>>>>>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>>>>>> functions to support AF_INET6 family.
>>>>>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>>>>>> functions.
>>>>>> * Refactors add_rule_net_service() and landlock_add_rule
>>>>>> syscall to support network rule inserting.
>>>>>> * Refactors init_layer_masks() to support network rules.
>>>>>>
>>>>>> Changes since v3:
>>>>>> * Splits commit.
>>>>>> * Adds SECURITY_NETWORK in config.
>>>>>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>>>>>> * Adds hook_socket_bind and hook_socket_connect hooks.
>>>>>>
>>>>>> ---
>>>>>>     security/landlock/Kconfig    |   1 +
>>>>>>     security/landlock/Makefile   |   2 +
>>>>>>     security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>>>>>     security/landlock/net.h      |  26 ++++++
>>>>>>     security/landlock/setup.c    |   2 +
>>>>>>     security/landlock/syscalls.c |  59 ++++++++++++-
>>>>>>     6 files changed, 251 insertions(+), 3 deletions(-)
>>>>>>     create mode 100644 security/landlock/net.c
>>>>>>     create mode 100644 security/landlock/net.h
>>>>>>
>>>>>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>>>>>> index 8e33c4e8ffb8..10c099097533 100644
>>>>>> --- a/security/landlock/Kconfig
>>>>>> +++ b/security/landlock/Kconfig
>>>>>> @@ -3,6 +3,7 @@
>>>>>>     config SECURITY_LANDLOCK
>>>>>>     	bool "Landlock support"
>>>>>>     	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>>>>>> +	select SECURITY_NETWORK
>>>>>>     	select SECURITY_PATH
>>>>>>     	help
>>>>>>     	  Landlock is a sandboxing mechanism that enables processes to restrict
>>>>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>>>>> index 7bbd2f413b3e..53d3c92ae22e 100644
>>>>>> --- a/security/landlock/Makefile
>>>>>> +++ b/security/landlock/Makefile
>>>>>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>>>>>>
>>>>>>     landlock-y := setup.o syscalls.o object.o ruleset.o \
>>>>>>     	cred.o ptrace.o fs.o
>>>>>> +
>>>>>> +landlock-$(CONFIG_INET) += net.o
>>>>>> \ No newline at end of file
>>>>>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>>>>>> new file mode 100644
>>>>>> index 000000000000..39e8a156a1f4
>>>>>> --- /dev/null
>>>>>> +++ b/security/landlock/net.c
>>>>>> @@ -0,0 +1,164 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>>>> +/*
>>>>>> + * Landlock LSM - Network management and hooks
>>>>>> + *
>>>>>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>>>>>> + * Copyright © 2022 Microsoft Corporation
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/in.h>
>>>>>> +#include <linux/net.h>
>>>>>> +#include <linux/socket.h>
>>>>>> +#include <net/ipv6.h>
>>>>>> +
>>>>>> +#include "common.h"
>>>>>> +#include "cred.h"
>>>>>> +#include "limits.h"
>>>>>> +#include "net.h"
>>>>>> +#include "ruleset.h"
>>>>>> +
>>>>>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>>>>>> +			     const u16 port, access_mask_t access_rights)
>>>>>> +{
>>>>>> +	int err;
>>>>>> +	const struct landlock_id id = {
>>>>>> +		.key.data = port,
>>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>>> +	};
>>>>>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>>>>>> +
>>>>>> +	/* Transforms relative access rights to absolute ones. */
>>>>>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>>>>>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>>>>>> +
>>>>>> +	mutex_lock(&ruleset->lock);
>>>>>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>>>>> +	mutex_unlock(&ruleset->lock);
>>>>>> +
>>>>>> +	return err;
>>>>>> +}
>>>>>> +
>>>>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>>>>> +			       u16 port, access_mask_t access_request)
>>>>>> +{
>>>>>> +	bool allowed = false;
>>>>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>>>>> +	const struct landlock_rule *rule;
>>>>>> +	access_mask_t handled_access;
>>>>>> +	const struct landlock_id id = {
>>>>>> +		.key.data = port,
>>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>>> +	};
>>>>>> +
>>>>>> +	if (WARN_ON_ONCE(!domain))
>>>>>> +		return 0;
>>>>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>>>>> +		return -EACCES;
>>>>>> +
>>>>>> +	rule = landlock_find_rule(domain, id);
>>>>>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>>>>>> +					  LANDLOCK_KEY_NET_PORT);
>>>>>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>>>>>> +				ARRAY_SIZE(layer_masks));
>>>>>> +
>>>>>> +	return allowed ? 0 : -EACCES;
>>>>>> +}
>>>>>> +
>>>>>> +static u16 get_port(const struct sockaddr *const address)
>>>>>
>>>>> get_port() should return a __be16 type. This enables to avoid converting
>>>>> port when checking a rule.
>>>>
>>>>      In this case a user must do a coverting port into __be16:
>>>>
>>>>      struct landlock_net_service_attr net_service = {
>>>>                    .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>
>>>>                    .port = htons(sock_port),
>>>>            };
>>>>     I think that a user should not think about this conversion cause it
>>>> makes UAPI more complex to use. Lets do this under kernel's hood and let
>>>> it as it is now -> u16 port.
>>>>
>>>> What do you think?
>>>
>>> BE and LE conversions may be error prone without strong typing, but the
>>> current Linux network UAPI uses this convention (see related syscalls),
>>> so developers already use htons() in their applications. I think it is
>>> less hazardous to use the same convention. It would be nice to have the
>>> point of view of network and API folks though.
>> 
>>     Ok. Thanks. Let ports be in BE format like in network packets.
>> 
>>     What should a selftest with port conversion be like?
>> 
>>     1. Set a port with a Landlock rule with no conversion. get an error
>> wit bind/connect actions.
>>     2. Convert a port with htons(sock_port). get no error.
>> 
>>     What do you think?
> 
> Right, you can do both on a LE architecture (that must be checked in the
> test or it should be skipped), test with a port value that has different
> representation in LE and BE.

   Do you mean to check architecture in a test first and then port 
representaton? What about BE architectures? My current VM is X86-64 
architecture a LE one. I can test just it now.
> .

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

* Re: [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-01-09 12:46   ` Mickaël Salaün
@ 2023-01-10  5:03     ` Konstantin Meskhidze (A)
  2023-01-10 17:40       ` Mickaël Salaün
  0 siblings, 1 reply; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-10  5:03 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



1/9/2023 3:46 PM, Mickaël Salaün пишет:
> 
> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>> These test suites try to check edge cases for TCP sockets
>> bind() and connect() actions.
>> 
>> socket:
>> * bind_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
>> * bind_with_restrictions: Tests with mixed landlock rules for ipv4 and
>> ipv6 sockets.
>> * connect_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
>> * connect_with_restrictions: Tests with mixed landlock rules for ipv4 and
>> ipv6 sockets.
>> * connect_afunspec_no_restrictions: Tests with no landlock restrictions
>> allowing to disconnect already connected socket with AF_UNSPEC socket
>> family.
>> * connect_afunspec_with_restrictions: Tests with landlocked process
>> refusing to disconnect already connected socket.
>> * ruleset_overlap: Tests with overlapping rules for one port.
>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>> gradually added one by one, restricting sockets' connections.
>> * inval: Tests with invalid user space supplied data:
>>      - out of range ruleset attribute;
>>      - unhandled allowed access;
>>      - zero port value;
>>      - zero access value;
>>      - legitimate access values;
>> 
>> layout1:
>> * with_net: Tests with network bind() socket action within
>> filesystem directory access test.
>> 
>> Test coverage for security/landlock is 94.3% of 920 lines according
>> to gcc/gcov-11.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v7:
>> * Squashes all selftest commits.
>> * Adds fs test with network bind() socket action.
>> * Minor fixes.
>> 
>> ---
>>   security/landlock/ruleset.h                 |   2 -
>>   tools/testing/selftests/landlock/config     |   4 +
>>   tools/testing/selftests/landlock/fs_test.c  |  65 ++
>>   tools/testing/selftests/landlock/net_test.c | 823 ++++++++++++++++++++
>>   4 files changed, 892 insertions(+), 2 deletions(-)
>>   create mode 100644 tools/testing/selftests/landlock/net_test.c
>> 
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index f272d2cd518c..ee1a02a404ce 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -264,7 +264,6 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>> 
>>   	/* Should already be checked in sys_landlock_create_ruleset(). */
>>   	WARN_ON_ONCE(fs_access_mask != fs_mask);
>> -	// TODO: Add tests to check "|=" and not "="
>>   	ruleset->access_masks[layer_level] |=
>>   		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
>>   }
>> @@ -278,7 +277,6 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
>> 
>>   	/* Should already be checked in sys_landlock_create_ruleset(). */
>>   	WARN_ON_ONCE(net_access_mask != net_mask);
>> -	// TODO: Add tests to check "|=" and not "="
>>   	ruleset->access_masks[layer_level] |=
>>   		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
>>   }
>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>> index 0f0a65287bac..71f7e9a8a64c 100644
>> --- a/tools/testing/selftests/landlock/config
>> +++ b/tools/testing/selftests/landlock/config
>> @@ -1,3 +1,7 @@
>> +CONFIG_INET=y
>> +CONFIG_IPV6=y
>> +CONFIG_NET=y
>> +CONFIG_NET_NS=y
>>   CONFIG_OVERLAY_FS=y
>>   CONFIG_SECURITY_LANDLOCK=y
>>   CONFIG_SECURITY_PATH=y
>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>> index 20c1ac8485f1..5c52da1a5a69 100644
>> --- a/tools/testing/selftests/landlock/fs_test.c
>> +++ b/tools/testing/selftests/landlock/fs_test.c
>> @@ -8,14 +8,17 @@
>>    */
>> 
>>   #define _GNU_SOURCE
>> +#include <arpa/inet.h>
>>   #include <fcntl.h>
>>   #include <linux/landlock.h>
>> +#include <netinet/in.h>
>>   #include <sched.h>
>>   #include <string.h>
>>   #include <sys/capability.h>
>>   #include <sys/mount.h>
>>   #include <sys/prctl.h>
>>   #include <sys/sendfile.h>
>> +#include <sys/socket.h>
>>   #include <sys/stat.h>
>>   #include <sys/sysmacros.h>
>>   #include <unistd.h>
>> @@ -4366,4 +4369,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>   	}
>>   }
>> 
>> +#define IP_ADDRESS "127.0.0.1"
>> +
>> +TEST_F_FORK(layout1, with_net)
>> +{
>> +	int sockfd;
>> +	int sock_port = 15000;
>> +	struct sockaddr_in addr4;
>> +
>> +	addr4.sin_family = AF_INET;
>> +	addr4.sin_port = htons(sock_port);
>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>> +	memset(&addr4.sin_zero, '\0', 8);
>> +
>> +	const struct rule rules[] = {
>> +		{
>> +			.path = dir_s1d2,
>> +			.access = ACCESS_RO,
>> +		},
>> +		{},
>> +	};
>> +
>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +	};
>> +	struct landlock_net_service_attr net_service = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> +
>> +		.port = sock_port,
>> +	};
>> +
>> +	/* Creates ruleset for network access. */
>> +	const int ruleset_fd_net = landlock_create_ruleset(
>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>> +	ASSERT_LE(0, ruleset_fd_net);
>> +
>> +	/* Adds a network rule. */
>> +	ASSERT_EQ(0,
>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>> +				    &net_service, 0));
>> +
>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>> +
>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>> +	ASSERT_LE(0, ruleset_fd);
>> +	enforce_ruleset(_metadata, ruleset_fd);
>> +	ASSERT_EQ(0, close(ruleset_fd));
>> +
>> +	/* Tests on a directory with the network rule loaded. */
>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>> +
>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port 15000. */
>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>> +
>> +	/* Closes bounded socket. */
>> +	ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>>   TEST_HARNESS_MAIN
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> new file mode 100644
>> index 000000000000..d1548bd7ab60
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -0,0 +1,823 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Landlock tests - Network
>> + *
>> + * Copyright (C) 2022 Huawei Tech. Co., Ltd.
>> + */
>> +
>> +#define _GNU_SOURCE
>> +#include <arpa/inet.h>
>> +#include <errno.h>
>> +#include <fcntl.h>
>> +#include <linux/landlock.h>
>> +#include <netinet/in.h>
>> +#include <sched.h>
>> +#include <string.h>
>> +#include <sys/prctl.h>
>> +#include <sys/socket.h>
>> +#include <sys/types.h>
>> +
>> +#include "common.h"
>> +
>> +#define MAX_SOCKET_NUM 10
>> +
>> +#define SOCK_PORT_START 3470
>> +#define SOCK_PORT_ADD 10
>> +
>> +#define IP_ADDRESS "127.0.0.1"
>> +
>> +/* Number pending connections queue to be hold. */
>> +#define BACKLOG 10
>> +
>> +const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
>> +
>> +/* Invalid attribute, out of landlock network access range. */
>> +#define LANDLOCK_INVAL_ATTR 7
>> +
>> +FIXTURE(socket)
>> +{
>> +	uint port[MAX_SOCKET_NUM];
>> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
>> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
>> +};
>> +
>> +/* struct _fixture_variant_socket */
>> +FIXTURE_VARIANT(socket)
>> +{
>> +	const bool is_ipv4;
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(socket, ipv4) {
>> +	/* clang-format on */
>> +	.is_ipv4 = true,
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(socket, ipv6) {
>> +	/* clang-format on */
>> +	.is_ipv4 = false,
>> +};
>> +
>> +static int
>> +create_socket_variant(const struct _fixture_variant_socket *const variant,
>> +		      const int type)
>> +{
>> +	if (variant->is_ipv4)
>> +		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
>> +	else
>> +		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
>> +}
>> +
>> +static int bind_variant(const struct _fixture_variant_socket *const variant,
>> +			const int sockfd,
>> +			const struct _test_data_socket *const self,
>> +			const size_t index)
>> +{
>> +	if (variant->is_ipv4)
>> +		return bind(sockfd, &self->addr4[index],
>> +			    sizeof(self->addr4[index]));
>> +	else
>> +		return bind(sockfd, &self->addr6[index],
>> +			    sizeof(self->addr6[index]));
>> +}
>> +
>> +static int connect_variant(const struct _fixture_variant_socket *const variant,
>> +			   const int sockfd,
>> +			   const struct _test_data_socket *const self,
>> +			   const size_t index)
>> +{
>> +	if (variant->is_ipv4)
>> +		return connect(sockfd, &self->addr4[index],
>> +			       sizeof(self->addr4[index]));
>> +	else
>> +		return connect(sockfd, &self->addr6[index],
>> +			       sizeof(self->addr6[index]));
>> +}
>> +
>> +FIXTURE_SETUP(socket)
>> +{
>> +	int i;
>> +
>> +	/* Creates IPv4 socket addresses. */
>> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
>> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
>> +		self->addr4[i].sin_family = AF_INET;
>> +		self->addr4[i].sin_port = htons(self->port[i]);
>> +		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS);
>> +		memset(&(self->addr4[i].sin_zero), '\0', 8);
>> +	}
>> +
>> +	/* Creates IPv6 socket addresses. */
>> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
>> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
>> +		self->addr6[i].sin6_family = AF_INET6;
>> +		self->addr6[i].sin6_port = htons(self->port[i]);
>> +		inet_pton(AF_INET6, IP_ADDRESS, &(self->addr6[i].sin6_addr));
>> +	}
>> +
>> +	set_cap(_metadata, CAP_SYS_ADMIN);
>> +	ASSERT_EQ(0, unshare(CLONE_NEWNET));
>> +	ASSERT_EQ(0, system("ip link set dev lo up"));
>> +	clear_cap(_metadata, CAP_SYS_ADMIN);
>> +}
>> +
>> +FIXTURE_TEARDOWN(socket)
>> +{
>> +}
>> +
>> +TEST_F_FORK(socket, bind_no_restrictions)
>> +{
>> +	int sockfd;
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +
>> +	/* Binds a socket to port[0]. */
>> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
>> +
>> +	ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>> +TEST_F_FORK(socket, bind_with_restrictions)
>> +{
>> +	int sockfd;
>> +
>> +	struct landlock_ruleset_attr ruleset_attr = {
>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +	};
>> +	struct landlock_net_service_attr net_service_1 = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +		.port = self->port[0],
>> +	};
>> +	struct landlock_net_service_attr net_service_2 = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +		.port = self->port[1],
>> +	};
>> +	struct landlock_net_service_attr net_service_3 = {
>> +		.allowed_access = 0,
>> +		.port = self->port[2],
>> +	};
>> +
>> +	const int ruleset_fd =
>> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>> +	ASSERT_LE(0, ruleset_fd);
>> +
>> +	/* Allows connect and bind operations to the port[0] socket. */
>> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +				       &net_service_1, 0));
>> +	/* Allows connect and deny bind operations to the port[1] socket. */
>> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +				       &net_service_2, 0));
>> +	/*
>> +	 * Empty allowed_access (i.e. deny rules) are ignored in network actions
>> +	 * for port[2] socket.
>> +	 */
>> +	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +					&net_service_3, 0));
>> +	ASSERT_EQ(ENOMSG, errno);
>> +
>> +	/* Enforces the ruleset. */
>> +	enforce_ruleset(_metadata, ruleset_fd);
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port[0]. */
>> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
>> +
>> +	/* Closes bounded socket. */
>> +	ASSERT_EQ(0, close(sockfd));
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port[1]. */
>> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 1));
>> +	ASSERT_EQ(EACCES, errno);
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port[2]. */
>> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2));
>> +	ASSERT_EQ(EACCES, errno);
> 
> This is inconsistent with the bind_no_restrictions test. If you
> deduplicate the tests with and without restrictions (i.e. only one
> "bind" test, and another "connect"…), you can extend
> FIXTURE_VARIANT(socket) with a new const bool enforce_landlock, and
> check that in all tests to either do Landlock syscalls or not. You can
> still initialize most variable whatever Landlock should be enforced or
> not (e.g. ruleset_attr, net_service_1…) to make it easiear to read.
> 

   I think it's not a deduplication. Tests enforeced with landlock are 
more various regarding port and net_service attributes used. The number 
of landlock atributes vary from test ot test. I'dont see how to unify it
with FIXTURE_VARIANT and enforce_landlock const will it make harder 
merging tests.
Please your opinion and suggestions?

> 
>> +}
>> +
>> +TEST_F_FORK(socket, connect_no_restrictions)
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2023-01-10  4:45             ` Konstantin Meskhidze (A)
@ 2023-01-10 17:24               ` Mickaël Salaün
  2023-01-11  1:54                 ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-10 17:24 UTC (permalink / raw)
  To: Konstantin Meskhidze (A),
	netdev, linux-api, Alejandro Colomar (man-pages)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module,
	netfilter-devel, artem.kuzin


On 10/01/2023 05:45, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/9/2023 3:38 PM, Mickaël Salaün пишет:
>>
>> On 09/01/2023 09:07, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 1/6/2023 10:30 PM, Mickaël Salaün пишет:
>>>>
>>>> On 05/01/2023 09:57, Konstantin Meskhidze (A) wrote:
>>>>>
>>>>>
>>>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>>>
>>>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>>>> particular ports.
>>>>>>
>>>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>>>> restrict TCP socket binding and connection to specific ports.
>>>>>>
>>>>>>
>>>>>>>
>>>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>>>> ---
>>>>>>>
>>>>>>> Changes since v7:
>>>>>>> * Minor fixes.
>>>>>>> * Refactors commit message.
>>>>>>>
>>>>>>> Changes since v6:
>>>>>>> * Updates copyright.
>>>>>>> * Refactors landlock_append_net_rule() and check_socket_access()
>>>>>>>       functions with landlock_id type.
>>>>>>>
>>>>>>> Changes since v5:
>>>>>>> * Fixes some logic errors.
>>>>>>> * Formats code with clang-format-14.
>>>>>>>
>>>>>>> Changes since v4:
>>>>>>> * Factors out CONFIG_INET into make file.
>>>>>>> * Refactors check_socket_access().
>>>>>>> * Adds helper get_port().
>>>>>>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>>>>>>> functions to support AF_INET6 family.
>>>>>>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>>>>>>> functions.
>>>>>>> * Refactors add_rule_net_service() and landlock_add_rule
>>>>>>> syscall to support network rule inserting.
>>>>>>> * Refactors init_layer_masks() to support network rules.
>>>>>>>
>>>>>>> Changes since v3:
>>>>>>> * Splits commit.
>>>>>>> * Adds SECURITY_NETWORK in config.
>>>>>>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>>>>>>> * Adds hook_socket_bind and hook_socket_connect hooks.
>>>>>>>
>>>>>>> ---
>>>>>>>      security/landlock/Kconfig    |   1 +
>>>>>>>      security/landlock/Makefile   |   2 +
>>>>>>>      security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>>>>>>      security/landlock/net.h      |  26 ++++++
>>>>>>>      security/landlock/setup.c    |   2 +
>>>>>>>      security/landlock/syscalls.c |  59 ++++++++++++-
>>>>>>>      6 files changed, 251 insertions(+), 3 deletions(-)
>>>>>>>      create mode 100644 security/landlock/net.c
>>>>>>>      create mode 100644 security/landlock/net.h
>>>>>>>
>>>>>>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>>>>>>> index 8e33c4e8ffb8..10c099097533 100644
>>>>>>> --- a/security/landlock/Kconfig
>>>>>>> +++ b/security/landlock/Kconfig
>>>>>>> @@ -3,6 +3,7 @@
>>>>>>>      config SECURITY_LANDLOCK
>>>>>>>      	bool "Landlock support"
>>>>>>>      	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>>>>>>> +	select SECURITY_NETWORK
>>>>>>>      	select SECURITY_PATH
>>>>>>>      	help
>>>>>>>      	  Landlock is a sandboxing mechanism that enables processes to restrict
>>>>>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>>>>>> index 7bbd2f413b3e..53d3c92ae22e 100644
>>>>>>> --- a/security/landlock/Makefile
>>>>>>> +++ b/security/landlock/Makefile
>>>>>>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>>>>>>>
>>>>>>>      landlock-y := setup.o syscalls.o object.o ruleset.o \
>>>>>>>      	cred.o ptrace.o fs.o
>>>>>>> +
>>>>>>> +landlock-$(CONFIG_INET) += net.o
>>>>>>> \ No newline at end of file
>>>>>>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>>>>>>> new file mode 100644
>>>>>>> index 000000000000..39e8a156a1f4
>>>>>>> --- /dev/null
>>>>>>> +++ b/security/landlock/net.c
>>>>>>> @@ -0,0 +1,164 @@
>>>>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>>>>> +/*
>>>>>>> + * Landlock LSM - Network management and hooks
>>>>>>> + *
>>>>>>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>>>>>>> + * Copyright © 2022 Microsoft Corporation
>>>>>>> + */
>>>>>>> +
>>>>>>> +#include <linux/in.h>
>>>>>>> +#include <linux/net.h>
>>>>>>> +#include <linux/socket.h>
>>>>>>> +#include <net/ipv6.h>
>>>>>>> +
>>>>>>> +#include "common.h"
>>>>>>> +#include "cred.h"
>>>>>>> +#include "limits.h"
>>>>>>> +#include "net.h"
>>>>>>> +#include "ruleset.h"
>>>>>>> +
>>>>>>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>>>>>>> +			     const u16 port, access_mask_t access_rights)
>>>>>>> +{
>>>>>>> +	int err;
>>>>>>> +	const struct landlock_id id = {
>>>>>>> +		.key.data = port,
>>>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>>>> +	};
>>>>>>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>>>>>>> +
>>>>>>> +	/* Transforms relative access rights to absolute ones. */
>>>>>>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>>>>>>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>>>>>>> +
>>>>>>> +	mutex_lock(&ruleset->lock);
>>>>>>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>>>>>> +	mutex_unlock(&ruleset->lock);
>>>>>>> +
>>>>>>> +	return err;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>>>>>> +			       u16 port, access_mask_t access_request)
>>>>>>> +{
>>>>>>> +	bool allowed = false;
>>>>>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>>>>>> +	const struct landlock_rule *rule;
>>>>>>> +	access_mask_t handled_access;
>>>>>>> +	const struct landlock_id id = {
>>>>>>> +		.key.data = port,
>>>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>>>> +	};
>>>>>>> +
>>>>>>> +	if (WARN_ON_ONCE(!domain))
>>>>>>> +		return 0;
>>>>>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>>>>>> +		return -EACCES;
>>>>>>> +
>>>>>>> +	rule = landlock_find_rule(domain, id);
>>>>>>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>>>>>>> +					  LANDLOCK_KEY_NET_PORT);
>>>>>>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>>>>>>> +				ARRAY_SIZE(layer_masks));
>>>>>>> +
>>>>>>> +	return allowed ? 0 : -EACCES;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static u16 get_port(const struct sockaddr *const address)
>>>>>>
>>>>>> get_port() should return a __be16 type. This enables to avoid converting
>>>>>> port when checking a rule.
>>>>>
>>>>>       In this case a user must do a coverting port into __be16:
>>>>>
>>>>>       struct landlock_net_service_attr net_service = {
>>>>>                     .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>>
>>>>>                     .port = htons(sock_port),
>>>>>             };
>>>>>      I think that a user should not think about this conversion cause it
>>>>> makes UAPI more complex to use. Lets do this under kernel's hood and let
>>>>> it as it is now -> u16 port.
>>>>>
>>>>> What do you think?
>>>>
>>>> BE and LE conversions may be error prone without strong typing, but the
>>>> current Linux network UAPI uses this convention (see related syscalls),
>>>> so developers already use htons() in their applications. I think it is
>>>> less hazardous to use the same convention. It would be nice to have the
>>>> point of view of network and API folks though.
>>>
>>>      Ok. Thanks. Let ports be in BE format like in network packets.
>>>
>>>      What should a selftest with port conversion be like?
>>>
>>>      1. Set a port with a Landlock rule with no conversion. get an error
>>> wit bind/connect actions.
>>>      2. Convert a port with htons(sock_port). get no error.
>>>
>>>      What do you think?
>>
>> Right, you can do both on a LE architecture (that must be checked in the
>> test or it should be skipped), test with a port value that has different
>> representation in LE and BE.
> 
>     Do you mean to check architecture in a test first and then port
> representaton? What about BE architectures? My current VM is X86-64
> architecture a LE one. I can test just it now.

It's just that tests should pass whatever architecture they are run on. 
So we need to check that the current architecture is LE to check against 
an LE result but not against a BE one, and vice versa. In fact no test 
should be skipped, just the result to compare with adjusted (i.e. either 
it pass or it failed).

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

* Re: [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-01-10  5:03     ` Konstantin Meskhidze (A)
@ 2023-01-10 17:40       ` Mickaël Salaün
  2023-01-11  1:52         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 87+ messages in thread
From: Mickaël Salaün @ 2023-01-10 17:40 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin


On 10/01/2023 06:03, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/9/2023 3:46 PM, Mickaël Salaün пишет:
>>
>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>> These test suites try to check edge cases for TCP sockets
>>> bind() and connect() actions.
>>>
>>> socket:
>>> * bind_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
>>> * bind_with_restrictions: Tests with mixed landlock rules for ipv4 and
>>> ipv6 sockets.
>>> * connect_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
>>> * connect_with_restrictions: Tests with mixed landlock rules for ipv4 and
>>> ipv6 sockets.
>>> * connect_afunspec_no_restrictions: Tests with no landlock restrictions
>>> allowing to disconnect already connected socket with AF_UNSPEC socket
>>> family.
>>> * connect_afunspec_with_restrictions: Tests with landlocked process
>>> refusing to disconnect already connected socket.
>>> * ruleset_overlap: Tests with overlapping rules for one port.
>>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>>> gradually added one by one, restricting sockets' connections.
>>> * inval: Tests with invalid user space supplied data:
>>>       - out of range ruleset attribute;
>>>       - unhandled allowed access;
>>>       - zero port value;
>>>       - zero access value;
>>>       - legitimate access values;
>>>
>>> layout1:
>>> * with_net: Tests with network bind() socket action within
>>> filesystem directory access test.
>>>
>>> Test coverage for security/landlock is 94.3% of 920 lines according
>>> to gcc/gcov-11.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v7:
>>> * Squashes all selftest commits.
>>> * Adds fs test with network bind() socket action.
>>> * Minor fixes.
>>>
>>> ---
>>>    security/landlock/ruleset.h                 |   2 -
>>>    tools/testing/selftests/landlock/config     |   4 +
>>>    tools/testing/selftests/landlock/fs_test.c  |  65 ++
>>>    tools/testing/selftests/landlock/net_test.c | 823 ++++++++++++++++++++
>>>    4 files changed, 892 insertions(+), 2 deletions(-)
>>>    create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>
>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>> index f272d2cd518c..ee1a02a404ce 100644
>>> --- a/security/landlock/ruleset.h
>>> +++ b/security/landlock/ruleset.h
>>> @@ -264,7 +264,6 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>>>
>>>    	/* Should already be checked in sys_landlock_create_ruleset(). */
>>>    	WARN_ON_ONCE(fs_access_mask != fs_mask);
>>> -	// TODO: Add tests to check "|=" and not "="
>>>    	ruleset->access_masks[layer_level] |=
>>>    		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
>>>    }
>>> @@ -278,7 +277,6 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
>>>
>>>    	/* Should already be checked in sys_landlock_create_ruleset(). */
>>>    	WARN_ON_ONCE(net_access_mask != net_mask);
>>> -	// TODO: Add tests to check "|=" and not "="
>>>    	ruleset->access_masks[layer_level] |=
>>>    		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
>>>    }
>>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>>> index 0f0a65287bac..71f7e9a8a64c 100644
>>> --- a/tools/testing/selftests/landlock/config
>>> +++ b/tools/testing/selftests/landlock/config
>>> @@ -1,3 +1,7 @@
>>> +CONFIG_INET=y
>>> +CONFIG_IPV6=y
>>> +CONFIG_NET=y
>>> +CONFIG_NET_NS=y
>>>    CONFIG_OVERLAY_FS=y
>>>    CONFIG_SECURITY_LANDLOCK=y
>>>    CONFIG_SECURITY_PATH=y
>>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>>> index 20c1ac8485f1..5c52da1a5a69 100644
>>> --- a/tools/testing/selftests/landlock/fs_test.c
>>> +++ b/tools/testing/selftests/landlock/fs_test.c
>>> @@ -8,14 +8,17 @@
>>>     */
>>>
>>>    #define _GNU_SOURCE
>>> +#include <arpa/inet.h>
>>>    #include <fcntl.h>
>>>    #include <linux/landlock.h>
>>> +#include <netinet/in.h>
>>>    #include <sched.h>
>>>    #include <string.h>
>>>    #include <sys/capability.h>
>>>    #include <sys/mount.h>
>>>    #include <sys/prctl.h>
>>>    #include <sys/sendfile.h>
>>> +#include <sys/socket.h>
>>>    #include <sys/stat.h>
>>>    #include <sys/sysmacros.h>
>>>    #include <unistd.h>
>>> @@ -4366,4 +4369,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>>    	}
>>>    }
>>>
>>> +#define IP_ADDRESS "127.0.0.1"
>>> +
>>> +TEST_F_FORK(layout1, with_net)
>>> +{
>>> +	int sockfd;
>>> +	int sock_port = 15000;
>>> +	struct sockaddr_in addr4;
>>> +
>>> +	addr4.sin_family = AF_INET;
>>> +	addr4.sin_port = htons(sock_port);
>>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>>> +	memset(&addr4.sin_zero, '\0', 8);
>>> +
>>> +	const struct rule rules[] = {
>>> +		{
>>> +			.path = dir_s1d2,
>>> +			.access = ACCESS_RO,
>>> +		},
>>> +		{},
>>> +	};
>>> +
>>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> +	};
>>> +	struct landlock_net_service_attr net_service = {
>>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>> +
>>> +		.port = sock_port,
>>> +	};
>>> +
>>> +	/* Creates ruleset for network access. */
>>> +	const int ruleset_fd_net = landlock_create_ruleset(
>>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>>> +	ASSERT_LE(0, ruleset_fd_net);
>>> +
>>> +	/* Adds a network rule. */
>>> +	ASSERT_EQ(0,
>>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>>> +				    &net_service, 0));
>>> +
>>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>>> +
>>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>>> +	ASSERT_LE(0, ruleset_fd);
>>> +	enforce_ruleset(_metadata, ruleset_fd);
>>> +	ASSERT_EQ(0, close(ruleset_fd));
>>> +
>>> +	/* Tests on a directory with the network rule loaded. */
>>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>>> +
>>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>>> +	ASSERT_LE(0, sockfd);
>>> +	/* Binds a socket to port 15000. */
>>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>>> +
>>> +	/* Closes bounded socket. */
>>> +	ASSERT_EQ(0, close(sockfd));
>>> +}
>>> +
>>>    TEST_HARNESS_MAIN
>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>> new file mode 100644
>>> index 000000000000..d1548bd7ab60
>>> --- /dev/null
>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>> @@ -0,0 +1,823 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Landlock tests - Network
>>> + *
>>> + * Copyright (C) 2022 Huawei Tech. Co., Ltd.
>>> + */
>>> +
>>> +#define _GNU_SOURCE
>>> +#include <arpa/inet.h>
>>> +#include <errno.h>
>>> +#include <fcntl.h>
>>> +#include <linux/landlock.h>
>>> +#include <netinet/in.h>
>>> +#include <sched.h>
>>> +#include <string.h>
>>> +#include <sys/prctl.h>
>>> +#include <sys/socket.h>
>>> +#include <sys/types.h>
>>> +
>>> +#include "common.h"
>>> +
>>> +#define MAX_SOCKET_NUM 10
>>> +
>>> +#define SOCK_PORT_START 3470
>>> +#define SOCK_PORT_ADD 10
>>> +
>>> +#define IP_ADDRESS "127.0.0.1"
>>> +
>>> +/* Number pending connections queue to be hold. */
>>> +#define BACKLOG 10
>>> +
>>> +const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
>>> +
>>> +/* Invalid attribute, out of landlock network access range. */
>>> +#define LANDLOCK_INVAL_ATTR 7
>>> +
>>> +FIXTURE(socket)
>>> +{
>>> +	uint port[MAX_SOCKET_NUM];
>>> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
>>> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
>>> +};
>>> +
>>> +/* struct _fixture_variant_socket */
>>> +FIXTURE_VARIANT(socket)
>>> +{
>>> +	const bool is_ipv4;
>>> +};
>>> +
>>> +/* clang-format off */
>>> +FIXTURE_VARIANT_ADD(socket, ipv4) {
>>> +	/* clang-format on */
>>> +	.is_ipv4 = true,
>>> +};
>>> +
>>> +/* clang-format off */
>>> +FIXTURE_VARIANT_ADD(socket, ipv6) {
>>> +	/* clang-format on */
>>> +	.is_ipv4 = false,
>>> +};
>>> +
>>> +static int
>>> +create_socket_variant(const struct _fixture_variant_socket *const variant,
>>> +		      const int type)
>>> +{
>>> +	if (variant->is_ipv4)
>>> +		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
>>> +	else
>>> +		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
>>> +}
>>> +
>>> +static int bind_variant(const struct _fixture_variant_socket *const variant,
>>> +			const int sockfd,
>>> +			const struct _test_data_socket *const self,
>>> +			const size_t index)
>>> +{
>>> +	if (variant->is_ipv4)
>>> +		return bind(sockfd, &self->addr4[index],
>>> +			    sizeof(self->addr4[index]));
>>> +	else
>>> +		return bind(sockfd, &self->addr6[index],
>>> +			    sizeof(self->addr6[index]));
>>> +}
>>> +
>>> +static int connect_variant(const struct _fixture_variant_socket *const variant,
>>> +			   const int sockfd,
>>> +			   const struct _test_data_socket *const self,
>>> +			   const size_t index)
>>> +{
>>> +	if (variant->is_ipv4)
>>> +		return connect(sockfd, &self->addr4[index],
>>> +			       sizeof(self->addr4[index]));
>>> +	else
>>> +		return connect(sockfd, &self->addr6[index],
>>> +			       sizeof(self->addr6[index]));
>>> +}
>>> +
>>> +FIXTURE_SETUP(socket)
>>> +{
>>> +	int i;
>>> +
>>> +	/* Creates IPv4 socket addresses. */
>>> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
>>> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
>>> +		self->addr4[i].sin_family = AF_INET;
>>> +		self->addr4[i].sin_port = htons(self->port[i]);
>>> +		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS);
>>> +		memset(&(self->addr4[i].sin_zero), '\0', 8);
>>> +	}
>>> +
>>> +	/* Creates IPv6 socket addresses. */
>>> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
>>> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
>>> +		self->addr6[i].sin6_family = AF_INET6;
>>> +		self->addr6[i].sin6_port = htons(self->port[i]);
>>> +		inet_pton(AF_INET6, IP_ADDRESS, &(self->addr6[i].sin6_addr));
>>> +	}
>>> +
>>> +	set_cap(_metadata, CAP_SYS_ADMIN);
>>> +	ASSERT_EQ(0, unshare(CLONE_NEWNET));
>>> +	ASSERT_EQ(0, system("ip link set dev lo up"));
>>> +	clear_cap(_metadata, CAP_SYS_ADMIN);
>>> +}
>>> +
>>> +FIXTURE_TEARDOWN(socket)
>>> +{
>>> +}
>>> +
>>> +TEST_F_FORK(socket, bind_no_restrictions)
>>> +{
>>> +	int sockfd;
>>> +
>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>> +	ASSERT_LE(0, sockfd);
>>> +
>>> +	/* Binds a socket to port[0]. */
>>> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
>>> +
>>> +	ASSERT_EQ(0, close(sockfd));
>>> +}
>>> +
>>> +TEST_F_FORK(socket, bind_with_restrictions)
>>> +{
>>> +	int sockfd;
>>> +
>>> +	struct landlock_ruleset_attr ruleset_attr = {
>>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> +	};
>>> +	struct landlock_net_service_attr net_service_1 = {
>>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> +				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> +		.port = self->port[0],
>>> +	};
>>> +	struct landlock_net_service_attr net_service_2 = {
>>> +		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> +		.port = self->port[1],
>>> +	};
>>> +	struct landlock_net_service_attr net_service_3 = {
>>> +		.allowed_access = 0,
>>> +		.port = self->port[2],
>>> +	};
>>> +
>>> +	const int ruleset_fd =
>>> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>>> +	ASSERT_LE(0, ruleset_fd);
>>> +
>>> +	/* Allows connect and bind operations to the port[0] socket. */
>>> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>> +				       &net_service_1, 0));
>>> +	/* Allows connect and deny bind operations to the port[1] socket. */
>>> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>> +				       &net_service_2, 0));
>>> +	/*
>>> +	 * Empty allowed_access (i.e. deny rules) are ignored in network actions
>>> +	 * for port[2] socket.
>>> +	 */
>>> +	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>> +					&net_service_3, 0));
>>> +	ASSERT_EQ(ENOMSG, errno);
>>> +
>>> +	/* Enforces the ruleset. */
>>> +	enforce_ruleset(_metadata, ruleset_fd);
>>> +
>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>> +	ASSERT_LE(0, sockfd);
>>> +	/* Binds a socket to port[0]. */
>>> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
>>> +
>>> +	/* Closes bounded socket. */
>>> +	ASSERT_EQ(0, close(sockfd));
>>> +
>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>> +	ASSERT_LE(0, sockfd);
>>> +	/* Binds a socket to port[1]. */
>>> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 1));
>>> +	ASSERT_EQ(EACCES, errno);
>>> +
>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>> +	ASSERT_LE(0, sockfd);
>>> +	/* Binds a socket to port[2]. */
>>> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2));
>>> +	ASSERT_EQ(EACCES, errno);
>>
>> This is inconsistent with the bind_no_restrictions test. If you
>> deduplicate the tests with and without restrictions (i.e. only one
>> "bind" test, and another "connect"…), you can extend
>> FIXTURE_VARIANT(socket) with a new const bool enforce_landlock, and
>> check that in all tests to either do Landlock syscalls or not. You can
>> still initialize most variable whatever Landlock should be enforced or
>> not (e.g. ruleset_attr, net_service_1…) to make it easiear to read.
>>
> 
>     I think it's not a deduplication. Tests enforeced with landlock are
> more various regarding port and net_service attributes used. The number
> of landlock atributes vary from test ot test. I'dont see how to unify it
> with FIXTURE_VARIANT and enforce_landlock const will it make harder
> merging tests.
> Please your opinion and suggestions?

What about that?

TEST_F_FORK(socket, bind)
{
	int sockfd;

	struct landlock_ruleset_attr ruleset_attr = {
		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
	};
	struct landlock_net_service_attr net_service_1 = {
		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
		.port = self->port[0],
	};
	struct landlock_net_service_attr net_service_2 = {
		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
		.port = self->port[1],
	};
	struct landlock_net_service_attr net_service_3 = {
		.allowed_access = 0,
		.port = self->port[2],
	};
	int ruleset_fd, ret;

	if (variant->is_sandboxed) {
		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
						     sizeof(ruleset_attr), 0);
		ASSERT_LE(0, ruleset_fd);

		/* Allows connect and bind operations to the port[0] socket. */
		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
					       LANDLOCK_RULE_NET_SERVICE,
					       &net_service_1, 0));

		/* Allows connect and deny bind operations to the port[1] socket. */
		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
					       LANDLOCK_RULE_NET_SERVICE,
					       &net_service_2, 0));

		/*
		 * Empty allowed_access (i.e. deny rules) are ignored in network actions
		 * for port[2] socket.
		 */
		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
						LANDLOCK_RULE_NET_SERVICE,
						&net_service_3, 0));
		ASSERT_EQ(ENOMSG, errno);

		enforce_ruleset(_metadata, ruleset_fd);
		ASSERT_EQ(0, close(ruleset_fd));
	}

	sockfd = create_socket_variant(variant, SOCK_STREAM);
	ASSERT_LE(0, sockfd);
	/* Binds a socket to port[0]. */
	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));

	/* Closes bounded socket. */
	ASSERT_EQ(0, close(sockfd));

	sockfd = create_socket_variant(variant, SOCK_STREAM);
	ASSERT_LE(0, sockfd);
	/* Binds a socket to port[1]. */
	ret = bind_variant(variant, sockfd, self, 1);
	if (variant->is_sandboxed) {
		ASSERT_EQ(-1, ret);
		ASSERT_EQ(EACCES, errno);
	} else {
		ASSERT_EQ(0, ret);
	}

	sockfd = create_socket_variant(variant, SOCK_STREAM);
	ASSERT_LE(0, sockfd);
	/* Binds a socket to port[2]. */
	ret = bind_variant(variant, sockfd, self, 2);
	if (variant->is_sandboxed) {
		ASSERT_EQ(-1, ret);
		ASSERT_EQ(EACCES, errno);
	} else {
		ASSERT_EQ(0, ret);
	}
}

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

* Re: [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-01-10 17:40       ` Mickaël Salaün
@ 2023-01-11  1:52         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-11  1:52 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, artem.kuzin



1/10/2023 8:40 PM, Mickaël Salaün пишет:
> 
> On 10/01/2023 06:03, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/9/2023 3:46 PM, Mickaël Salaün пишет:
>>>
>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>> These test suites try to check edge cases for TCP sockets
>>>> bind() and connect() actions.
>>>>
>>>> socket:
>>>> * bind_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
>>>> * bind_with_restrictions: Tests with mixed landlock rules for ipv4 and
>>>> ipv6 sockets.
>>>> * connect_no_restrictions: Tests with non-landlocked ipv4 and ipv6 sockets.
>>>> * connect_with_restrictions: Tests with mixed landlock rules for ipv4 and
>>>> ipv6 sockets.
>>>> * connect_afunspec_no_restrictions: Tests with no landlock restrictions
>>>> allowing to disconnect already connected socket with AF_UNSPEC socket
>>>> family.
>>>> * connect_afunspec_with_restrictions: Tests with landlocked process
>>>> refusing to disconnect already connected socket.
>>>> * ruleset_overlap: Tests with overlapping rules for one port.
>>>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>>>> gradually added one by one, restricting sockets' connections.
>>>> * inval: Tests with invalid user space supplied data:
>>>>       - out of range ruleset attribute;
>>>>       - unhandled allowed access;
>>>>       - zero port value;
>>>>       - zero access value;
>>>>       - legitimate access values;
>>>>
>>>> layout1:
>>>> * with_net: Tests with network bind() socket action within
>>>> filesystem directory access test.
>>>>
>>>> Test coverage for security/landlock is 94.3% of 920 lines according
>>>> to gcc/gcov-11.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v7:
>>>> * Squashes all selftest commits.
>>>> * Adds fs test with network bind() socket action.
>>>> * Minor fixes.
>>>>
>>>> ---
>>>>    security/landlock/ruleset.h                 |   2 -
>>>>    tools/testing/selftests/landlock/config     |   4 +
>>>>    tools/testing/selftests/landlock/fs_test.c  |  65 ++
>>>>    tools/testing/selftests/landlock/net_test.c | 823 ++++++++++++++++++++
>>>>    4 files changed, 892 insertions(+), 2 deletions(-)
>>>>    create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>>
>>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>>> index f272d2cd518c..ee1a02a404ce 100644
>>>> --- a/security/landlock/ruleset.h
>>>> +++ b/security/landlock/ruleset.h
>>>> @@ -264,7 +264,6 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
>>>>
>>>>    	/* Should already be checked in sys_landlock_create_ruleset(). */
>>>>    	WARN_ON_ONCE(fs_access_mask != fs_mask);
>>>> -	// TODO: Add tests to check "|=" and not "="
>>>>    	ruleset->access_masks[layer_level] |=
>>>>    		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
>>>>    }
>>>> @@ -278,7 +277,6 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
>>>>
>>>>    	/* Should already be checked in sys_landlock_create_ruleset(). */
>>>>    	WARN_ON_ONCE(net_access_mask != net_mask);
>>>> -	// TODO: Add tests to check "|=" and not "="
>>>>    	ruleset->access_masks[layer_level] |=
>>>>    		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
>>>>    }
>>>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>>>> index 0f0a65287bac..71f7e9a8a64c 100644
>>>> --- a/tools/testing/selftests/landlock/config
>>>> +++ b/tools/testing/selftests/landlock/config
>>>> @@ -1,3 +1,7 @@
>>>> +CONFIG_INET=y
>>>> +CONFIG_IPV6=y
>>>> +CONFIG_NET=y
>>>> +CONFIG_NET_NS=y
>>>>    CONFIG_OVERLAY_FS=y
>>>>    CONFIG_SECURITY_LANDLOCK=y
>>>>    CONFIG_SECURITY_PATH=y
>>>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>>>> index 20c1ac8485f1..5c52da1a5a69 100644
>>>> --- a/tools/testing/selftests/landlock/fs_test.c
>>>> +++ b/tools/testing/selftests/landlock/fs_test.c
>>>> @@ -8,14 +8,17 @@
>>>>     */
>>>>
>>>>    #define _GNU_SOURCE
>>>> +#include <arpa/inet.h>
>>>>    #include <fcntl.h>
>>>>    #include <linux/landlock.h>
>>>> +#include <netinet/in.h>
>>>>    #include <sched.h>
>>>>    #include <string.h>
>>>>    #include <sys/capability.h>
>>>>    #include <sys/mount.h>
>>>>    #include <sys/prctl.h>
>>>>    #include <sys/sendfile.h>
>>>> +#include <sys/socket.h>
>>>>    #include <sys/stat.h>
>>>>    #include <sys/sysmacros.h>
>>>>    #include <unistd.h>
>>>> @@ -4366,4 +4369,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>>>    	}
>>>>    }
>>>>
>>>> +#define IP_ADDRESS "127.0.0.1"
>>>> +
>>>> +TEST_F_FORK(layout1, with_net)
>>>> +{
>>>> +	int sockfd;
>>>> +	int sock_port = 15000;
>>>> +	struct sockaddr_in addr4;
>>>> +
>>>> +	addr4.sin_family = AF_INET;
>>>> +	addr4.sin_port = htons(sock_port);
>>>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>>>> +	memset(&addr4.sin_zero, '\0', 8);
>>>> +
>>>> +	const struct rule rules[] = {
>>>> +		{
>>>> +			.path = dir_s1d2,
>>>> +			.access = ACCESS_RO,
>>>> +		},
>>>> +		{},
>>>> +	};
>>>> +
>>>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>>>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>> +	};
>>>> +	struct landlock_net_service_attr net_service = {
>>>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>> +
>>>> +		.port = sock_port,
>>>> +	};
>>>> +
>>>> +	/* Creates ruleset for network access. */
>>>> +	const int ruleset_fd_net = landlock_create_ruleset(
>>>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>>>> +	ASSERT_LE(0, ruleset_fd_net);
>>>> +
>>>> +	/* Adds a network rule. */
>>>> +	ASSERT_EQ(0,
>>>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>>>> +				    &net_service, 0));
>>>> +
>>>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>>>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>>>> +
>>>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>>>> +	ASSERT_LE(0, ruleset_fd);
>>>> +	enforce_ruleset(_metadata, ruleset_fd);
>>>> +	ASSERT_EQ(0, close(ruleset_fd));
>>>> +
>>>> +	/* Tests on a directory with the network rule loaded. */
>>>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>>>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>>>> +
>>>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>>>> +	ASSERT_LE(0, sockfd);
>>>> +	/* Binds a socket to port 15000. */
>>>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>>>> +
>>>> +	/* Closes bounded socket. */
>>>> +	ASSERT_EQ(0, close(sockfd));
>>>> +}
>>>> +
>>>>    TEST_HARNESS_MAIN
>>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>>> new file mode 100644
>>>> index 000000000000..d1548bd7ab60
>>>> --- /dev/null
>>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>>> @@ -0,0 +1,823 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +/*
>>>> + * Landlock tests - Network
>>>> + *
>>>> + * Copyright (C) 2022 Huawei Tech. Co., Ltd.
>>>> + */
>>>> +
>>>> +#define _GNU_SOURCE
>>>> +#include <arpa/inet.h>
>>>> +#include <errno.h>
>>>> +#include <fcntl.h>
>>>> +#include <linux/landlock.h>
>>>> +#include <netinet/in.h>
>>>> +#include <sched.h>
>>>> +#include <string.h>
>>>> +#include <sys/prctl.h>
>>>> +#include <sys/socket.h>
>>>> +#include <sys/types.h>
>>>> +
>>>> +#include "common.h"
>>>> +
>>>> +#define MAX_SOCKET_NUM 10
>>>> +
>>>> +#define SOCK_PORT_START 3470
>>>> +#define SOCK_PORT_ADD 10
>>>> +
>>>> +#define IP_ADDRESS "127.0.0.1"
>>>> +
>>>> +/* Number pending connections queue to be hold. */
>>>> +#define BACKLOG 10
>>>> +
>>>> +const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
>>>> +
>>>> +/* Invalid attribute, out of landlock network access range. */
>>>> +#define LANDLOCK_INVAL_ATTR 7
>>>> +
>>>> +FIXTURE(socket)
>>>> +{
>>>> +	uint port[MAX_SOCKET_NUM];
>>>> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
>>>> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
>>>> +};
>>>> +
>>>> +/* struct _fixture_variant_socket */
>>>> +FIXTURE_VARIANT(socket)
>>>> +{
>>>> +	const bool is_ipv4;
>>>> +};
>>>> +
>>>> +/* clang-format off */
>>>> +FIXTURE_VARIANT_ADD(socket, ipv4) {
>>>> +	/* clang-format on */
>>>> +	.is_ipv4 = true,
>>>> +};
>>>> +
>>>> +/* clang-format off */
>>>> +FIXTURE_VARIANT_ADD(socket, ipv6) {
>>>> +	/* clang-format on */
>>>> +	.is_ipv4 = false,
>>>> +};
>>>> +
>>>> +static int
>>>> +create_socket_variant(const struct _fixture_variant_socket *const variant,
>>>> +		      const int type)
>>>> +{
>>>> +	if (variant->is_ipv4)
>>>> +		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
>>>> +	else
>>>> +		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
>>>> +}
>>>> +
>>>> +static int bind_variant(const struct _fixture_variant_socket *const variant,
>>>> +			const int sockfd,
>>>> +			const struct _test_data_socket *const self,
>>>> +			const size_t index)
>>>> +{
>>>> +	if (variant->is_ipv4)
>>>> +		return bind(sockfd, &self->addr4[index],
>>>> +			    sizeof(self->addr4[index]));
>>>> +	else
>>>> +		return bind(sockfd, &self->addr6[index],
>>>> +			    sizeof(self->addr6[index]));
>>>> +}
>>>> +
>>>> +static int connect_variant(const struct _fixture_variant_socket *const variant,
>>>> +			   const int sockfd,
>>>> +			   const struct _test_data_socket *const self,
>>>> +			   const size_t index)
>>>> +{
>>>> +	if (variant->is_ipv4)
>>>> +		return connect(sockfd, &self->addr4[index],
>>>> +			       sizeof(self->addr4[index]));
>>>> +	else
>>>> +		return connect(sockfd, &self->addr6[index],
>>>> +			       sizeof(self->addr6[index]));
>>>> +}
>>>> +
>>>> +FIXTURE_SETUP(socket)
>>>> +{
>>>> +	int i;
>>>> +
>>>> +	/* Creates IPv4 socket addresses. */
>>>> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
>>>> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
>>>> +		self->addr4[i].sin_family = AF_INET;
>>>> +		self->addr4[i].sin_port = htons(self->port[i]);
>>>> +		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS);
>>>> +		memset(&(self->addr4[i].sin_zero), '\0', 8);
>>>> +	}
>>>> +
>>>> +	/* Creates IPv6 socket addresses. */
>>>> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {
>>>> +		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
>>>> +		self->addr6[i].sin6_family = AF_INET6;
>>>> +		self->addr6[i].sin6_port = htons(self->port[i]);
>>>> +		inet_pton(AF_INET6, IP_ADDRESS, &(self->addr6[i].sin6_addr));
>>>> +	}
>>>> +
>>>> +	set_cap(_metadata, CAP_SYS_ADMIN);
>>>> +	ASSERT_EQ(0, unshare(CLONE_NEWNET));
>>>> +	ASSERT_EQ(0, system("ip link set dev lo up"));
>>>> +	clear_cap(_metadata, CAP_SYS_ADMIN);
>>>> +}
>>>> +
>>>> +FIXTURE_TEARDOWN(socket)
>>>> +{
>>>> +}
>>>> +
>>>> +TEST_F_FORK(socket, bind_no_restrictions)
>>>> +{
>>>> +	int sockfd;
>>>> +
>>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>>> +	ASSERT_LE(0, sockfd);
>>>> +
>>>> +	/* Binds a socket to port[0]. */
>>>> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
>>>> +
>>>> +	ASSERT_EQ(0, close(sockfd));
>>>> +}
>>>> +
>>>> +TEST_F_FORK(socket, bind_with_restrictions)
>>>> +{
>>>> +	int sockfd;
>>>> +
>>>> +	struct landlock_ruleset_attr ruleset_attr = {
>>>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>> +	};
>>>> +	struct landlock_net_service_attr net_service_1 = {
>>>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>> +				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>> +		.port = self->port[0],
>>>> +	};
>>>> +	struct landlock_net_service_attr net_service_2 = {
>>>> +		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>> +		.port = self->port[1],
>>>> +	};
>>>> +	struct landlock_net_service_attr net_service_3 = {
>>>> +		.allowed_access = 0,
>>>> +		.port = self->port[2],
>>>> +	};
>>>> +
>>>> +	const int ruleset_fd =
>>>> +		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>>>> +	ASSERT_LE(0, ruleset_fd);
>>>> +
>>>> +	/* Allows connect and bind operations to the port[0] socket. */
>>>> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>> +				       &net_service_1, 0));
>>>> +	/* Allows connect and deny bind operations to the port[1] socket. */
>>>> +	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>> +				       &net_service_2, 0));
>>>> +	/*
>>>> +	 * Empty allowed_access (i.e. deny rules) are ignored in network actions
>>>> +	 * for port[2] socket.
>>>> +	 */
>>>> +	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>> +					&net_service_3, 0));
>>>> +	ASSERT_EQ(ENOMSG, errno);
>>>> +
>>>> +	/* Enforces the ruleset. */
>>>> +	enforce_ruleset(_metadata, ruleset_fd);
>>>> +
>>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>>> +	ASSERT_LE(0, sockfd);
>>>> +	/* Binds a socket to port[0]. */
>>>> +	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
>>>> +
>>>> +	/* Closes bounded socket. */
>>>> +	ASSERT_EQ(0, close(sockfd));
>>>> +
>>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>>> +	ASSERT_LE(0, sockfd);
>>>> +	/* Binds a socket to port[1]. */
>>>> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 1));
>>>> +	ASSERT_EQ(EACCES, errno);
>>>> +
>>>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>>>> +	ASSERT_LE(0, sockfd);
>>>> +	/* Binds a socket to port[2]. */
>>>> +	ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2));
>>>> +	ASSERT_EQ(EACCES, errno);
>>>
>>> This is inconsistent with the bind_no_restrictions test. If you
>>> deduplicate the tests with and without restrictions (i.e. only one
>>> "bind" test, and another "connect"…), you can extend
>>> FIXTURE_VARIANT(socket) with a new const bool enforce_landlock, and
>>> check that in all tests to either do Landlock syscalls or not. You can
>>> still initialize most variable whatever Landlock should be enforced or
>>> not (e.g. ruleset_attr, net_service_1…) to make it easiear to read.
>>>
>> 
>>     I think it's not a deduplication. Tests enforeced with landlock are
>> more various regarding port and net_service attributes used. The number
>> of landlock atributes vary from test ot test. I'dont see how to unify it
>> with FIXTURE_VARIANT and enforce_landlock const will it make harder
>> merging tests.
>> Please your opinion and suggestions?
> 
> What about that?


> 
> TEST_F_FORK(socket, bind)
> {
> 	int sockfd;
> 
> 	struct landlock_ruleset_attr ruleset_attr = {
> 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> 				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
> 	};
> 	struct landlock_net_service_attr net_service_1 = {
> 		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> 				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
> 		.port = self->port[0],
> 	};
> 	struct landlock_net_service_attr net_service_2 = {
> 		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> 		.port = self->port[1],
> 	};
> 	struct landlock_net_service_attr net_service_3 = {
> 		.allowed_access = 0,
> 		.port = self->port[2],
> 	};
> 	int ruleset_fd, ret;
> 
> 	if (variant->is_sandboxed) {
> 		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> 						     sizeof(ruleset_attr), 0);
> 		ASSERT_LE(0, ruleset_fd);
> 
> 		/* Allows connect and bind operations to the port[0] socket. */
> 		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
> 					       LANDLOCK_RULE_NET_SERVICE,
> 					       &net_service_1, 0));
> 
> 		/* Allows connect and deny bind operations to the port[1] socket. */
> 		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
> 					       LANDLOCK_RULE_NET_SERVICE,
> 					       &net_service_2, 0));
> 
> 		/*
> 		 * Empty allowed_access (i.e. deny rules) are ignored in network actions
> 		 * for port[2] socket.
> 		 */
> 		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
> 						LANDLOCK_RULE_NET_SERVICE,
> 						&net_service_3, 0));
> 		ASSERT_EQ(ENOMSG, errno);
> 
> 		enforce_ruleset(_metadata, ruleset_fd);
> 		ASSERT_EQ(0, close(ruleset_fd));
> 	}
> 
> 	sockfd = create_socket_variant(variant, SOCK_STREAM);
> 	ASSERT_LE(0, sockfd);
> 	/* Binds a socket to port[0]. */
> 	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0));
> 
> 	/* Closes bounded socket. */
> 	ASSERT_EQ(0, close(sockfd));
> 
> 	sockfd = create_socket_variant(variant, SOCK_STREAM);
> 	ASSERT_LE(0, sockfd);
> 	/* Binds a socket to port[1]. */
> 	ret = bind_variant(variant, sockfd, self, 1);
> 	if (variant->is_sandboxed) {
> 		ASSERT_EQ(-1, ret);
> 		ASSERT_EQ(EACCES, errno);
> 	} else {
> 		ASSERT_EQ(0, ret);
> 	}
> 
> 	sockfd = create_socket_variant(variant, SOCK_STREAM);
> 	ASSERT_LE(0, sockfd);
> 	/* Binds a socket to port[2]. */
> 	ret = bind_variant(variant, sockfd, self, 2);
> 	if (variant->is_sandboxed) {
> 		ASSERT_EQ(-1, ret);
> 		ASSERT_EQ(EACCES, errno);
> 	} else {
> 		ASSERT_EQ(0, ret);
> 	}
> }

   oh...This way.
   Sorry. There was a misunderstadting from me.
   Got your point now. Thanks for the tip.
> .

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

* Re: [PATCH v8 08/12] landlock: Implement TCP network hooks
  2023-01-10 17:24               ` Mickaël Salaün
@ 2023-01-11  1:54                 ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 87+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-11  1:54 UTC (permalink / raw)
  To: Mickaël Salaün, netdev, linux-api,
	Alejandro Colomar (man-pages)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module,
	netfilter-devel, artem.kuzin



1/10/2023 8:24 PM, Mickaël Salaün пишет:
> 
> On 10/01/2023 05:45, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/9/2023 3:38 PM, Mickaël Salaün пишет:
>>>
>>> On 09/01/2023 09:07, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 1/6/2023 10:30 PM, Mickaël Salaün пишет:
>>>>>
>>>>> On 05/01/2023 09:57, Konstantin Meskhidze (A) wrote:
>>>>>>
>>>>>>
>>>>>> 11/17/2022 9:43 PM, Mickaël Salaün пишет:
>>>>>>>
>>>>>>> On 21/10/2022 17:26, Konstantin Meskhidze wrote:
>>>>>>>> This patch adds support of socket_bind() and socket_connect() hooks.
>>>>>>>> It's possible to restrict binding and connecting of TCP sockets to
>>>>>>>> particular ports.
>>>>>>>
>>>>>>> Implement socket_bind() and socket_connect LSM hooks, which enable to
>>>>>>> restrict TCP socket binding and connection to specific ports.
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> Changes since v7:
>>>>>>>> * Minor fixes.
>>>>>>>> * Refactors commit message.
>>>>>>>>
>>>>>>>> Changes since v6:
>>>>>>>> * Updates copyright.
>>>>>>>> * Refactors landlock_append_net_rule() and check_socket_access()
>>>>>>>>       functions with landlock_id type.
>>>>>>>>
>>>>>>>> Changes since v5:
>>>>>>>> * Fixes some logic errors.
>>>>>>>> * Formats code with clang-format-14.
>>>>>>>>
>>>>>>>> Changes since v4:
>>>>>>>> * Factors out CONFIG_INET into make file.
>>>>>>>> * Refactors check_socket_access().
>>>>>>>> * Adds helper get_port().
>>>>>>>> * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect
>>>>>>>> functions to support AF_INET6 family.
>>>>>>>> * Adds AF_UNSPEC family support in hook_socket_bind/connect
>>>>>>>> functions.
>>>>>>>> * Refactors add_rule_net_service() and landlock_add_rule
>>>>>>>> syscall to support network rule inserting.
>>>>>>>> * Refactors init_layer_masks() to support network rules.
>>>>>>>>
>>>>>>>> Changes since v3:
>>>>>>>> * Splits commit.
>>>>>>>> * Adds SECURITY_NETWORK in config.
>>>>>>>> * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>>>>>>>> * Adds hook_socket_bind and hook_socket_connect hooks.
>>>>>>>>
>>>>>>>> ---
>>>>>>>>      security/landlock/Kconfig    |   1 +
>>>>>>>>      security/landlock/Makefile   |   2 +
>>>>>>>>      security/landlock/net.c      | 164 +++++++++++++++++++++++++++++++++++
>>>>>>>>      security/landlock/net.h      |  26 ++++++
>>>>>>>>      security/landlock/setup.c    |   2 +
>>>>>>>>      security/landlock/syscalls.c |  59 ++++++++++++-
>>>>>>>>      6 files changed, 251 insertions(+), 3 deletions(-)
>>>>>>>>      create mode 100644 security/landlock/net.c
>>>>>>>>      create mode 100644 security/landlock/net.h
>>>>>>>>
>>>>>>>> diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
>>>>>>>> index 8e33c4e8ffb8..10c099097533 100644
>>>>>>>> --- a/security/landlock/Kconfig
>>>>>>>> +++ b/security/landlock/Kconfig
>>>>>>>> @@ -3,6 +3,7 @@
>>>>>>>>      config SECURITY_LANDLOCK
>>>>>>>>      	bool "Landlock support"
>>>>>>>>      	depends on SECURITY && !ARCH_EPHEMERAL_INODES
>>>>>>>> +	select SECURITY_NETWORK
>>>>>>>>      	select SECURITY_PATH
>>>>>>>>      	help
>>>>>>>>      	  Landlock is a sandboxing mechanism that enables processes to restrict
>>>>>>>> diff --git a/security/landlock/Makefile b/security/landlock/Makefile
>>>>>>>> index 7bbd2f413b3e..53d3c92ae22e 100644
>>>>>>>> --- a/security/landlock/Makefile
>>>>>>>> +++ b/security/landlock/Makefile
>>>>>>>> @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
>>>>>>>>
>>>>>>>>      landlock-y := setup.o syscalls.o object.o ruleset.o \
>>>>>>>>      	cred.o ptrace.o fs.o
>>>>>>>> +
>>>>>>>> +landlock-$(CONFIG_INET) += net.o
>>>>>>>> \ No newline at end of file
>>>>>>>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000000..39e8a156a1f4
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/security/landlock/net.c
>>>>>>>> @@ -0,0 +1,164 @@
>>>>>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>>>>>> +/*
>>>>>>>> + * Landlock LSM - Network management and hooks
>>>>>>>> + *
>>>>>>>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>>>>>>>> + * Copyright © 2022 Microsoft Corporation
>>>>>>>> + */
>>>>>>>> +
>>>>>>>> +#include <linux/in.h>
>>>>>>>> +#include <linux/net.h>
>>>>>>>> +#include <linux/socket.h>
>>>>>>>> +#include <net/ipv6.h>
>>>>>>>> +
>>>>>>>> +#include "common.h"
>>>>>>>> +#include "cred.h"
>>>>>>>> +#include "limits.h"
>>>>>>>> +#include "net.h"
>>>>>>>> +#include "ruleset.h"
>>>>>>>> +
>>>>>>>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>>>>>>>> +			     const u16 port, access_mask_t access_rights)
>>>>>>>> +{
>>>>>>>> +	int err;
>>>>>>>> +	const struct landlock_id id = {
>>>>>>>> +		.key.data = port,
>>>>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>>>>> +	};
>>>>>>>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>>>>>>>> +
>>>>>>>> +	/* Transforms relative access rights to absolute ones. */
>>>>>>>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>>>>>>>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>>>>>>>> +
>>>>>>>> +	mutex_lock(&ruleset->lock);
>>>>>>>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>>>>>>>> +	mutex_unlock(&ruleset->lock);
>>>>>>>> +
>>>>>>>> +	return err;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>>>>>>> +			       u16 port, access_mask_t access_request)
>>>>>>>> +{
>>>>>>>> +	bool allowed = false;
>>>>>>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>>>>>>> +	const struct landlock_rule *rule;
>>>>>>>> +	access_mask_t handled_access;
>>>>>>>> +	const struct landlock_id id = {
>>>>>>>> +		.key.data = port,
>>>>>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>>>>>> +	};
>>>>>>>> +
>>>>>>>> +	if (WARN_ON_ONCE(!domain))
>>>>>>>> +		return 0;
>>>>>>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>>>>>>> +		return -EACCES;
>>>>>>>> +
>>>>>>>> +	rule = landlock_find_rule(domain, id);
>>>>>>>> +	handled_access = init_layer_masks(domain, access_request, &layer_masks,
>>>>>>>> +					  LANDLOCK_KEY_NET_PORT);
>>>>>>>> +	allowed = unmask_layers(rule, handled_access, &layer_masks,
>>>>>>>> +				ARRAY_SIZE(layer_masks));
>>>>>>>> +
>>>>>>>> +	return allowed ? 0 : -EACCES;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static u16 get_port(const struct sockaddr *const address)
>>>>>>>
>>>>>>> get_port() should return a __be16 type. This enables to avoid converting
>>>>>>> port when checking a rule.
>>>>>>
>>>>>>       In this case a user must do a coverting port into __be16:
>>>>>>
>>>>>>       struct landlock_net_service_attr net_service = {
>>>>>>                     .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>>>
>>>>>>                     .port = htons(sock_port),
>>>>>>             };
>>>>>>      I think that a user should not think about this conversion cause it
>>>>>> makes UAPI more complex to use. Lets do this under kernel's hood and let
>>>>>> it as it is now -> u16 port.
>>>>>>
>>>>>> What do you think?
>>>>>
>>>>> BE and LE conversions may be error prone without strong typing, but the
>>>>> current Linux network UAPI uses this convention (see related syscalls),
>>>>> so developers already use htons() in their applications. I think it is
>>>>> less hazardous to use the same convention. It would be nice to have the
>>>>> point of view of network and API folks though.
>>>>
>>>>      Ok. Thanks. Let ports be in BE format like in network packets.
>>>>
>>>>      What should a selftest with port conversion be like?
>>>>
>>>>      1. Set a port with a Landlock rule with no conversion. get an error
>>>> wit bind/connect actions.
>>>>      2. Convert a port with htons(sock_port). get no error.
>>>>
>>>>      What do you think?
>>>
>>> Right, you can do both on a LE architecture (that must be checked in the
>>> test or it should be skipped), test with a port value that has different
>>> representation in LE and BE.
>> 
>>     Do you mean to check architecture in a test first and then port
>> representaton? What about BE architectures? My current VM is X86-64
>> architecture a LE one. I can test just it now.
> 
> It's just that tests should pass whatever architecture they are run on.
> So we need to check that the current architecture is LE to check against
> an LE result but not against a BE one, and vice versa. In fact no test
> should be skipped, just the result to compare with adjusted (i.e. either
> it pass or it failed).

   Ok. Thanks.
> .

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

end of thread, other threads:[~2023-01-11  1:55 UTC | newest]

Thread overview: 87+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-21 15:26 [PATCH v8 00/12] Network support for Landlock Konstantin Meskhidze
2022-10-21 15:26 ` [PATCH v8 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
2022-11-17 18:41   ` Mickaël Salaün
2022-11-28  2:53     ` Konstantin Meskhidze (A)
2022-11-28 20:22       ` Mickaël Salaün
2022-12-02  2:49         ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
2022-11-17 18:41   ` Mickaël Salaün
2022-11-17 18:55     ` [PATCH] landlock: Allow filesystem layout changes for domains without such rule type Mickaël Salaün
2022-11-18  9:16       ` Mickaël Salaün
2022-11-28  3:04         ` Konstantin Meskhidze (A)
2022-11-28 20:23           ` Mickaël Salaün
2022-12-02  2:50             ` Konstantin Meskhidze (A)
2022-12-24  3:10             ` Konstantin Meskhidze (A)
2022-12-26 21:24               ` Mickaël Salaün
2022-12-27  1:47                 ` Konstantin Meskhidze (A)
2022-11-28  3:02       ` Konstantin Meskhidze (A)
2022-11-28 20:25         ` Mickaël Salaün
2022-12-02  2:51           ` Konstantin Meskhidze (A)
2022-11-22 17:17     ` [PATCH v8 02/12] landlock: Refactor landlock_find_rule/insert_rule Mickaël Salaün
2022-11-28  3:06       ` Konstantin Meskhidze (A)
2022-11-28  2:58     ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 03/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
2022-11-17 18:41   ` Mickaël Salaün
2022-11-28  3:07     ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 04/12] landlock: Move unmask_layers() and init_layer_masks() Konstantin Meskhidze
2022-11-17 18:42   ` Mickaël Salaün
2022-11-28  3:25     ` Konstantin Meskhidze (A)
2022-11-28 20:25       ` Mickaël Salaün
2022-12-02  2:52         ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 05/12] landlock: Refactor " Konstantin Meskhidze
2022-11-17 18:42   ` Mickaël Salaün
2022-11-28  3:30     ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 06/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
2022-11-17 18:42   ` Mickaël Salaün
2022-11-28  3:32     ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 07/12] landlock: Add network rules support Konstantin Meskhidze
2022-11-17 18:43   ` Mickaël Salaün
2022-11-28  4:01     ` Konstantin Meskhidze (A)
2022-11-28 20:26       ` Mickaël Salaün
2022-12-02  2:54         ` Konstantin Meskhidze (A)
2023-01-03 12:44     ` Konstantin Meskhidze (A)
2023-01-04 11:41     ` Konstantin Meskhidze (A)
2023-01-06 19:22       ` Mickaël Salaün
2023-01-09  7:59         ` Konstantin Meskhidze (A)
2023-01-09  8:58           ` Dan Carpenter
2023-01-09  9:26             ` Konstantin Meskhidze (A)
2023-01-09 10:20               ` Dan Carpenter
2023-01-09 11:39                 ` Konstantin Meskhidze (A)
2023-01-09 11:53                   ` Dan Carpenter
2023-01-09 12:18                     ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 08/12] landlock: Implement TCP network hooks Konstantin Meskhidze
2022-11-17 18:43   ` Mickaël Salaün
2022-11-28  8:21     ` Konstantin Meskhidze (A)
2022-11-28 21:00       ` Mickaël Salaün
2022-12-02  3:13         ` Konstantin Meskhidze (A)
2022-12-02 13:01           ` Mickaël Salaün
2022-12-05  2:55             ` Konstantin Meskhidze (A)
2022-12-05 13:18               ` Mickaël Salaün
2023-01-05  8:57     ` Konstantin Meskhidze (A)
2023-01-06 19:30       ` Mickaël Salaün
2023-01-09  8:07         ` Konstantin Meskhidze (A)
2023-01-09 12:38           ` Mickaël Salaün
2023-01-10  4:45             ` Konstantin Meskhidze (A)
2023-01-10 17:24               ` Mickaël Salaün
2023-01-11  1:54                 ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
2022-11-17 18:43   ` Mickaël Salaün
2022-11-28  4:02     ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
2023-01-09 12:46   ` Mickaël Salaün
2023-01-10  5:03     ` Konstantin Meskhidze (A)
2023-01-10 17:40       ` Mickaël Salaün
2023-01-11  1:52         ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 11/12] samples/landlock: Add network demo Konstantin Meskhidze
2022-11-16 14:25   ` Mickaël Salaün
2022-11-28  2:49     ` Konstantin Meskhidze (A)
2022-11-28 20:26       ` Mickaël Salaün
2022-12-02  2:48         ` Konstantin Meskhidze (A)
2023-01-05  3:46     ` Konstantin Meskhidze (A)
2023-01-06 19:34       ` Mickaël Salaün
2023-01-09  7:57         ` Konstantin Meskhidze (A)
2022-10-21 15:26 ` [PATCH v8 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
2022-11-17 18:44   ` Mickaël Salaün
2022-11-28  6:44     ` Konstantin Meskhidze (A)
2022-11-28 20:26       ` Mickaël Salaün
2022-12-02  3:14         ` Konstantin Meskhidze (A)

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