netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 00/15] Network support for Landlock
@ 2022-05-16 15:20 Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 01/15] landlock: access mask renaming Konstantin Meskhidze
                   ` (15 more replies)
  0 siblings, 16 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Hi,
This is a new V5 patch related to Landlock LSM network confinement.
It is based on the latest landlock-wip branch on top of v5.18-rc5:
https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=landlock-wip

It brings refactoring of previous patch version V4.
Added additional selftests for IP6 network families and network namespace.
Added TCP sockets confinement support in sandboxer demo.

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

Still have issue with base_test were compiled without -static flag
(landlock-wip branch without network support)
1. base_test: 6/7 tests passed.
 Error:
 #  RUN           global.inconsistent_attr ...
 # base_test.c:54:inconsistent_attr:Expected ENOMSG (42) == errno (22)
 # inconsistent_attr: Test terminated by assertion
 #          FAIL  global.inconsistent_attr
not ok 1 global.inconsistent_attr

LCOV - code coverage report:
            Hit  Total  Coverage
Lines:      952  1010    94.3 %
Functions:  79   82      96.3 %

Previous versions:
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 (15):
  landlock: access mask renaming
  landlock: landlock_find/insert_rule refactoring
  landlock: merge and inherit function refactoring
  landlock: helper functions refactoring
  landlock: landlock_add_rule syscall refactoring
  landlock: user space API network support
  landlock: add support network rules
  landlock: TCP network hooks implementation
  seltests/landlock: add tests for bind() hooks
  seltests/landlock: add tests for connect() hooks
  seltests/landlock: connect() with AF_UNSPEC tests
  seltests/landlock: rules overlapping test
  seltests/landlock: ruleset expanding test
  seltests/landlock: invalid user input data test
  samples/landlock: adds network demo

 include/uapi/linux/landlock.h                |  48 +
 samples/landlock/sandboxer.c                 | 105 ++-
 security/landlock/Kconfig                    |   1 +
 security/landlock/Makefile                   |   2 +
 security/landlock/fs.c                       | 169 +---
 security/landlock/limits.h                   |   8 +-
 security/landlock/net.c                      | 159 ++++
 security/landlock/net.h                      |  25 +
 security/landlock/ruleset.c                  | 481 ++++++++--
 security/landlock/ruleset.h                  | 102 +-
 security/landlock/setup.c                    |   2 +
 security/landlock/syscalls.c                 | 173 ++--
 tools/testing/selftests/landlock/base_test.c |   4 +-
 tools/testing/selftests/landlock/common.h    |   9 +
 tools/testing/selftests/landlock/config      |   5 +-
 tools/testing/selftests/landlock/fs_test.c   |  10 -
 tools/testing/selftests/landlock/net_test.c  | 935 +++++++++++++++++++
 17 files changed, 1925 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] 56+ messages in thread

* [PATCH v5 01/15] landlock: access mask renaming
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  8:12   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 02/15] landlock: landlock_find/insert_rule refactoring Konstantin Meskhidze
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Currently Landlock supports filesystem
restrictions. To support network type rules,
this modification extends and renames
ruleset's access masks.
This patch adds filesystem helper functions
to set and get filesystem mask. Also the modification
adds a helper structure landlock_access_mask to
support managing multiple access mask.

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

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

Changes since v4:
* Deletes struct landlock_access_mask.

---
 security/landlock/fs.c       | 15 ++++++++-------
 security/landlock/ruleset.c  | 25 +++++++++++++------------
 security/landlock/ruleset.h  | 27 ++++++++++++++++++++++-----
 security/landlock/syscalls.c |  7 ++++---
 4 files changed, 47 insertions(+), 27 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index ec5a6247cd3e..8eea52e5a3a4 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -167,7 +167,8 @@ 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_rights |= LANDLOCK_MASK_ACCESS_FS &
+				~landlock_get_fs_access_mask(ruleset, 0);
 	object = get_inode_object(d_backing_inode(path->dentry));
 	if (IS_ERR(object))
 		return PTR_ERR(object);
@@ -285,9 +286,9 @@ get_handled_accesses(const struct landlock_ruleset *const domain)
 		size_t layer_level;

 		for (layer_level = 0; layer_level < domain->num_layers;
-		     layer_level++) {
-			if (domain->fs_access_masks[layer_level] &
-			    BIT_ULL(access_bit)) {
+				layer_level++) {
+			if (landlock_get_fs_access_mask(domain, layer_level) &
+					BIT_ULL(access_bit)) {
 				access_dom |= BIT_ULL(access_bit);
 				break;
 			}
@@ -315,9 +316,9 @@ init_layer_masks(const struct landlock_ruleset *const domain,
 		unsigned long access_bit;

 		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
-			if (domain->fs_access_masks[layer_level] &
-			    BIT_ULL(access_bit)) {
+				ARRAY_SIZE(*layer_masks)) {
+			if (landlock_get_fs_access_mask(domain, layer_level) &
+					BIT_ULL(access_bit)) {
 				(*layer_masks)[access_bit] |=
 					BIT_ULL(layer_level);
 				handled_accesses |= BIT_ULL(access_bit);
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 996484f98bfd..b8917f6a8050 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -28,9 +28,9 @@ 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),
-			GFP_KERNEL_ACCOUNT);
+	new_ruleset = kzalloc(struct_size(new_ruleset, access_masks,
+				num_layers), GFP_KERNEL_ACCOUNT);
+
 	if (!new_ruleset)
 		return ERR_PTR(-ENOMEM);
 	refcount_set(&new_ruleset->usage, 1);
@@ -40,22 +40,23 @@ 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;
 }

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

 	/* Informs about useless ruleset. */
-	if (!fs_access_mask)
+	if (!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_set_fs_access_mask(new_ruleset, access_mask, 0);
+
 	return new_ruleset;
 }

@@ -117,7 +118,7 @@ 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]) fs_access_mask = ~0;

 	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
 	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
@@ -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..f27a79624962 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -20,6 +20,7 @@
 #include "object.h"

 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 for_each_set_bit() and for_each_clear_bit() calls are OK. */
@@ -110,7 +111,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 +138,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 +149,13 @@ struct landlock_ruleset {
 			 * layers are set once and never changed for the
 			 * lifetime of the ruleset.
 			 */
-			access_mask_t fs_access_masks[];
+			u32 access_masks[];
 		};
 	};
 };

-struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask);
+struct landlock_ruleset *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 +178,20 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
 		refcount_inc(&ruleset->usage);
 }

+/* A helper function to set a filesystem mask */
+static inline void landlock_set_fs_access_mask(struct landlock_ruleset *ruleset,
+				 const access_mask_t access_maskset,
+				 u16 mask_level)
+{
+	ruleset->access_masks[mask_level] = access_maskset;
+}
+
+/* A helper function to get a filesystem mask */
+static inline u32 landlock_get_fs_access_mask(
+					const struct landlock_ruleset *ruleset,
+					u16 mask_level)
+{
+	return ruleset->access_masks[mask_level];
+}
+
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 735a0865ea11..1db799d1a50b 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] 56+ messages in thread

* [PATCH v5 02/15] landlock: landlock_find/insert_rule refactoring
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 01/15] landlock: access mask renaming Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 03/15] landlock: merge and inherit function refactoring Konstantin Meskhidze
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

A new object union added to support a socket port
rule type. To support it landlock_insert_rule() and
landlock_find_rule() were refactored. Now adding
or searching a rule in a ruleset depends on a
rule_type argument provided in refactored
functions mentioned above.

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

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

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

---
 security/landlock/fs.c      |   8 ++-
 security/landlock/ruleset.c | 129 +++++++++++++++++++++++++-----------
 security/landlock/ruleset.h |  32 +++++----
 3 files changed, 113 insertions(+), 56 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 8eea52e5a3a4..5de24d4dd74c 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -173,7 +173,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 	if (IS_ERR(object))
 		return PTR_ERR(object);
 	mutex_lock(&ruleset->lock);
-	err = landlock_insert_rule(ruleset, object, access_rights);
+	err = landlock_insert_rule(ruleset, object, 0, access_rights,
+				   LANDLOCK_RULE_PATH_BENEATH);
 	mutex_unlock(&ruleset->lock);
 	/*
 	 * No need to check for an error because landlock_insert_rule()
@@ -203,8 +204,9 @@ 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));
+	rule = landlock_find_rule(domain,
+			(uintptr_t)rcu_dereference(landlock_inode(inode)->object),
+			LANDLOCK_RULE_PATH_BENEATH);
 	rcu_read_unlock();
 	return rule;
 }
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index b8917f6a8050..f079a2a320f1 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
@@ -69,10 +69,12 @@ static void build_check_rule(void)
 	BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
 }

-static struct landlock_rule *
-create_rule(struct landlock_object *const object,
-	    const struct landlock_layer (*const layers)[], const u32 num_layers,
-	    const struct landlock_layer *const new_layer)
+static struct landlock_rule *create_rule(
+		struct landlock_object *const object_ptr,
+		const uintptr_t object_data,
+		const struct landlock_layer (*const layers)[],
+		const u32 num_layers,
+		const struct landlock_layer *const new_layer)
 {
 	struct landlock_rule *new_rule;
 	u32 new_num_layers;
@@ -91,8 +93,15 @@ 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 (object_ptr) {
+		landlock_get_object(object_ptr);
+		new_rule->object.ptr = object_ptr;
+	} else if (object_ptr && object_data) {
+		WARN_ON_ONCE(1);
+		return ERR_PTR(-EINVAL);
+	}
+
 	new_rule->num_layers = new_num_layers;
 	/* Copies the original layer stack. */
 	memcpy(new_rule->layers, layers,
@@ -108,7 +117,7 @@ static void free_rule(struct landlock_rule *const rule)
 	might_sleep();
 	if (!rule)
 		return;
-	landlock_put_object(rule->object);
+	landlock_put_object(rule->object.ptr);
 	kfree(rule);
 }

@@ -144,26 +153,44 @@ 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_layer (*const layers)[],
-		       size_t num_layers)
+		struct landlock_object *const object_ptr,
+		uintptr_t object_data, u16 rule_type,
+		const struct landlock_layer (*const layers)[],
+		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))
+	/* Choose rb_tree structure depending on a rule type */
+
+	if (WARN_ON_ONCE(!layers))
 		return -ENOENT;
-	walker_node = &(ruleset->root.rb_node);
+	if (WARN_ON_ONCE(object_ptr && object_data))
+		return -EINVAL;
+
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		if (WARN_ON_ONCE(!object_ptr))
+			return -ENOENT;
+		object_data = (uintptr_t)object_ptr;
+		root = &ruleset->root_inode;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return -EINVAL;
+	}
+	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->object.data != object_data) {
 			parent_node = *walker_node;
-			if (this->object < object)
+			if (this->object.data < object_data)
 				walker_node = &((*walker_node)->rb_right);
 			else
 				walker_node = &((*walker_node)->rb_left);
@@ -195,11 +222,16 @@ 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,
-				       &(*layers)[0]);
+		switch (rule_type) {
+		case LANDLOCK_RULE_PATH_BENEATH:
+			new_rule = create_rule(object_ptr, 0, &this->layers,
+					       this->num_layers,
+					       &(*layers)[0]);
+			break;
+		}
 		if (IS_ERR(new_rule))
 			return PTR_ERR(new_rule);
-		rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
+		rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode);
 		free_rule(this);
 		return 0;
 	}
@@ -208,11 +240,15 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 	build_check_ruleset();
 	if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
 		return -E2BIG;
-	new_rule = create_rule(object, layers, num_layers, NULL);
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL);
+		break;
+	}
 	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, &ruleset->root_inode);
 	ruleset->num_rules++;
 	return 0;
 }
@@ -230,8 +266,10 @@ 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 access_mask_t access)
+			 struct landlock_object *const object_ptr,
+			 const uintptr_t object_data,
+			 const access_mask_t access,
+			 const u16 rule_type)
 {
 	struct landlock_layer layers[] = { {
 		.access = access,
@@ -240,7 +278,8 @@ 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, object_ptr, object_data, rule_type, &layers,
+			   ARRAY_SIZE(layers));
 }

 static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -285,9 +324,9 @@ 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,
-					     node) {
-		struct landlock_layer layers[] = { {
+	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+			&src->root_inode, node) {
+		struct landlock_layer layers[] = {{
 			.level = dst->num_layers,
 		} };

@@ -300,7 +339,9 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 			goto out_unlock;
 		}
 		layers[0].access = walker_rule->layers[0].access;
-		err = insert_rule(dst, walker_rule->object, &layers,
+
+		err = insert_rule(dst, walker_rule->object.ptr, 0,
+				  LANDLOCK_RULE_PATH_BENEATH, &layers,
 				  ARRAY_SIZE(layers));
 		if (err)
 			goto out_unlock;
@@ -328,10 +369,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,

 	/* 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,
-				  walker_rule->num_layers);
+			&parent->root_inode, node) {
+		err = insert_rule(child, walker_rule->object.ptr, 0,
+				LANDLOCK_RULE_PATH_BENEATH, &walker_rule->layers,
+				walker_rule->num_layers);
 		if (err)
 			goto out_unlock;
 	}
@@ -362,7 +403,8 @@ 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)
+	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+			node)
 		free_rule(freeme);
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
@@ -452,22 +494,31 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
 /*
  * The returned access has the same lifetime as @ruleset.
  */
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_object *const object)
+const struct landlock_rule *landlock_find_rule(
+		const struct landlock_ruleset *const ruleset,
+		const uintptr_t object_data, const u16 rule_type)
 {
 	const struct rb_node *node;

-	if (!object)
+	if (!object_data)
 		return NULL;
-	node = ruleset->root.rb_node;
+
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		node = ruleset->root_inode.rb_node;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return NULL;
+	}
+
 	while (node) {
 		struct landlock_rule *this =
 			rb_entry(node, struct landlock_rule, node);

-		if (this->object == object)
+		if (this->object.data == object_data)
 			return this;
-		if (this->object < object)
+		if (this->object.data < object_data)
 			node = node->rb_right;
 		else
 			node = node->rb_left;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f27a79624962..3066e5d7180c 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -54,15 +54,17 @@ 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.
-	 */
-	struct landlock_object *object;
-	/**
-	 * @num_layers: Number of entries in @layers.
+	 * @object: 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. This pointer/@object.ptr/ is set once and
+	 * never modified. It always points to an allocated object because each
+	 * rule increments the refcount of its object (for inodes);
 	 */
+	 union {
+		struct landlock_object *ptr;
+		uintptr_t data;
+	 } object;
+
 	u32 num_layers;
 	/**
 	 * @layers: Stack of layers, from the latest to the newest, implemented
@@ -99,7 +101,7 @@ struct landlock_ruleset {
 	 * nodes.  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.
@@ -161,16 +163,18 @@ 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 access_mask_t access);
+			 struct landlock_object *const object_ptr,
+			 const uintptr_t object_data,
+			 const access_mask_t access,
+			 const u16 rule_type);

 struct landlock_ruleset *
 landlock_merge_ruleset(struct landlock_ruleset *const parent,
 		       struct landlock_ruleset *const ruleset);

-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_object *const object);
+const struct landlock_rule *landlock_find_rule(
+		const struct landlock_ruleset *const ruleset,
+		const uintptr_t object_data, const u16 rule_type);

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


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

* [PATCH v5 03/15] landlock: merge and inherit function refactoring
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 01/15] landlock: access mask renaming Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 02/15] landlock: landlock_find/insert_rule refactoring Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  8:14   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 04/15] landlock: helper functions refactoring Konstantin Meskhidze
                   ` (12 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Merge_ruleset() and inherit_ruleset() functions were
refactored to support new rule types. This patch adds
tree_merge() and tree_copy() helpers. Each has
rule_type argument to choose a particular rb_tree
structure in a ruleset.

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

Changes since v3:
* Split commit.
* Refactoring functions:
	-insert_rule.
	-merge_ruleset.
	-tree_merge.
	-inherit_ruleset.
	-tree_copy.
	-free_rule.

Changes since v4:
* None

---
 security/landlock/ruleset.c | 144 ++++++++++++++++++++++++------------
 1 file changed, 98 insertions(+), 46 deletions(-)

diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index f079a2a320f1..4b4c9953bb32 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -112,12 +112,16 @@ static struct landlock_rule *create_rule(
 	return new_rule;
 }

-static void free_rule(struct landlock_rule *const rule)
+static void free_rule(struct landlock_rule *const rule, const u16 rule_type)
 {
 	might_sleep();
 	if (!rule)
 		return;
-	landlock_put_object(rule->object.ptr);
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		landlock_put_object(rule->object.ptr);
+		break;
+	}
 	kfree(rule);
 }

@@ -227,12 +231,12 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 			new_rule = create_rule(object_ptr, 0, &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_inode);
+			free_rule(this, rule_type);
 			break;
 		}
-		if (IS_ERR(new_rule))
-			return PTR_ERR(new_rule);
-		rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode);
-		free_rule(this);
 		return 0;
 	}

@@ -243,13 +247,12 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 	switch (rule_type) {
 	case LANDLOCK_RULE_PATH_BENEATH:
 		new_rule = create_rule(object_ptr, 0, 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_inode);
 		break;
 	}
-	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_inode);
-	ruleset->num_rules++;
 	return 0;
 }

@@ -298,10 +301,53 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy)
 	}
 }

+static int tree_merge(struct landlock_ruleset *const src,
+		struct landlock_ruleset *const dst, u16 rule_type)
+{
+	struct landlock_rule *walker_rule, *next_rule;
+	struct rb_root *src_root;
+	int err = 0;
+
+	/* Choose rb_tree structure depending on a rule type */
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		src_root = &src->root_inode;
+		break;
+	default:
+		return -EINVAL;
+	}
+	/* Merges the @src tree. */
+	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+					     src_root, node) {
+		struct landlock_layer layers[] = {{
+			.level = dst->num_layers,
+		}};
+
+		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
+			err = -EINVAL;
+			return err;
+		}
+		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
+			err = -EINVAL;
+			return err;
+		}
+		layers[0].access = walker_rule->layers[0].access;
+
+		switch (rule_type) {
+		case LANDLOCK_RULE_PATH_BENEATH:
+			err = insert_rule(dst, walker_rule->object.ptr, 0, rule_type,
+					  &layers, ARRAY_SIZE(layers));
+			break;
+		}
+		if (err)
+			return err;
+	}
+	return err;
+}
+
 static int merge_ruleset(struct landlock_ruleset *const dst,
 			 struct landlock_ruleset *const src)
 {
-	struct landlock_rule *walker_rule, *next_rule;
 	int err = 0;

 	might_sleep();
@@ -323,29 +369,10 @@ 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_inode, node) {
-		struct landlock_layer layers[] = {{
-			.level = dst->num_layers,
-		} };
-
-		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;
-		}
-		layers[0].access = walker_rule->layers[0].access;
-
-		err = insert_rule(dst, walker_rule->object.ptr, 0,
-				  LANDLOCK_RULE_PATH_BENEATH, &layers,
-				  ARRAY_SIZE(layers));
-		if (err)
-			goto out_unlock;
-	}
+	/* Merges the @src inode tree. */
+	err = tree_merge(src, dst, LANDLOCK_RULE_PATH_BENEATH);
+	if (err)
+		goto out_unlock;

 out_unlock:
 	mutex_unlock(&src->lock);
@@ -353,10 +380,40 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	return err;
 }

+static int tree_copy(struct landlock_ruleset *const parent,
+		struct landlock_ruleset *const child, u16 rule_type)
+{
+	struct landlock_rule *walker_rule, *next_rule;
+	struct rb_root *parent_root;
+	int err = 0;
+
+	/* Choose rb_tree structure depending on a rule type */
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		parent_root = &parent->root_inode;
+		break;
+	default:
+		return -EINVAL;
+	}
+	/* Copies the @parent inode tree. */
+	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+					     parent_root, node) {
+		switch (rule_type) {
+		case LANDLOCK_RULE_PATH_BENEATH:
+			err = insert_rule(child, walker_rule->object.ptr, 0,
+					  rule_type, &walker_rule->layers,
+					  walker_rule->num_layers);
+			break;
+		}
+		if (err)
+			return err;
+	}
+	return err;
+}
+
 static int inherit_ruleset(struct landlock_ruleset *const parent,
 			   struct landlock_ruleset *const child)
 {
-	struct landlock_rule *walker_rule, *next_rule;
 	int err = 0;

 	might_sleep();
@@ -367,15 +424,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 	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_inode, node) {
-		err = insert_rule(child, walker_rule->object.ptr, 0,
-				LANDLOCK_RULE_PATH_BENEATH, &walker_rule->layers,
-				walker_rule->num_layers);
-		if (err)
-			goto out_unlock;
-	}
+	/* Copies the @parent inode tree. */
+	err = tree_copy(parent, child, LANDLOCK_RULE_PATH_BENEATH);
+	if (err)
+		goto out_unlock;

 	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
 		err = -EINVAL;
@@ -405,7 +457,7 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
 	might_sleep();
 	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
 			node)
-		free_rule(freeme);
+		free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
--
2.25.1


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

* [PATCH v5 04/15] landlock: helper functions refactoring
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (2 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 03/15] landlock: merge and inherit function refactoring Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 17:14   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring Konstantin Meskhidze
                   ` (11 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Unmask_layers(), init_layer_masks() and
get_handled_accesses() helper functions move to
ruleset.c and rule_type argument is added.
This modification supports implementing new rule
types into next landlock versions.

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

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

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

---
 security/landlock/fs.c      | 158 ++++++++----------------------------
 security/landlock/ruleset.c | 152 +++++++++++++++++++++++++++++++---
 security/landlock/ruleset.h |  17 +++-
 3 files changed, 192 insertions(+), 135 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 5de24d4dd74c..3506e182b23e 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -211,60 +211,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
@@ -277,59 +223,6 @@ 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)
-{
-	access_mask_t access_dom = 0;
-	unsigned long access_bit;
-
-	for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
-	     access_bit++) {
-		size_t layer_level;
-
-		for (layer_level = 0; layer_level < domain->num_layers;
-				layer_level++) {
-			if (landlock_get_fs_access_mask(domain, layer_level) &
-					BIT_ULL(access_bit)) {
-				access_dom |= BIT_ULL(access_bit);
-				break;
-			}
-		}
-	}
-	return access_dom;
-}
-
-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)) {
-			if (landlock_get_fs_access_mask(domain, layer_level) &
-					BIT_ULL(access_bit)) {
-				(*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.
@@ -506,7 +399,8 @@ static int check_access_path_dual(
 		 * a superset of the meaningful requested accesses).
 		 */
 		access_masked_parent1 = access_masked_parent2 =
-			get_handled_accesses(domain);
+			get_handled_accesses(domain, LANDLOCK_RULE_PATH_BENEATH,
+					     LANDLOCK_NUM_ACCESS_FS);
 		is_dom_check = true;
 	} else {
 		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
@@ -519,17 +413,25 @@ static int check_access_path_dual(

 	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);
+				init_layer_masks(domain,
+					LANDLOCK_MASK_ACCESS_FS,
+					&_layer_masks_child1,
+					sizeof(_layer_masks_child1),
+					LANDLOCK_RULE_PATH_BENEATH),
+				&_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);
+				init_layer_masks(domain,
+					LANDLOCK_MASK_ACCESS_FS,
+					&_layer_masks_child2,
+					sizeof(_layer_masks_child2),
+					LANDLOCK_RULE_PATH_BENEATH),
+				&_layer_masks_child2,
+				ARRAY_SIZE(_layer_masks_child2));
 		layer_masks_child2 = &_layer_masks_child2;
 		child2_is_directory = d_is_dir(dentry_child2);
 	}
@@ -582,14 +484,15 @@ static int check_access_path_dual(

 		rule = find_rule(domain, walker_path.dentry);
 		allowed_parent1 = unmask_layers(rule, access_masked_parent1,
-						layer_masks_parent1);
+				layer_masks_parent1,
+				ARRAY_SIZE(*layer_masks_parent1));
 		allowed_parent2 = unmask_layers(rule, access_masked_parent2,
-						layer_masks_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)) {
@@ -645,7 +548,9 @@ 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, sizeof(layer_masks),
+			LANDLOCK_RULE_PATH_BENEATH);
 	return check_access_path_dual(domain, path, access_request,
 				      &layer_masks, NULL, 0, NULL, NULL);
 }
@@ -729,7 +634,8 @@ static bool collect_domain_accesses(
 		return true;

 	access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
-				      layer_masks_dom);
+			layer_masks_dom, sizeof(*layer_masks_dom),
+			LANDLOCK_RULE_PATH_BENEATH);

 	dget(dir);
 	while (true) {
@@ -737,7 +643,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.
@@ -851,9 +758,10 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 		 * The LANDLOCK_ACCESS_FS_REFER access right is not required
 		 * for same-directory referer (i.e. no reparenting).
 		 */
-		access_request_parent1 = init_layer_masks(
-			dom, access_request_parent1 | access_request_parent2,
-			&layer_masks_parent1);
+		access_request_parent1 = init_layer_masks(dom,
+				access_request_parent1 | access_request_parent2,
+				&layer_masks_parent1, sizeof(layer_masks_parent1),
+				LANDLOCK_RULE_PATH_BENEATH);
 		return check_access_path_dual(dom, new_dir,
 					      access_request_parent1,
 					      &layer_masks_parent1, NULL, 0,
@@ -861,7 +769,9 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 	}

 	/* Backward compatibility: no reparenting support. */
-	if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER))
+	if (!(get_handled_accesses(dom, LANDLOCK_RULE_PATH_BENEATH,
+				   LANDLOCK_NUM_ACCESS_FS) &
+						LANDLOCK_ACCESS_FS_REFER))
 		return -EXDEV;

 	access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4b4c9953bb32..c4ed783d655b 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -233,7 +233,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 					       &(*layers)[0]);
 			if (IS_ERR(new_rule))
 				return PTR_ERR(new_rule);
-			rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode);
+			rb_replace_node(&this->node, &new_rule->node,
+					&ruleset->root_inode);
 			free_rule(this, rule_type);
 			break;
 		}
@@ -246,7 +247,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 		return -E2BIG;
 	switch (rule_type) {
 	case LANDLOCK_RULE_PATH_BENEATH:
-		new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL);
+		new_rule = create_rule(object_ptr, 0, layers,
+				       num_layers, NULL);
 		if (IS_ERR(new_rule))
 			return PTR_ERR(new_rule);
 		rb_link_node(&new_rule->node, parent_node, walker_node);
@@ -281,8 +283,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
 	} };

 	build_check_layer();
-	return insert_rule(ruleset, object_ptr, object_data, rule_type, &layers,
-			   ARRAY_SIZE(layers));
+	return insert_rule(ruleset, object_ptr, object_data, rule_type,
+			   &layers, ARRAY_SIZE(layers));
 }

 static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -335,8 +337,9 @@ static int tree_merge(struct landlock_ruleset *const src,

 		switch (rule_type) {
 		case LANDLOCK_RULE_PATH_BENEATH:
-			err = insert_rule(dst, walker_rule->object.ptr, 0, rule_type,
-					  &layers, ARRAY_SIZE(layers));
+			err = insert_rule(dst, walker_rule->object.ptr, 0,
+					  rule_type, &layers,
+					  ARRAY_SIZE(layers));
 			break;
 		}
 		if (err)
@@ -433,9 +436,13 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 		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));
+			flex_array_size(parent, access_masks,
+					parent->num_layers));

 	if (WARN_ON_ONCE(!parent->hierarchy)) {
 		err = -EINVAL;
@@ -455,8 +462,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_inode,
-			node)
+	rbtree_postorder_for_each_entry_safe(freeme, next,
+					     &ruleset->root_inode,
+					     node)
 		free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
@@ -577,3 +585,127 @@ const struct landlock_rule *landlock_find_rule(
 	}
 	return NULL;
 }
+
+access_mask_t get_handled_accesses(
+		const struct landlock_ruleset *const domain,
+		u16 rule_type, u16 num_access)
+{
+	access_mask_t access_dom = 0;
+	unsigned long access_bit;
+
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
+			access_bit++) {
+			size_t layer_level;
+
+			for (layer_level = 0; layer_level < domain->num_layers;
+					layer_level++) {
+				if (landlock_get_fs_access_mask(domain,
+								layer_level) &
+						BIT_ULL(access_bit)) {
+					access_dom |= BIT_ULL(access_bit);
+					break;
+				}
+			}
+		}
+		break;
+	default:
+		break;
+	}
+	return access_dom;
+}
+
+/*
+ * @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)[], size_t masks_array_size)
+{
+	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, masks_array_size) {
+			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;
+}
+
+access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
+			       const access_mask_t access_request,
+			       layer_mask_t (*const layer_masks)[],
+			       size_t masks_size,
+			       u16 rule_type)
+{
+	access_mask_t handled_accesses = 0;
+	size_t layer_level;
+
+	memset(layer_masks, 0, masks_size);
+
+	/* 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;
+
+		switch (rule_type) {
+		case LANDLOCK_RULE_PATH_BENEATH:
+			for_each_set_bit(access_bit, &access_req,
+					LANDLOCK_NUM_ACCESS_FS) {
+				if (landlock_get_fs_access_mask(domain,
+								layer_level) &
+						BIT_ULL(access_bit)) {
+					(*layer_masks)[access_bit] |=
+						BIT_ULL(layer_level);
+					handled_accesses |=
+							   BIT_ULL(access_bit);
+				}
+			}
+			break;
+		default:
+			return 0;
+		}
+	}
+	return handled_accesses;
+}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 3066e5d7180c..f3cd890d0348 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -195,7 +195,22 @@ static inline u32 landlock_get_fs_access_mask(
 					const struct landlock_ruleset *ruleset,
 					u16 mask_level)
 {
-	return ruleset->access_masks[mask_level];
+	return (ruleset->access_masks[mask_level] & LANDLOCK_MASK_ACCESS_FS);
 }

+access_mask_t get_handled_accesses(
+		const struct landlock_ruleset *const domain,
+		u16 rule_type, u16 num_access);
+
+bool unmask_layers(const struct landlock_rule *const rule,
+		   const access_mask_t access_request,
+		   layer_mask_t (*const layer_masks)[],
+		   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)[],
+			       size_t masks_size,
+			       u16 rule_type);
+
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.25.1


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

* [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (3 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 04/15] landlock: helper functions refactoring Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  8:04   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 06/15] landlock: user space API network support Konstantin Meskhidze
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Landlock_add_rule syscall was refactored to support new
rule types in future Landlock versions. Add_rule_path_beneath()
helper was added to support current filesystem rules. It is called
by the switch case.

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

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

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

---
 security/landlock/syscalls.c                 | 105 ++++++++++---------
 tools/testing/selftests/landlock/base_test.c |   4 +-
 2 files changed, 59 insertions(+), 50 deletions(-)

diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 1db799d1a50b..412ced6c512f 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -274,67 +274,23 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
 	return err;
 }

-/**
- * 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_attr: Pointer to a rule (only of type &struct
- *             landlock_path_beneath_attr for now).
- * @flags: Must be 0.
- *
- * This system call enables to define a new rule and add it to an existing
- * ruleset.
- *
- * Possible returned errors are:
- *
- * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
- * - 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);
- * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
- * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
- *   member of @rule_attr is not a file descriptor as expected;
- * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
- *   @rule_attr is not the expected file descriptor type;
- * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
- * - EFAULT: @rule_attr inconsistency.
- */
-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)
+static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_attr)
 {
 	struct landlock_path_beneath_attr path_beneath_attr;
 	struct path path;
 	struct landlock_ruleset *ruleset;
 	int res, err;

-	if (!landlock_initialized)
-		return -EOPNOTSUPP;
-
-	/* No flag for now. */
-	if (flags)
-		return -EINVAL;
-
 	/* Gets and checks the ruleset. */
 	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
 	if (IS_ERR(ruleset))
 		return PTR_ERR(ruleset);

-	if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
-		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;
-	}
+				sizeof(path_beneath_attr));
+	if (res)
+		return -EFAULT;

 	/*
 	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
@@ -370,6 +326,59 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	return err;
 }

+/**
+ * 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_attr: Pointer to a rule (only of type &struct
+ *             landlock_path_beneath_attr for now).
+ * @flags: Must be 0.
+ *
+ * This system call enables to define a new rule and add it to an existing
+ * ruleset.
+ *
+ * Possible returned errors are:
+ *
+ * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - 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 rule's
+ *   accesses);
+ * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
+ * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
+ *   member of @rule_attr is not a file descriptor as expected;
+ * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
+ *   @rule_attr is not the expected file descriptor type (e.g. file open
+ *   without O_PATH);
+ * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
+ * - EFAULT: @rule_attr inconsistency.
+ */
+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)
+{
+	int err;
+
+	if (!landlock_initialized)
+		return -EOPNOTSUPP;
+
+	/* No flag for now. */
+	if (flags)
+		return -EINVAL;
+
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		err = add_rule_path_beneath(ruleset_fd, rule_attr);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	return err;
+}
+
 /* Enforcement */

 /**
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index da9290817866..0c4c3a538d54 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -156,11 +156,11 @@ TEST(add_rule_checks_ordering)
 	ASSERT_LE(0, ruleset_fd);

 	/* Checks invalid flags. */
-	ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
+	ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, NULL, 1));
 	ASSERT_EQ(EINVAL, errno);

 	/* Checks invalid ruleset FD. */
-	ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0));
+	ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, NULL, 0));
 	ASSERT_EQ(EBADF, errno);

 	/* Checks invalid rule type. */
--
2.25.1


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

* [PATCH v5 06/15] landlock: user space API network support
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (4 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 07/15] landlock: add support network rules Konstantin Meskhidze
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

User space API was refactored to support
network actions. New network access flags,
network rule and network attributes were
added.

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

Changes since v3:
* Split commit.
* Refactoring User API for network rule type.

Changes since v4:
* None

---
 include/uapi/linux/landlock.h | 48 +++++++++++++++++++++++++++++++++++
 security/landlock/syscalls.c  |  3 ++-
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 23df4e0e8ace..91d6cb359bf8 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
  *
@@ -162,4 +192,22 @@ struct landlock_path_beneath_attr {
 #define LANDLOCK_ACCESS_FS_REFER			(1ULL << 13)
 /* 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.
+ */
+#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
+#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
+
 #endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 412ced6c512f..31f9facec123 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);
--
2.25.1


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

* [PATCH v5 07/15] landlock: add support network rules
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (5 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 06/15] landlock: user space API network support Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  8:27   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 08/15] landlock: TCP network hooks implementation Konstantin Meskhidze
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

This modification adds network rules support
in internal landlock functions (presented in ruleset.c)
and landlock_create_ruleset syscall.

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

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

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

---
 security/landlock/limits.h   |  8 +++-
 security/landlock/ruleset.c  | 82 +++++++++++++++++++++++++++++++-----
 security/landlock/ruleset.h  | 34 +++++++++++++--
 security/landlock/syscalls.c | 45 +++++++++++---------
 4 files changed, 132 insertions(+), 37 deletions(-)

diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index b54184ab9439..23694bf05cb7 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -22,6 +22,12 @@
 #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
 #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)

-/* 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_MASK_SHIFT_NET		16
+
+#define LANDLOCK_RULE_TYPE_NUM		LANDLOCK_RULE_NET_SERVICE

+/* clang-format on */
 #endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index c4ed783d655b..ea9ecb3f471a 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -36,6 +36,7 @@ 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;
+	new_ruleset->root_net_port = RB_ROOT;
 	new_ruleset->num_layers = num_layers;
 	/*
 	 * hierarchy = NULL
@@ -46,17 +47,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 }

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

 	/* Informs about useless ruleset. */
-	if (!access_mask)
+	if (!access_mask_fs && !access_mask_net)
 		return ERR_PTR(-ENOMSG);
 	new_ruleset = create_ruleset(1);
-	if (!IS_ERR(new_ruleset))
-		landlock_set_fs_access_mask(new_ruleset, access_mask, 0);
-
+	if (IS_ERR(new_ruleset))
+		return new_ruleset;
+	if (access_mask_fs)
+		landlock_set_fs_access_mask(new_ruleset, access_mask_fs, 0);
+	if (access_mask_net)
+		landlock_set_net_access_mask(new_ruleset, access_mask_net, 0);
 	return new_ruleset;
 }

@@ -94,9 +99,11 @@ static struct landlock_rule *create_rule(
 		return ERR_PTR(-ENOMEM);
 	RB_CLEAR_NODE(&new_rule->node);

-	if (object_ptr) {
+	if (object_ptr && !object_data) {
 		landlock_get_object(object_ptr);
 		new_rule->object.ptr = object_ptr;
+	} else if (object_data && !object_ptr) {
+		new_rule->object.data = object_data;
 	} else if (object_ptr && object_data) {
 		WARN_ON_ONCE(1);
 		return ERR_PTR(-EINVAL);
@@ -132,10 +139,12 @@ static void build_check_ruleset(void)
 		.num_layers = ~0,
 	};
 	typeof(ruleset.access_masks[0]) fs_access_mask = ~0;
+	typeof(ruleset.access_masks[0]) net_access_mask = ~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(net_access_mask < LANDLOCK_MASK_ACCESS_NET);
 }

 /**
@@ -183,6 +192,11 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 		object_data = (uintptr_t)object_ptr;
 		root = &ruleset->root_inode;
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		if (WARN_ON_ONCE(object_ptr))
+			return -EINVAL;
+		root = &ruleset->root_net_port;
+		break;
 	default:
 		WARN_ON_ONCE(1);
 		return -EINVAL;
@@ -237,6 +251,16 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 					&ruleset->root_inode);
 			free_rule(this, rule_type);
 			break;
+		case LANDLOCK_RULE_NET_SERVICE:
+			new_rule = create_rule(NULL, object_data,
+					       &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_net_port);
+			free_rule(this, rule_type);
+			break;
 		}
 		return 0;
 	}
@@ -254,6 +278,15 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 		rb_link_node(&new_rule->node, parent_node, walker_node);
 		rb_insert_color(&new_rule->node, &ruleset->root_inode);
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		new_rule = create_rule(NULL, object_data, 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_net_port);
+		ruleset->num_rules++;
+		break;
 	}
 	return 0;
 }
@@ -315,6 +348,9 @@ static int tree_merge(struct landlock_ruleset *const src,
 	case LANDLOCK_RULE_PATH_BENEATH:
 		src_root = &src->root_inode;
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		src_root = &src->root_net_port;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -341,6 +377,11 @@ static int tree_merge(struct landlock_ruleset *const src,
 					  rule_type, &layers,
 					  ARRAY_SIZE(layers));
 			break;
+		case LANDLOCK_RULE_NET_SERVICE:
+			err = insert_rule(dst, NULL, walker_rule->object.data,
+					  rule_type, &layers,
+					  ARRAY_SIZE(layers));
+			break;
 		}
 		if (err)
 			return err;
@@ -376,6 +417,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	err = tree_merge(src, dst, LANDLOCK_RULE_PATH_BENEATH);
 	if (err)
 		goto out_unlock;
+	/* Merges the @src network tree. */
+	err = tree_merge(src, dst, LANDLOCK_RULE_NET_SERVICE);
+	if (err)
+		goto out_unlock;

 out_unlock:
 	mutex_unlock(&src->lock);
@@ -395,6 +440,9 @@ static int tree_copy(struct landlock_ruleset *const parent,
 	case LANDLOCK_RULE_PATH_BENEATH:
 		parent_root = &parent->root_inode;
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		parent_root = &parent->root_net_port;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -407,6 +455,12 @@ static int tree_copy(struct landlock_ruleset *const parent,
 					  rule_type, &walker_rule->layers,
 					  walker_rule->num_layers);
 			break;
+		case LANDLOCK_RULE_NET_SERVICE:
+			err = insert_rule(child, NULL,
+					  walker_rule->object.data, rule_type,
+					  &walker_rule->layers,
+					  walker_rule->num_layers);
+			break;
 		}
 		if (err)
 			return err;
@@ -429,6 +483,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,

 	/* Copies the @parent inode tree. */
 	err = tree_copy(parent, child, LANDLOCK_RULE_PATH_BENEATH);
+	if (err)
+		goto out_unlock;
+	/* Copies the @parent inode tree. */
+	err = tree_copy(parent, child, LANDLOCK_RULE_NET_SERVICE);
 	if (err)
 		goto out_unlock;

@@ -463,9 +521,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)

 	might_sleep();
 	rbtree_postorder_for_each_entry_safe(freeme, next,
-					     &ruleset->root_inode,
-					     node)
+					     &ruleset->root_inode, node)
 		free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
+	rbtree_postorder_for_each_entry_safe(freeme, next,
+					     &ruleset->root_net_port, node)
+		free_rule(freeme, LANDLOCK_RULE_NET_SERVICE);
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
@@ -560,13 +620,13 @@ const struct landlock_rule *landlock_find_rule(
 {
 	const struct rb_node *node;

-	if (!object_data)
-		return NULL;
-
 	switch (rule_type) {
 	case LANDLOCK_RULE_PATH_BENEATH:
 		node = ruleset->root_inode.rb_node;
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		node = ruleset->root_net_port.rb_node;
+		break;
 	default:
 		WARN_ON_ONCE(1);
 		return NULL;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f3cd890d0348..916b30b31c06 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -102,6 +102,12 @@ struct landlock_ruleset {
 	 * tree is immutable until @usage reaches zero.
 	 */
 	struct rb_root root_inode;
+	/**
+	 * @root_net_port: Root of a red-black tree containing object nodes
+	 * for 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;
 	/**
 	 * @hierarchy: Enables hierarchy identification even when a parent
 	 * domain vanishes.  This is needed for the ptrace protection.
@@ -157,7 +163,8 @@ struct landlock_ruleset {
 };

 struct landlock_ruleset *landlock_create_ruleset(
-		const access_mask_t access_mask);
+					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);
@@ -183,11 +190,12 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
 }

 /* A helper function to set a filesystem mask */
-static inline void landlock_set_fs_access_mask(struct landlock_ruleset *ruleset,
-				 const access_mask_t access_maskset,
+static inline void landlock_set_fs_access_mask(
+				 struct landlock_ruleset *ruleset,
+				 const access_mask_t access_mask_fs,
 				 u16 mask_level)
 {
-	ruleset->access_masks[mask_level] = access_maskset;
+	ruleset->access_masks[mask_level] = access_mask_fs;
 }

 /* A helper function to get a filesystem mask */
@@ -198,6 +206,24 @@ static inline u32 landlock_get_fs_access_mask(
 	return (ruleset->access_masks[mask_level] & LANDLOCK_MASK_ACCESS_FS);
 }

+/* A helper function to set a network mask */
+static inline void landlock_set_net_access_mask(
+				  struct landlock_ruleset *ruleset,
+				  const access_mask_t access_mask_net,
+				  u16 mask_level)
+{
+	ruleset->access_masks[mask_level] |= (access_mask_net <<
+					      LANDLOCK_MASK_SHIFT_NET);
+}
+
+/* A helper function to get a network mask */
+static inline u32 landlock_get_net_access_mask(
+				const struct landlock_ruleset *ruleset,
+				u16 mask_level)
+{
+	return (ruleset->access_masks[mask_level] >> LANDLOCK_MASK_SHIFT_NET);
+}
+
 access_mask_t get_handled_accesses(
 		const struct landlock_ruleset *const domain,
 		u16 rule_type, u16 num_access);
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 31f9facec123..812541f4e155 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -189,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);

@@ -275,21 +281,17 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
 	return err;
 }

-static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_attr)
+static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
+				 const void *const rule_attr)
 {
 	struct landlock_path_beneath_attr path_beneath_attr;
 	struct path path;
-	struct landlock_ruleset *ruleset;
 	int res, err;
-
-	/* Gets and checks the ruleset. */
-	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
-	if (IS_ERR(ruleset))
-		return PTR_ERR(ruleset);
+	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));
+			sizeof(path_beneath_attr));
 	if (res)
 		return -EFAULT;

@@ -298,32 +300,26 @@ static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_at
 	 * are ignored in path walks.
 	 */
 	if (!path_beneath_attr.allowed_access) {
-		err = -ENOMSG;
-		goto out_put_ruleset;
+		return -ENOMSG;
 	}
 	/*
 	 * 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;
-	}
+	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)
-		goto out_put_ruleset;
+		return err;

 	/* 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;
 }

@@ -360,6 +356,7 @@ 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_ruleset *ruleset;
 	int err;

 	if (!landlock_initialized)
@@ -369,14 +366,20 @@ SYSCALL_DEFINE4(landlock_add_rule,
 	if (flags)
 		return -EINVAL;

+	/* Gets and checks the ruleset. */
+	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
+	if (IS_ERR(ruleset))
+		return PTR_ERR(ruleset);
+
 	switch (rule_type) {
 	case LANDLOCK_RULE_PATH_BENEATH:
-		err = add_rule_path_beneath(ruleset_fd, rule_attr);
+		err = add_rule_path_beneath(ruleset, rule_attr);
 		break;
 	default:
 		err = -EINVAL;
 		break;
 	}
+	landlock_put_ruleset(ruleset);
 	return err;
 }

--
2.25.1


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

* [PATCH v5 08/15] landlock: TCP network hooks implementation
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (6 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 07/15] landlock: add support network rules Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  8:51   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks Konstantin Meskhidze
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Support of socket_bind() and socket_connect() hooks.
Its possible to restrict binding and connecting of TCP
types of sockets to particular ports. Its just basic idea
how Landlock could support network confinement.

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

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

Changes since v4:
* Factors out CONFIG_INET into make file.
* Refactoring 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.
* Refactoring add_rule_net_service() and landlock_add_rule
syscall to support network rule inserting.
* Refactoring init_layer_masks() to support network rules.

---
 security/landlock/Kconfig    |   1 +
 security/landlock/Makefile   |   2 +
 security/landlock/net.c      | 159 +++++++++++++++++++++++++++++++++++
 security/landlock/net.h      |  25 ++++++
 security/landlock/ruleset.c  |  15 +++-
 security/landlock/setup.c    |   2 +
 security/landlock/syscalls.c |  63 ++++++++++++--
 7 files changed, 261 insertions(+), 6 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..9302e5891991
--- /dev/null
+++ b/security/landlock/net.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright (C) 2022 Huawei Tech. Co., Ltd.
+ */
+
+#include <linux/in.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <net/ipv6.h>
+
+#include "cred.h"
+#include "limits.h"
+#include "net.h"
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+			     u16 port, u32 access_rights)
+{
+	int err;
+
+	/* Transforms relative access rights to absolute ones. */
+	access_rights |= LANDLOCK_MASK_ACCESS_NET &
+			 ~landlock_get_net_access_mask(ruleset, 0);
+
+	BUILD_BUG_ON(sizeof(port) > sizeof(uintptr_t));
+	mutex_lock(&ruleset->lock);
+	err = landlock_insert_rule(ruleset, NULL, port,
+				access_rights, LANDLOCK_RULE_NET_SERVICE);
+	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;
+
+	if (WARN_ON_ONCE(!domain))
+		return 0;
+	if (WARN_ON_ONCE(domain->num_layers < 1))
+		return -EACCES;
+
+	rule = landlock_find_rule(domain, port,
+					LANDLOCK_RULE_NET_SERVICE);
+
+	handled_access = init_layer_masks(domain, access_request,
+			&layer_masks, sizeof(layer_masks),
+			LANDLOCK_RULE_NET_SERVICE);
+	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
+	}
+	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;
+
+	/* Get port value in host byte order */
+	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;
+
+	/* Get port value in host byte order */
+	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..da5ce8fa04cc
--- /dev/null
+++ b/security/landlock/net.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright (C) 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,
+				u16 port, u32 access_hierarchy);
+#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/ruleset.c b/security/landlock/ruleset.c
index ea9ecb3f471a..317cf98890f6 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -671,7 +671,7 @@ access_mask_t get_handled_accesses(
 		}
 		break;
 	default:
-		break;
+		return 0;
 	}
 	return access_dom;
 }
@@ -763,6 +763,19 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
 				}
 			}
 			break;
+		case LANDLOCK_RULE_NET_SERVICE:
+			for_each_set_bit(access_bit, &access_req,
+					LANDLOCK_NUM_ACCESS_NET) {
+				if (landlock_get_net_access_mask(domain,
+								 layer_level) &
+						BIT_ULL(access_bit)) {
+					(*layer_masks)[access_bit] |=
+						BIT_ULL(layer_level);
+					handled_accesses |=
+							   BIT_ULL(access_bit);
+				}
+			}
+			break;
 		default:
 			return 0;
 		}
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index f8e8e980454c..8059dc0b47d3 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;

@@ -28,6 +29,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 812541f4e155..9454c6361011 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 */
@@ -299,9 +306,9 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
 	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
 	 * are ignored in path walks.
 	 */
-	if (!path_beneath_attr.allowed_access) {
+	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).
@@ -323,13 +330,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 *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.
@@ -340,6 +388,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 rule's
  *   accesses);
@@ -375,6 +425,9 @@ SYSCALL_DEFINE4(landlock_add_rule,
 	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] 56+ messages in thread

* [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (7 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 08/15] landlock: TCP network hooks implementation Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 21:11   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 10/15] seltests/landlock: add tests for connect() hooks Konstantin Meskhidze
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Adds selftests for bind socket action.
The first is with no landlock restrictions:
    - bind_no_restrictions_ip4;
    - bind_no_restrictions_ip6;
The second ones is with mixed landlock rules:
    - bind_with_restrictions_ip4;
    - bind_with_restrictions_ip6;

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

Changes since v3:
* Split commit.
* Add helper create_socket.
* Add FIXTURE_SETUP.

Changes since v4:
* Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4
and struct sockaddr_in addr6 in FIXTURE.
* Refactoring FIXTURE_SETUP:
    - initializing self->port, self->addr4 and self->addr6.
    - adding network namespace.
* Refactoring code with self->port, self->addr4 and
self->addr6 variables.
* Adds selftests for IP6 family:
    - bind_no_restrictions_ip6.
    - bind_with_restrictions_ip6.
* Refactoring selftests/landlock/config
* Moves enforce_ruleset() into common.h

---
 tools/testing/selftests/landlock/common.h   |   9 +
 tools/testing/selftests/landlock/config     |   5 +-
 tools/testing/selftests/landlock/fs_test.c  |  10 -
 tools/testing/selftests/landlock/net_test.c | 237 ++++++++++++++++++++
 4 files changed, 250 insertions(+), 11 deletions(-)
 create mode 100644 tools/testing/selftests/landlock/net_test.c

diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 7ba18eb23783..c5381e641dfd 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -102,6 +102,15 @@ static inline int landlock_restrict_self(const int ruleset_fd,
 }
 #endif

+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));
+	}
+}
+
 static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
 {
 	cap_t cap_p;
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 0f0a65287bac..b56f3274d3f5 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -1,7 +1,10 @@
+CONFIG_INET=y
+CONFIG_IPV6=y
+CONFIG_NET=y
 CONFIG_OVERLAY_FS=y
 CONFIG_SECURITY_LANDLOCK=y
 CONFIG_SECURITY_PATH=y
 CONFIG_SECURITY=y
 CONFIG_SHMEM=y
 CONFIG_TMPFS_XATTR=y
-CONFIG_TMPFS=y
+CONFIG_TMPFS=y
\ No newline at end of file
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 21a2ce8fa739..036dd6f8f9ea 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -551,16 +551,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[] = {
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
new file mode 100644
index 000000000000..478ef2eff559
--- /dev/null
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -0,0 +1,237 @@
+// 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
+
+static int create_socket(struct __test_metadata *const _metadata,
+			bool ip6, bool reuse_addr)
+{
+		int sockfd;
+		int one = 1;
+
+		if (ip6)
+			sockfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+		else
+			sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+
+		ASSERT_LE(0, sockfd);
+		/* Allows to reuse of local address */
+		if (reuse_addr)
+			ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET,
+					SO_REUSEADDR, &one, sizeof(one)));
+		return sockfd;
+}
+
+FIXTURE(socket_test) {
+	uint port[MAX_SOCKET_NUM];
+	struct sockaddr_in addr4[MAX_SOCKET_NUM];
+	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
+};
+
+FIXTURE_SETUP(socket_test)
+{
+	int i;
+	/* Creates IP4 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 = htonl(INADDR_ANY);
+		memset(&(self->addr4[i].sin_zero), '\0', 8);
+	}
+
+	/* Creates IP6 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]);
+		self->addr6[i].sin6_addr = in6addr_any;
+	}
+
+	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)
+{ }
+
+TEST_F_FORK(socket_test, bind_no_restrictions_ip4) {
+
+	int sockfd;
+
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds a socket to port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	ASSERT_EQ(0, close(sockfd));
+}
+
+TEST_F_FORK(socket_test, bind_no_restrictions_ip6) {
+
+	int sockfd;
+
+	sockfd = create_socket(_metadata, true, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds a socket to port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0])));
+
+	ASSERT_EQ(0, close(sockfd));
+}
+
+TEST_F_FORK(socket_test, bind_with_restrictions_ip4) {
+
+	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(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/* Close bounded socket*/
+	ASSERT_EQ(0, close(sockfd));
+
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[1] */
+	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[1])));
+	ASSERT_EQ(EACCES, errno);
+
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[2] */
+	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[2], sizeof(self->addr4[2])));
+	ASSERT_EQ(EACCES, errno);
+}
+
+TEST_F_FORK(socket_test, bind_with_restrictions_ip6) {
+
+	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(_metadata, true, false);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0])));
+
+	/* Close bounded socket*/
+	ASSERT_EQ(0, close(sockfd));
+
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[1] */
+	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[1], sizeof(self->addr6[1])));
+	ASSERT_EQ(EACCES, errno);
+
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[2] */
+	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[2], sizeof(self->addr6[2])));
+	ASSERT_EQ(EACCES, errno);
+}
+TEST_HARNESS_MAIN
--
2.25.1


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

* [PATCH v5 10/15] seltests/landlock: add tests for connect() hooks
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (8 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests Konstantin Meskhidze
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Adds selftests for connect socket action.
The first are with no landlock restrictions:
    - connect_no_restrictions_ip4;
    - connect_no_restrictions_ip6;
The second ones are with mixed landlock rules:
    - connect_with_restrictions_ip4;
    - connect_with_restrictions_ip6;

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

Changes since v3:
* Split commit.

Changes since v4:
* Adds selftests for IP6 family:
    - connect_no_restrictions_ip6.
    - connect_with_restrictions_ip6.
* Refactoring code with self->port, self->addr4 and
self->addr6 variables.

---
 tools/testing/selftests/landlock/net_test.c | 322 ++++++++++++++++++++
 1 file changed, 322 insertions(+)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 478ef2eff559..cf914d311eb3 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -234,4 +234,326 @@ TEST_F_FORK(socket_test, bind_with_restrictions_ip6) {
 	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[2], sizeof(self->addr6[2])));
 	ASSERT_EQ(EACCES, errno);
 }
+
+TEST_F_FORK(socket_test, connect_no_restrictions_ip4) {
+
+	int sockfd, new_fd;
+	pid_t child;
+	int status;
+
+	/* Creates a server socket */
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds a socket to port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[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));
+		/* Create a stream client socket */
+		child_sockfd = create_socket(_metadata, false, false);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket */
+		ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[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_test, connect_no_restrictions_ip6) {
+
+	int sockfd, new_fd;
+	pid_t child;
+	int status;
+
+	/* Creates a server socket */
+	sockfd = create_socket(_metadata, true, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds a socket to port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[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));
+		/* Create a stream client socket */
+		child_sockfd = create_socket(_metadata, true, false);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket */
+		ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr6[0],
+						   sizeof(self->addr6[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_test, connect_with_restrictions_ip4) {
+
+	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(_metadata, false, false);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0] */
+	ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[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(_metadata, false, false);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket */
+		ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[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(_metadata, false, false);
+	ASSERT_LE(0, sockfd_2);
+
+	/* Binds the socket 2 to address with port[1] */
+	ASSERT_EQ(0, bind(sockfd_2, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[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(_metadata, false, false);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket */
+		ASSERT_EQ(-1, connect(child_sockfd, (struct sockaddr *)&self->addr4[1],
+						   sizeof(self->addr4[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_test, connect_with_restrictions_ip6) {
+
+	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(_metadata, true, false);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0] */
+	ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[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(_metadata, true, false);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket */
+		ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr6[0],
+						   sizeof(self->addr6[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(_metadata, true, false);
+	ASSERT_LE(0, sockfd_2);
+
+	/* Binds the socket 2 to address with port[1] */
+	ASSERT_EQ(0, bind(sockfd_2, (struct sockaddr *)&self->addr6[1], sizeof(self->addr6[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(_metadata, true, false);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket */
+		ASSERT_EQ(-1, connect(child_sockfd, (struct sockaddr *)&self->addr6[1],
+						   sizeof(self->addr6[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_HARNESS_MAIN
--
2.25.1


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

* [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (9 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 10/15] seltests/landlock: add tests for connect() hooks Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  8:55   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 12/15] seltests/landlock: rules overlapping test Konstantin Meskhidze
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Adds two selftests for connect() action with
AF_UNSPEC family flag.
The one is with no landlock restrictions
allows to disconnect already conneted socket
with connect(..., AF_UNSPEC, ...):
    - connect_afunspec_no_restictions;
The second one refuses landlocked process
to disconnect already connected socket:
    - connect_afunspec_with_restictions;

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

Changes since v3:
* Add connect_afunspec_no_restictions test.
* Add connect_afunspec_with_restictions test.

Changes since v4:
* Refactoring code with self->port, self->addr4 variables.
* Adds bind() hook check for with AF_UNSPEC family.

---
 tools/testing/selftests/landlock/net_test.c | 121 ++++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index cf914d311eb3..bf8e49466d1d 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -449,6 +449,7 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
 	int new_fd;
 	int sockfd_1, sockfd_2;
 	pid_t child_1, child_2;
+
 	int status;

 	struct landlock_ruleset_attr ruleset_attr = {
@@ -467,10 +468,12 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {

 	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,
@@ -480,6 +483,7 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
 	enforce_ruleset(_metadata, ruleset_fd);

 	/* Creates a server socket 1 */
+
 	sockfd_1 = create_socket(_metadata, true, false);
 	ASSERT_LE(0, sockfd_1);

@@ -556,4 +560,121 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
 	ASSERT_EQ(1, WIFEXITED(status));
 	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
 }
+
+TEST_F_FORK(socket_test, connect_afunspec_no_restictions) {
+
+	int sockfd;
+	pid_t child;
+	int status;
+
+	/* Creates a server socket 1 */
+	sockfd = create_socket(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds the socket 1 to address with port[0] with AF_UNSPEC family */
+	self->addr4[0].sin_family = AF_UNSPEC;
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/* Makes connection to socket with port[0] */
+	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[0])));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC};
+
+		/* 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_test, connect_afunspec_with_restictions) {
+
+	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(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds the socket 1 to address with port[0] with AF_UNSPEC family */
+	self->addr4[0].sin_family = AF_UNSPEC;
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/* Makes connection to socket with port[0] */
+	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[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) {
+		struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC};
+
+		/* 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_HARNESS_MAIN
--
2.25.1


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

* [PATCH v5 12/15] seltests/landlock: rules overlapping test
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (10 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 17:41   ` Mickaël Salaün
  2022-05-16 15:20 ` [PATCH v5 13/15] seltests/landlock: ruleset expanding test Konstantin Meskhidze
                   ` (3 subsequent siblings)
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

This patch adds overlapping rules for one port.
First rule adds just bind() access right for a port.
The second one adds both bind() and connect()
access rights for the same port.

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

Changes since v3:
* Add ruleset_overlap test.

Changes since v4:
* Refactoring code with self->port, self->addr4 variables.

---
 tools/testing/selftests/landlock/net_test.c | 51 +++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index bf8e49466d1d..1d8c9dfdbd48 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -677,4 +677,55 @@ TEST_F_FORK(socket_test, connect_afunspec_with_restictions) {
 	ASSERT_EQ(1, WIFEXITED(status));
 	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
 }
+
+TEST_F_FORK(socket_test, ruleset_overlap) {
+
+	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,
+
+		.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],
+	};
+
+	const 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(_metadata, false, false);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds the socket to address with port[0] */
+	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/* Makes connection to socket with port[0] */
+	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[0])));
+
+	/* Closes socket */
+	ASSERT_EQ(0, close(sockfd));
+}
+
 TEST_HARNESS_MAIN
--
2.25.1


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

* [PATCH v5 13/15] seltests/landlock: ruleset expanding test
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (11 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 12/15] seltests/landlock: rules overlapping test Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 14/15] seltests/landlock: invalid user input data test Konstantin Meskhidze
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

This patch adds expanding rulesets in which
rules are gradually added one by one, restricting
sockets' connections.

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

Changes since v3:
* Add ruleset_expanding test.

Changes since v4:
* Refactoring code with self->port, self->addr4 variables.

---
 tools/testing/selftests/landlock/net_test.c | 152 ++++++++++++++++++++
 1 file changed, 152 insertions(+)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 1d8c9dfdbd48..b1639a55a898 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -728,4 +728,156 @@ TEST_F_FORK(socket_test, ruleset_overlap) {
 	ASSERT_EQ(0, close(sockfd));
 }

+TEST_F_FORK(socket_test, ruleset_expanding) {
+
+	int sockfd_1, sockfd_2;
+
+	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(_metadata, false, true);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0] */
+	ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/* Makes connection to socket 1 with port[0] */
+	ASSERT_EQ(0, connect(sockfd_1, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[0])));
+
+	/* Closes socket 1 */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	/* Creates a socket 2 */
+	sockfd_2 = create_socket(_metadata, false, true);
+	ASSERT_LE(0, sockfd_2);
+
+	/*
+	 * Forbids to bind the socket 2 to address with port[1],
+	 * cause there is no rule with bind() access for port[1].
+	 */
+	ASSERT_EQ(-1, bind(sockfd_2, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[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(_metadata, false, true);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0] */
+	ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/* Makes connection to socket 1 with port[0] */
+	ASSERT_EQ(0, connect(sockfd_1, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[0])));
+	/* Closes socket 1 */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	/* Creates a socket 2 */
+	sockfd_2 = create_socket(_metadata, false, true);
+	ASSERT_LE(0, sockfd_2);
+
+	/*
+	 * Forbids to bind the socket 2 to address with port[1],
+	 * cause just one layer has bind() access rule.
+	 */
+	ASSERT_EQ(-1, bind(sockfd_2, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[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(_metadata, false, true);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0] */
+	ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
+
+	/*
+	 * Forbids to bind the socket 1 to address with port[0],
+	 * cause just one layer has connect() access rule.
+	 */
+	ASSERT_EQ(-1, connect(sockfd_1, (struct sockaddr *)&self->addr4[0],
+						   sizeof(self->addr4[0])));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Closes socket 1 */
+	ASSERT_EQ(0, close(sockfd_1));
+}
 TEST_HARNESS_MAIN
--
2.25.1


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

* [PATCH v5 14/15] seltests/landlock: invalid user input data test
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (12 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 13/15] seltests/landlock: ruleset expanding test Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-16 15:20 ` [PATCH v5 15/15] samples/landlock: adds network demo Konstantin Meskhidze
  2022-05-20 10:48 ` [PATCH v5 00/15] Network support for Landlock - UDP discussion Mickaël Salaün
  15 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

This patch adds rules with invalid user space
supplied data:
    - unhandled allowed access;
    - zero port value;
    - zero access value;

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

Changes since v3:
* Add inval test.

Changes since v4:
* Refactoring code with self->port variable.

---
 tools/testing/selftests/landlock/net_test.c | 52 +++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index b1639a55a898..ca4b7a7256e8 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -880,4 +880,56 @@ TEST_F_FORK(socket_test, ruleset_expanding) {
 	/* Closes socket 1 */
 	ASSERT_EQ(0, close(sockfd_1));
 }
+
+TEST_F_FORK(socket_test, inval) {
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_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 = 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],
+	};
+
+	/* 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));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+}
 TEST_HARNESS_MAIN
--
2.25.1


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

* [PATCH v5 15/15] samples/landlock: adds network demo
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (13 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 14/15] seltests/landlock: invalid user input data test Konstantin Meskhidze
@ 2022-05-16 15:20 ` Konstantin Meskhidze
  2022-05-17  9:19   ` Mickaël Salaün
  2022-05-20 10:48 ` [PATCH v5 00/15] Network support for Landlock - UDP discussion Mickaël Salaün
  15 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 15:20 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

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

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

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.
* Refactoring main() to support network sandboxing.

---
 samples/landlock/sandboxer.c | 105 +++++++++++++++++++++++++++++++----
 security/landlock/ruleset.h  |   4 +-
 2 files changed, 95 insertions(+), 14 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 3e404e51ec64..4006c42eec1c 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 ( \
@@ -80,7 +96,7 @@ 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,
+static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
 			    const __u64 allowed_access)
 {
 	int num_paths, i, ret = 1;
@@ -142,6 +158,49 @@ 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) {
+		/* Prevents users to forget a setting. */
+		fprintf(stderr, "Missing environment variable %s\n", env_var);
+		return 1;
+	}
+	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 ( \
@@ -173,19 +232,24 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	char *const *cmd_argv;
 	int ruleset_fd, abi;
 	__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 = LANDLOCK_ACCESS_NET_BIND_TCP |
+					LANDLOCK_ACCESS_NET_CONNECT_TCP;
 	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);
@@ -193,11 +257,19 @@ int main(const int argc, char *const argv[], char *const *const envp)
 			"* %s: list of paths allowed to be used in a read-write way.\n",
 			ENV_FS_RW_NAME);
 		fprintf(stderr,
-			"\nexample:\n"
+			"* %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=\"15000:16000\" "
+			"%s=\"10000:12000\" "
 			"%s bash -i\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]);
 		return 1;
 	}

@@ -234,16 +306,25 @@ int main(const int argc, char *const argv[], char *const *const envp)

 	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(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+
+	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_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)) {
 		perror("Failed to restrict privileges");
 		goto err_close_ruleset;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 916b30b31c06..e1ff40f238a6 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -19,7 +19,7 @@
 #include "limits.h"
 #include "object.h"

-typedef u16 access_mask_t;
+typedef u32 access_mask_t;

 /* Makes sure all filesystem access rights can be stored. */
 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
@@ -157,7 +157,7 @@ struct landlock_ruleset {
 			 * layers are set once and never changed for the
 			 * lifetime of the ruleset.
 			 */
-			u32 access_masks[];
+			access_mask_t access_masks[];
 		};
 	};
 };
--
2.25.1


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

* Re: [PATCH v5 04/15] landlock: helper functions refactoring
  2022-05-16 15:20 ` [PATCH v5 04/15] landlock: helper functions refactoring Konstantin Meskhidze
@ 2022-05-16 17:14   ` Mickaël Salaün
  2022-05-16 17:43     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-16 17:14 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Unmask_layers(), init_layer_masks() and
> get_handled_accesses() helper functions move to
> ruleset.c and rule_type argument is added.
> This modification supports implementing new rule
> types into next landlock versions.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Splits commit.
> * Refactoring landlock_unmask_layers functions.
> 

Please sort changes in antichronological order. It is easier to look at 
the first lines to get the last changes.

> Changes since v4:
> * Refactoring init_layer_masks(), get_handled_accesses()
> and unmask_layers() functions to support multiple rule types.
> * Refactoring landlock_get_fs_access_mask() function with
> LANDLOCK_MASK_ACCESS_FS mask. >
> ---
>   security/landlock/fs.c      | 158 ++++++++----------------------------
>   security/landlock/ruleset.c | 152 +++++++++++++++++++++++++++++++---
>   security/landlock/ruleset.h |  17 +++-
>   3 files changed, 192 insertions(+), 135 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 5de24d4dd74c..3506e182b23e 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -211,60 +211,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])

Moving these entire blocks of code make the review/diff impossible. Why 
moving these helpers?

> -{
> -	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
> @@ -277,59 +223,6 @@ 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)
> -{
> -	access_mask_t access_dom = 0;
> -	unsigned long access_bit;
> -
> -	for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
> -	     access_bit++) {
> -		size_t layer_level;
> -
> -		for (layer_level = 0; layer_level < domain->num_layers;
> -				layer_level++) {
> -			if (landlock_get_fs_access_mask(domain, layer_level) &
> -					BIT_ULL(access_bit)) {
> -				access_dom |= BIT_ULL(access_bit);
> -				break;
> -			}
> -		}
> -	}
> -	return access_dom;
> -}
> -
> -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)) {
> -			if (landlock_get_fs_access_mask(domain, layer_level) &
> -					BIT_ULL(access_bit)) {
> -				(*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.
> @@ -506,7 +399,8 @@ static int check_access_path_dual(
>   		 * a superset of the meaningful requested accesses).
>   		 */
>   		access_masked_parent1 = access_masked_parent2 =
> -			get_handled_accesses(domain);
> +			get_handled_accesses(domain, LANDLOCK_RULE_PATH_BENEATH,
> +					     LANDLOCK_NUM_ACCESS_FS);
>   		is_dom_check = true;
>   	} else {
>   		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
> @@ -519,17 +413,25 @@ static int check_access_path_dual(
> 
>   	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);
> +				init_layer_masks(domain,
> +					LANDLOCK_MASK_ACCESS_FS,
> +					&_layer_masks_child1,
> +					sizeof(_layer_masks_child1),
> +					LANDLOCK_RULE_PATH_BENEATH),
> +				&_layer_masks_child1,
> +				ARRAY_SIZE(_layer_masks_child1));

There is a lot of formatting diff and that makes the review difficult. 
Please format everything with clang-format-14.

>   		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);
> +				init_layer_masks(domain,
> +					LANDLOCK_MASK_ACCESS_FS,
> +					&_layer_masks_child2,
> +					sizeof(_layer_masks_child2),
> +					LANDLOCK_RULE_PATH_BENEATH),
> +				&_layer_masks_child2,
> +				ARRAY_SIZE(_layer_masks_child2));
>   		layer_masks_child2 = &_layer_masks_child2;
>   		child2_is_directory = d_is_dir(dentry_child2);
>   	}
> @@ -582,14 +484,15 @@ static int check_access_path_dual(
> 
>   		rule = find_rule(domain, walker_path.dentry);
>   		allowed_parent1 = unmask_layers(rule, access_masked_parent1,
> -						layer_masks_parent1);
> +				layer_masks_parent1,
> +				ARRAY_SIZE(*layer_masks_parent1));
>   		allowed_parent2 = unmask_layers(rule, access_masked_parent2,
> -						layer_masks_parent2);
> +				layer_masks_parent2,
> +				ARRAY_SIZE(*layer_masks_parent2));
> 
>   		/* Stops when a rule from each layer grants access. */
>   		if (allowed_parent1 && allowed_parent2)
>   			break;
> -

There is no place for such formatting/whitespace patches.


>   jump_up:
>   		if (walker_path.dentry == walker_path.mnt->mnt_root) {
>   			if (follow_up(&walker_path)) {
> @@ -645,7 +548,9 @@ 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, sizeof(layer_masks),
> +			LANDLOCK_RULE_PATH_BENEATH);
>   	return check_access_path_dual(domain, path, access_request,
>   				      &layer_masks, NULL, 0, NULL, NULL);
>   }
> @@ -729,7 +634,8 @@ static bool collect_domain_accesses(
>   		return true;
> 
>   	access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> -				      layer_masks_dom);
> +			layer_masks_dom, sizeof(*layer_masks_dom),
> +			LANDLOCK_RULE_PATH_BENEATH);
> 
>   	dget(dir);
>   	while (true) {
> @@ -737,7 +643,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.
> @@ -851,9 +758,10 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>   		 * The LANDLOCK_ACCESS_FS_REFER access right is not required
>   		 * for same-directory referer (i.e. no reparenting).
>   		 */
> -		access_request_parent1 = init_layer_masks(
> -			dom, access_request_parent1 | access_request_parent2,
> -			&layer_masks_parent1);
> +		access_request_parent1 = init_layer_masks(dom,
> +				access_request_parent1 | access_request_parent2,
> +				&layer_masks_parent1, sizeof(layer_masks_parent1),
> +				LANDLOCK_RULE_PATH_BENEATH);
>   		return check_access_path_dual(dom, new_dir,
>   					      access_request_parent1,
>   					      &layer_masks_parent1, NULL, 0,
> @@ -861,7 +769,9 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>   	}
> 
>   	/* Backward compatibility: no reparenting support. */
> -	if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER))
> +	if (!(get_handled_accesses(dom, LANDLOCK_RULE_PATH_BENEATH,
> +				   LANDLOCK_NUM_ACCESS_FS) &
> +						LANDLOCK_ACCESS_FS_REFER))
>   		return -EXDEV;
> 
>   	access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 4b4c9953bb32..c4ed783d655b 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -233,7 +233,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   					       &(*layers)[0]);
>   			if (IS_ERR(new_rule))
>   				return PTR_ERR(new_rule);
> -			rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode);
> +			rb_replace_node(&this->node, &new_rule->node,
> +					&ruleset->root_inode);

This is a pure formatting hunk. :/


>   			free_rule(this, rule_type);
>   			break;
>   		}
> @@ -246,7 +247,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   		return -E2BIG;
>   	switch (rule_type) {
>   	case LANDLOCK_RULE_PATH_BENEATH:
> -		new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL);
> +		new_rule = create_rule(object_ptr, 0, layers,
> +				       num_layers, NULL);
>   		if (IS_ERR(new_rule))
>   			return PTR_ERR(new_rule);
>   		rb_link_node(&new_rule->node, parent_node, walker_node);
> @@ -281,8 +283,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>   	} };
> 
>   	build_check_layer();
> -	return insert_rule(ruleset, object_ptr, object_data, rule_type, &layers,
> -			   ARRAY_SIZE(layers));
> +	return insert_rule(ruleset, object_ptr, object_data, rule_type,
> +			   &layers, ARRAY_SIZE(layers));
>   }
> 
>   static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
> @@ -335,8 +337,9 @@ static int tree_merge(struct landlock_ruleset *const src,
> 
>   		switch (rule_type) {
>   		case LANDLOCK_RULE_PATH_BENEATH:
> -			err = insert_rule(dst, walker_rule->object.ptr, 0, rule_type,
> -					  &layers, ARRAY_SIZE(layers));
> +			err = insert_rule(dst, walker_rule->object.ptr, 0,
> +					  rule_type, &layers,
> +					  ARRAY_SIZE(layers));
>   			break;
>   		}
>   		if (err)
> @@ -433,9 +436,13 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
>   		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));
> +			flex_array_size(parent, access_masks,
> +					parent->num_layers));
> 
>   	if (WARN_ON_ONCE(!parent->hierarchy)) {
>   		err = -EINVAL;
> @@ -455,8 +462,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_inode,
> -			node)
> +	rbtree_postorder_for_each_entry_safe(freeme, next,
> +					     &ruleset->root_inode,
> +					     node)
>   		free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
>   	put_hierarchy(ruleset->hierarchy);
>   	kfree(ruleset);
> @@ -577,3 +585,127 @@ const struct landlock_rule *landlock_find_rule(
>   	}
>   	return NULL;
>   }
> +
> +access_mask_t get_handled_accesses(
> +		const struct landlock_ruleset *const domain,
> +		u16 rule_type, u16 num_access)
> +{
> +	access_mask_t access_dom = 0;
> +	unsigned long access_bit;
> +
> +	switch (rule_type) {
> +	case LANDLOCK_RULE_PATH_BENEATH:
> +		for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
> +			access_bit++) {
> +			size_t layer_level;
> +
> +			for (layer_level = 0; layer_level < domain->num_layers;
> +					layer_level++) {
> +				if (landlock_get_fs_access_mask(domain,
> +								layer_level) &
> +						BIT_ULL(access_bit)) {
> +					access_dom |= BIT_ULL(access_bit);
> +					break;
> +				}
> +			}
> +		}
> +		break;
> +	default:
> +		break;
> +	}
> +	return access_dom;
> +}
> +
> +/*
> + * @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)[], size_t masks_array_size)
> +{
> +	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, masks_array_size) {
> +			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;
> +}
> +
> +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
> +			       const access_mask_t access_request,
> +			       layer_mask_t (*const layer_masks)[],
> +			       size_t masks_size,
> +			       u16 rule_type)
> +{
> +	access_mask_t handled_accesses = 0;
> +	size_t layer_level;
> +
> +	memset(layer_masks, 0, masks_size);
> +
> +	/* 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;
> +
> +		switch (rule_type) {
> +		case LANDLOCK_RULE_PATH_BENEATH:
> +			for_each_set_bit(access_bit, &access_req,
> +					LANDLOCK_NUM_ACCESS_FS) {
> +				if (landlock_get_fs_access_mask(domain,
> +								layer_level) &
> +						BIT_ULL(access_bit)) {
> +					(*layer_masks)[access_bit] |=
> +						BIT_ULL(layer_level);
> +					handled_accesses |=
> +							   BIT_ULL(access_bit);
> +				}
> +			}
> +			break;
> +		default:
> +			return 0;
> +		}
> +	}
> +	return handled_accesses;
> +}
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 3066e5d7180c..f3cd890d0348 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -195,7 +195,22 @@ static inline u32 landlock_get_fs_access_mask(
>   					const struct landlock_ruleset *ruleset,
>   					u16 mask_level)
>   {
> -	return ruleset->access_masks[mask_level];
> +	return (ruleset->access_masks[mask_level] & LANDLOCK_MASK_ACCESS_FS);
>   }
> 
> +access_mask_t get_handled_accesses(
> +		const struct landlock_ruleset *const domain,
> +		u16 rule_type, u16 num_access);
> +
> +bool unmask_layers(const struct landlock_rule *const rule,
> +		   const access_mask_t access_request,
> +		   layer_mask_t (*const layer_masks)[],
> +		   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)[],
> +			       size_t masks_size,
> +			       u16 rule_type);

These declarations are useless.

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

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

* Re: [PATCH v5 12/15] seltests/landlock: rules overlapping test
  2022-05-16 15:20 ` [PATCH v5 12/15] seltests/landlock: rules overlapping test Konstantin Meskhidze
@ 2022-05-16 17:41   ` Mickaël Salaün
  2022-05-19 12:24     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-16 17:41 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

Please fix these kind of subjects (selftests). I'd also like the subject 
description to (quickly) describe what is done (with a verb), to start 
with a capital (like a title), and to contain "network", something like 
this:
selftests/landlock: Add test for overlapping network rules

This is a good test though.


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> This patch adds overlapping rules for one port.
> First rule adds just bind() access right for a port.
> The second one adds both bind() and connect()
> access rights for the same port.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Add ruleset_overlap test.
> 
> Changes since v4:
> * Refactoring code with self->port, self->addr4 variables.
> 
> ---
>   tools/testing/selftests/landlock/net_test.c | 51 +++++++++++++++++++++
>   1 file changed, 51 insertions(+)
> 
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index bf8e49466d1d..1d8c9dfdbd48 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -677,4 +677,55 @@ TEST_F_FORK(socket_test, connect_afunspec_with_restictions) {
>   	ASSERT_EQ(1, WIFEXITED(status));
>   	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
>   }
> +
> +TEST_F_FORK(socket_test, ruleset_overlap) {

Please run clang-format-14 on all files (and all commits).

> +
> +	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,
> +
> +		.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],
> +	};
> +
> +	const 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 */

Please ends this kind of comments with a final dot (all files/commits).

> +	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(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +
> +	/* Binds the socket to address with port[0] */
> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
> +
> +	/* Makes connection to socket with port[0] */
> +	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],

Can you please get rid of this (struct sockaddr *) type casting please 
(without compiler warning)?

> +						   sizeof(self->addr4[0])));

Here, you can enforce a new ruleset with net_service_1 and check that 
bind() is still allowed but not connect().

> +
> +	/* Closes socket */
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
>   TEST_HARNESS_MAIN
> --
> 2.25.1
> 

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

* Re: [PATCH v5 04/15] landlock: helper functions refactoring
  2022-05-16 17:14   ` Mickaël Salaün
@ 2022-05-16 17:43     ` Konstantin Meskhidze
  2022-05-16 18:28       ` Mickaël Salaün
  0 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-16 17:43 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/16/2022 8:14 PM, Mickaël Salaün пишет:
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Unmask_layers(), init_layer_masks() and
>> get_handled_accesses() helper functions move to
>> ruleset.c and rule_type argument is added.
>> This modification supports implementing new rule
>> types into next landlock versions.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Splits commit.
>> * Refactoring landlock_unmask_layers functions.
>>
> 
> Please sort changes in antichronological order. It is easier to look at 
> the first lines to get the last changes.

  Ok. I will. Thanks
> 
>> Changes since v4:
>> * Refactoring init_layer_masks(), get_handled_accesses()
>> and unmask_layers() functions to support multiple rule types.
>> * Refactoring landlock_get_fs_access_mask() function with
>> LANDLOCK_MASK_ACCESS_FS mask. >
>> ---
>>   security/landlock/fs.c      | 158 ++++++++----------------------------
>>   security/landlock/ruleset.c | 152 +++++++++++++++++++++++++++++++---
>>   security/landlock/ruleset.h |  17 +++-
>>   3 files changed, 192 insertions(+), 135 deletions(-)
>>
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 5de24d4dd74c..3506e182b23e 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -211,60 +211,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])
> 
> Moving these entire blocks of code make the review/diff impossible. Why 
> moving these helpers?

   Cause these helpers are going to be used both for filesystem and 
network. I moved them into ruleset.c/h
> 
>> -{
>> -    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
>> @@ -277,59 +223,6 @@ 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)
>> -{
>> -    access_mask_t access_dom = 0;
>> -    unsigned long access_bit;
>> -
>> -    for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
>> -         access_bit++) {
>> -        size_t layer_level;
>> -
>> -        for (layer_level = 0; layer_level < domain->num_layers;
>> -                layer_level++) {
>> -            if (landlock_get_fs_access_mask(domain, layer_level) &
>> -                    BIT_ULL(access_bit)) {
>> -                access_dom |= BIT_ULL(access_bit);
>> -                break;
>> -            }
>> -        }
>> -    }
>> -    return access_dom;
>> -}
>> -
>> -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)) {
>> -            if (landlock_get_fs_access_mask(domain, layer_level) &
>> -                    BIT_ULL(access_bit)) {
>> -                (*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.
>> @@ -506,7 +399,8 @@ static int check_access_path_dual(
>>            * a superset of the meaningful requested accesses).
>>            */
>>           access_masked_parent1 = access_masked_parent2 =
>> -            get_handled_accesses(domain);
>> +            get_handled_accesses(domain, LANDLOCK_RULE_PATH_BENEATH,
>> +                         LANDLOCK_NUM_ACCESS_FS);
>>           is_dom_check = true;
>>       } else {
>>           if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
>> @@ -519,17 +413,25 @@ static int check_access_path_dual(
>>
>>       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);
>> +                init_layer_masks(domain,
>> +                    LANDLOCK_MASK_ACCESS_FS,
>> +                    &_layer_masks_child1,
>> +                    sizeof(_layer_masks_child1),
>> +                    LANDLOCK_RULE_PATH_BENEATH),
>> +                &_layer_masks_child1,
>> +                ARRAY_SIZE(_layer_masks_child1));
> 
> There is a lot of formatting diff and that makes the review difficult. 
> Please format everything with clang-format-14.

   Ok. Do you have some tool that helps you with editing code with clang 
format?
> 
>>           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);
>> +                init_layer_masks(domain,
>> +                    LANDLOCK_MASK_ACCESS_FS,
>> +                    &_layer_masks_child2,
>> +                    sizeof(_layer_masks_child2),
>> +                    LANDLOCK_RULE_PATH_BENEATH),
>> +                &_layer_masks_child2,
>> +                ARRAY_SIZE(_layer_masks_child2));
>>           layer_masks_child2 = &_layer_masks_child2;
>>           child2_is_directory = d_is_dir(dentry_child2);
>>       }
>> @@ -582,14 +484,15 @@ static int check_access_path_dual(
>>
>>           rule = find_rule(domain, walker_path.dentry);
>>           allowed_parent1 = unmask_layers(rule, access_masked_parent1,
>> -                        layer_masks_parent1);
>> +                layer_masks_parent1,
>> +                ARRAY_SIZE(*layer_masks_parent1));
>>           allowed_parent2 = unmask_layers(rule, access_masked_parent2,
>> -                        layer_masks_parent2);
>> +                layer_masks_parent2,
>> +                ARRAY_SIZE(*layer_masks_parent2));
>>
>>           /* Stops when a rule from each layer grants access. */
>>           if (allowed_parent1 && allowed_parent2)
>>               break;
>> -
> 
> There is no place for such formatting/whitespace patches.
> 
   I missed that. scripts/checkpatch.pl did not show any problem here.
   I will fix it. Thanks.
> 
>>   jump_up:
>>           if (walker_path.dentry == walker_path.mnt->mnt_root) {
>>               if (follow_up(&walker_path)) {
>> @@ -645,7 +548,9 @@ 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, sizeof(layer_masks),
>> +            LANDLOCK_RULE_PATH_BENEATH);
>>       return check_access_path_dual(domain, path, access_request,
>>                         &layer_masks, NULL, 0, NULL, NULL);
>>   }
>> @@ -729,7 +634,8 @@ static bool collect_domain_accesses(
>>           return true;
>>
>>       access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>> -                      layer_masks_dom);
>> +            layer_masks_dom, sizeof(*layer_masks_dom),
>> +            LANDLOCK_RULE_PATH_BENEATH);
>>
>>       dget(dir);
>>       while (true) {
>> @@ -737,7 +643,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.
>> @@ -851,9 +758,10 @@ static int current_check_refer_path(struct dentry 
>> *const old_dentry,
>>            * The LANDLOCK_ACCESS_FS_REFER access right is not required
>>            * for same-directory referer (i.e. no reparenting).
>>            */
>> -        access_request_parent1 = init_layer_masks(
>> -            dom, access_request_parent1 | access_request_parent2,
>> -            &layer_masks_parent1);
>> +        access_request_parent1 = init_layer_masks(dom,
>> +                access_request_parent1 | access_request_parent2,
>> +                &layer_masks_parent1, sizeof(layer_masks_parent1),
>> +                LANDLOCK_RULE_PATH_BENEATH);
>>           return check_access_path_dual(dom, new_dir,
>>                             access_request_parent1,
>>                             &layer_masks_parent1, NULL, 0,
>> @@ -861,7 +769,9 @@ static int current_check_refer_path(struct dentry 
>> *const old_dentry,
>>       }
>>
>>       /* Backward compatibility: no reparenting support. */
>> -    if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER))
>> +    if (!(get_handled_accesses(dom, LANDLOCK_RULE_PATH_BENEATH,
>> +                   LANDLOCK_NUM_ACCESS_FS) &
>> +                        LANDLOCK_ACCESS_FS_REFER))
>>           return -EXDEV;
>>
>>       access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 4b4c9953bb32..c4ed783d655b 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -233,7 +233,8 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>                              &(*layers)[0]);
>>               if (IS_ERR(new_rule))
>>                   return PTR_ERR(new_rule);
>> -            rb_replace_node(&this->node, &new_rule->node, 
>> &ruleset->root_inode);
>> +            rb_replace_node(&this->node, &new_rule->node,
>> +                    &ruleset->root_inode);
> 
> This is a pure formatting hunk. :/
> 
   Thats strange, cause in my editor I have normal aligment of arguments.
   Could please share clang-format-14 tab size and other format 	parameters?
> 
>>               free_rule(this, rule_type);
>>               break;
>>           }
>> @@ -246,7 +247,8 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>           return -E2BIG;
>>       switch (rule_type) {
>>       case LANDLOCK_RULE_PATH_BENEATH:
>> -        new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL);
>> +        new_rule = create_rule(object_ptr, 0, layers,
>> +                       num_layers, NULL);
>>           if (IS_ERR(new_rule))
>>               return PTR_ERR(new_rule);
>>           rb_link_node(&new_rule->node, parent_node, walker_node);
>> @@ -281,8 +283,8 @@ int landlock_insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>       } };
>>
>>       build_check_layer();
>> -    return insert_rule(ruleset, object_ptr, object_data, rule_type, 
>> &layers,
>> -               ARRAY_SIZE(layers));
>> +    return insert_rule(ruleset, object_ptr, object_data, rule_type,
>> +               &layers, ARRAY_SIZE(layers));
>>   }
>>
>>   static inline void get_hierarchy(struct landlock_hierarchy *const 
>> hierarchy)
>> @@ -335,8 +337,9 @@ static int tree_merge(struct landlock_ruleset 
>> *const src,
>>
>>           switch (rule_type) {
>>           case LANDLOCK_RULE_PATH_BENEATH:
>> -            err = insert_rule(dst, walker_rule->object.ptr, 0, 
>> rule_type,
>> -                      &layers, ARRAY_SIZE(layers));
>> +            err = insert_rule(dst, walker_rule->object.ptr, 0,
>> +                      rule_type, &layers,
>> +                      ARRAY_SIZE(layers));
>>               break;
>>           }
>>           if (err)
>> @@ -433,9 +436,13 @@ static int inherit_ruleset(struct 
>> landlock_ruleset *const parent,
>>           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));
>> +            flex_array_size(parent, access_masks,
>> +                    parent->num_layers));
>>
>>       if (WARN_ON_ONCE(!parent->hierarchy)) {
>>           err = -EINVAL;
>> @@ -455,8 +462,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_inode,
>> -            node)
>> +    rbtree_postorder_for_each_entry_safe(freeme, next,
>> +                         &ruleset->root_inode,
>> +                         node)
>>           free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
>>       put_hierarchy(ruleset->hierarchy);
>>       kfree(ruleset);
>> @@ -577,3 +585,127 @@ const struct landlock_rule *landlock_find_rule(
>>       }
>>       return NULL;
>>   }
>> +
>> +access_mask_t get_handled_accesses(
>> +        const struct landlock_ruleset *const domain,
>> +        u16 rule_type, u16 num_access)
>> +{
>> +    access_mask_t access_dom = 0;
>> +    unsigned long access_bit;
>> +
>> +    switch (rule_type) {
>> +    case LANDLOCK_RULE_PATH_BENEATH:
>> +        for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
>> +            access_bit++) {
>> +            size_t layer_level;
>> +
>> +            for (layer_level = 0; layer_level < domain->num_layers;
>> +                    layer_level++) {
>> +                if (landlock_get_fs_access_mask(domain,
>> +                                layer_level) &
>> +                        BIT_ULL(access_bit)) {
>> +                    access_dom |= BIT_ULL(access_bit);
>> +                    break;
>> +                }
>> +            }
>> +        }
>> +        break;
>> +    default:
>> +        break;
>> +    }
>> +    return access_dom;
>> +}
>> +
>> +/*
>> + * @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)[], size_t masks_array_size)
>> +{
>> +    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, masks_array_size) {
>> +            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;
>> +}
>> +
>> +access_mask_t init_layer_masks(const struct landlock_ruleset *const 
>> domain,
>> +                   const access_mask_t access_request,
>> +                   layer_mask_t (*const layer_masks)[],
>> +                   size_t masks_size,
>> +                   u16 rule_type)
>> +{
>> +    access_mask_t handled_accesses = 0;
>> +    size_t layer_level;
>> +
>> +    memset(layer_masks, 0, masks_size);
>> +
>> +    /* 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;
>> +
>> +        switch (rule_type) {
>> +        case LANDLOCK_RULE_PATH_BENEATH:
>> +            for_each_set_bit(access_bit, &access_req,
>> +                    LANDLOCK_NUM_ACCESS_FS) {
>> +                if (landlock_get_fs_access_mask(domain,
>> +                                layer_level) &
>> +                        BIT_ULL(access_bit)) {
>> +                    (*layer_masks)[access_bit] |=
>> +                        BIT_ULL(layer_level);
>> +                    handled_accesses |=
>> +                               BIT_ULL(access_bit);
>> +                }
>> +            }
>> +            break;
>> +        default:
>> +            return 0;
>> +        }
>> +    }
>> +    return handled_accesses;
>> +}
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index 3066e5d7180c..f3cd890d0348 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -195,7 +195,22 @@ static inline u32 landlock_get_fs_access_mask(
>>                       const struct landlock_ruleset *ruleset,
>>                       u16 mask_level)
>>   {
>> -    return ruleset->access_masks[mask_level];
>> +    return (ruleset->access_masks[mask_level] & 
>> LANDLOCK_MASK_ACCESS_FS);
>>   }
>>
>> +access_mask_t get_handled_accesses(
>> +        const struct landlock_ruleset *const domain,
>> +        u16 rule_type, u16 num_access);
>> +
>> +bool unmask_layers(const struct landlock_rule *const rule,
>> +           const access_mask_t access_request,
>> +           layer_mask_t (*const layer_masks)[],
>> +           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)[],
>> +                   size_t masks_size,
>> +                   u16 rule_type);
> 
> These declarations are useless.

   I moved helpers in rulset.c/h to use them both for filesystem
and network.
> 
>> +
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 04/15] landlock: helper functions refactoring
  2022-05-16 17:43     ` Konstantin Meskhidze
@ 2022-05-16 18:28       ` Mickaël Salaün
  2022-05-18  9:14         ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-16 18:28 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 16/05/2022 19:43, Konstantin Meskhidze wrote:
> 
> 
> 5/16/2022 8:14 PM, Mickaël Salaün пишет:
>>
>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>> Unmask_layers(), init_layer_masks() and
>>> get_handled_accesses() helper functions move to
>>> ruleset.c and rule_type argument is added.
>>> This modification supports implementing new rule
>>> types into next landlock versions.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>

[...]

>>> -/*
>>> - * @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])
>>
>> Moving these entire blocks of code make the review/diff impossible. 
>> Why moving these helpers?
> 
>    Cause these helpers are going to be used both for filesystem and 
> network. I moved them into ruleset.c/h

Right. Please create a commit which only moves these helpers without 
modifying them, and explain in the commit message that this removes 
inlined code. We'll see later if this adds a visible performance impact.

[...]

>>> @@ -519,17 +413,25 @@ static int check_access_path_dual(
>>>
>>>       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);
>>> +                init_layer_masks(domain,
>>> +                    LANDLOCK_MASK_ACCESS_FS,
>>> +                    &_layer_masks_child1,
>>> +                    sizeof(_layer_masks_child1),
>>> +                    LANDLOCK_RULE_PATH_BENEATH),
>>> +                &_layer_masks_child1,
>>> +                ARRAY_SIZE(_layer_masks_child1));
>>
>> There is a lot of formatting diff and that makes the review difficult. 
>> Please format everything with clang-format-14.
> 
>    Ok. Do you have some tool that helps you with editing code with clang 
> format?

I just run `clang-format-14 -i` on files before each commit. Some 
editors such as VSCode can handle the clang-format configuration (which 
is in the Linux source tree).


>>
>>>           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);
>>> +                init_layer_masks(domain,
>>> +                    LANDLOCK_MASK_ACCESS_FS,
>>> +                    &_layer_masks_child2,
>>> +                    sizeof(_layer_masks_child2),
>>> +                    LANDLOCK_RULE_PATH_BENEATH),
>>> +                &_layer_masks_child2,
>>> +                ARRAY_SIZE(_layer_masks_child2));
>>>           layer_masks_child2 = &_layer_masks_child2;
>>>           child2_is_directory = d_is_dir(dentry_child2);
>>>       }
>>> @@ -582,14 +484,15 @@ static int check_access_path_dual(
>>>
>>>           rule = find_rule(domain, walker_path.dentry);
>>>           allowed_parent1 = unmask_layers(rule, access_masked_parent1,
>>> -                        layer_masks_parent1);
>>> +                layer_masks_parent1,
>>> +                ARRAY_SIZE(*layer_masks_parent1));
>>>           allowed_parent2 = unmask_layers(rule, access_masked_parent2,
>>> -                        layer_masks_parent2);
>>> +                layer_masks_parent2,
>>> +                ARRAY_SIZE(*layer_masks_parent2));
>>>
>>>           /* Stops when a rule from each layer grants access. */
>>>           if (allowed_parent1 && allowed_parent2)
>>>               break;
>>> -
>>
>> There is no place for such formatting/whitespace patches.
>>
>    I missed that. scripts/checkpatch.pl did not show any problem here.

checkpatch.pl doesn't warn about whitespace changes.


>    I will fix it. Thanks.
>>
>>>   jump_up:
>>>           if (walker_path.dentry == walker_path.mnt->mnt_root) {
>>>               if (follow_up(&walker_path)) {
>>> @@ -645,7 +548,9 @@ 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, sizeof(layer_masks),
>>> +            LANDLOCK_RULE_PATH_BENEATH);
>>>       return check_access_path_dual(domain, path, access_request,
>>>                         &layer_masks, NULL, 0, NULL, NULL);
>>>   }
>>> @@ -729,7 +634,8 @@ static bool collect_domain_accesses(
>>>           return true;
>>>
>>>       access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>>> -                      layer_masks_dom);
>>> +            layer_masks_dom, sizeof(*layer_masks_dom),
>>> +            LANDLOCK_RULE_PATH_BENEATH);
>>>
>>>       dget(dir);
>>>       while (true) {
>>> @@ -737,7 +643,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.
>>> @@ -851,9 +758,10 @@ static int current_check_refer_path(struct 
>>> dentry *const old_dentry,
>>>            * The LANDLOCK_ACCESS_FS_REFER access right is not required
>>>            * for same-directory referer (i.e. no reparenting).
>>>            */
>>> -        access_request_parent1 = init_layer_masks(
>>> -            dom, access_request_parent1 | access_request_parent2,
>>> -            &layer_masks_parent1);
>>> +        access_request_parent1 = init_layer_masks(dom,
>>> +                access_request_parent1 | access_request_parent2,
>>> +                &layer_masks_parent1, sizeof(layer_masks_parent1),
>>> +                LANDLOCK_RULE_PATH_BENEATH);
>>>           return check_access_path_dual(dom, new_dir,
>>>                             access_request_parent1,
>>>                             &layer_masks_parent1, NULL, 0,
>>> @@ -861,7 +769,9 @@ static int current_check_refer_path(struct dentry 
>>> *const old_dentry,
>>>       }
>>>
>>>       /* Backward compatibility: no reparenting support. */
>>> -    if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER))
>>> +    if (!(get_handled_accesses(dom, LANDLOCK_RULE_PATH_BENEATH,
>>> +                   LANDLOCK_NUM_ACCESS_FS) &
>>> +                        LANDLOCK_ACCESS_FS_REFER))
>>>           return -EXDEV;
>>>
>>>       access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>> index 4b4c9953bb32..c4ed783d655b 100644
>>> --- a/security/landlock/ruleset.c
>>> +++ b/security/landlock/ruleset.c
>>> @@ -233,7 +233,8 @@ static int insert_rule(struct landlock_ruleset 
>>> *const ruleset,
>>>                              &(*layers)[0]);
>>>               if (IS_ERR(new_rule))
>>>                   return PTR_ERR(new_rule);
>>> -            rb_replace_node(&this->node, &new_rule->node, 
>>> &ruleset->root_inode);
>>> +            rb_replace_node(&this->node, &new_rule->node,
>>> +                    &ruleset->root_inode);
>>
>> This is a pure formatting hunk. :/
>>
>    Thats strange, cause in my editor I have normal aligment of arguments.
>    Could please share clang-format-14 tab size and other format     
> parameters?

They are in the .clang-format file. It would be much easier to just run 
clang-format-14 -i on your changed files. I guess you had different 
changes between consecutive commits.

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

* Re: [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks
  2022-05-16 15:20 ` [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks Konstantin Meskhidze
@ 2022-05-16 21:11   ` Mickaël Salaün
  2022-05-19 12:10     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-16 21:11 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Adds selftests for bind socket action.
> The first is with no landlock restrictions:
>      - bind_no_restrictions_ip4;
>      - bind_no_restrictions_ip6;
> The second ones is with mixed landlock rules:
>      - bind_with_restrictions_ip4;
>      - bind_with_restrictions_ip6;
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Split commit.
> * Add helper create_socket.
> * Add FIXTURE_SETUP.
> 
> Changes since v4:
> * Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4
> and struct sockaddr_in addr6 in FIXTURE.
> * Refactoring FIXTURE_SETUP:
>      - initializing self->port, self->addr4 and self->addr6.
>      - adding network namespace.
> * Refactoring code with self->port, self->addr4 and
> self->addr6 variables.
> * Adds selftests for IP6 family:
>      - bind_no_restrictions_ip6.
>      - bind_with_restrictions_ip6.
> * Refactoring selftests/landlock/config
> * Moves enforce_ruleset() into common.h
> 
> ---
>   tools/testing/selftests/landlock/common.h   |   9 +
>   tools/testing/selftests/landlock/config     |   5 +-
>   tools/testing/selftests/landlock/fs_test.c  |  10 -
>   tools/testing/selftests/landlock/net_test.c | 237 ++++++++++++++++++++
>   4 files changed, 250 insertions(+), 11 deletions(-)
>   create mode 100644 tools/testing/selftests/landlock/net_test.c
> 
> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
> index 7ba18eb23783..c5381e641dfd 100644
> --- a/tools/testing/selftests/landlock/common.h
> +++ b/tools/testing/selftests/landlock/common.h
> @@ -102,6 +102,15 @@ static inline int landlock_restrict_self(const int ruleset_fd,
>   }
>   #endif
> 
> +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));
> +	}
> +}
> +

Please create a commit which moves all the needed code for all network 
tests. I think there is only this helper though.


>   static void _init_caps(struct __test_metadata *const _metadata, bool drop_all)
>   {
>   	cap_t cap_p;
> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
> index 0f0a65287bac..b56f3274d3f5 100644
> --- a/tools/testing/selftests/landlock/config
> +++ b/tools/testing/selftests/landlock/config
> @@ -1,7 +1,10 @@
> +CONFIG_INET=y
> +CONFIG_IPV6=y
> +CONFIG_NET=y
>   CONFIG_OVERLAY_FS=y
>   CONFIG_SECURITY_LANDLOCK=y
>   CONFIG_SECURITY_PATH=y
>   CONFIG_SECURITY=y
>   CONFIG_SHMEM=y
>   CONFIG_TMPFS_XATTR=y
> -CONFIG_TMPFS=y
> +CONFIG_TMPFS=y
> \ No newline at end of file

You add whitespace changes.


> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 21a2ce8fa739..036dd6f8f9ea 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -551,16 +551,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[] = {
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> new file mode 100644
> index 000000000000..478ef2eff559
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -0,0 +1,237 @@
> +// 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

"Number of pending connection queues to be hold." maybe? This is not use 
in this patch so it shouldn't be added by this patch.


> +
> +static int create_socket(struct __test_metadata *const _metadata,
> +			bool ip6, bool reuse_addr)

This helper is good and I think you can improve it by leveraging test 
variants. You could even factor out all the ipv4/ipv6 tests thanks to 
new helpers such as bind_variant() and connect_variant(). No need to add 
_metadata to those though. This would avoid duplicating all ipv4/ipv6 
tests and even simplifying bind() and connect() calls. Something like this:

// rename "socket_test" to "socket" (no need to duplicate "test")
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 socket_variant(const 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);
}

socket_variant(variant, SOCK_STREAM);
// this could be used to create UDP sockets too


static int bind_variant(const FIXTURE_VARIANT(socket) *const variant, 
const int sockfd, const FIXTURE_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]));
}

bind_variant(variant, sockfd, self, 0);


> +{
> +		int sockfd;
> +		int one = 1;
> +
> +		if (ip6)
> +			sockfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
> +		else
> +			sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
> +
> +		ASSERT_LE(0, sockfd);
> +		/* Allows to reuse of local address */
> +		if (reuse_addr)
> +			ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET,
> +					SO_REUSEADDR, &one, sizeof(one)));

This reuse_addr part is not used in this patch and I think it would 
simplify this helper to not add reuse_addr but to explicitely call 
setsockopt() when required. This also enables to get rid of _metadata in 
this helper.


> +		return sockfd;
> +}
> +
> +FIXTURE(socket_test) {
> +	uint port[MAX_SOCKET_NUM];
> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
> +};
> +
> +FIXTURE_SETUP(socket_test)
> +{
> +	int i;
> +	/* Creates IP4 socket addresses */
> +	for (i = 0; i < MAX_SOCKET_NUM; i++) {

Nice!

> +		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 = htonl(INADDR_ANY);

Could you use the local addr (127.0.0.1) instead?

> +		memset(&(self->addr4[i].sin_zero), '\0', 8);
> +	}
> +
> +	/* Creates IP6 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]);
> +		self->addr6[i].sin6_addr = in6addr_any;

ditto

> +	}
> +
> +	set_cap(_metadata, CAP_SYS_ADMIN);
> +	ASSERT_EQ(0, unshare(CLONE_NEWNET));
> +	ASSERT_EQ(0, system("ip link set dev lo up"));

If this is really required, could you avoid calling system() but set up 
the network in C? You can strace it to see what is going on underneath.


> +	clear_cap(_metadata, CAP_SYS_ADMIN);
> +}
> +
> +FIXTURE_TEARDOWN(socket_test)
> +{ }
> +
> +TEST_F_FORK(socket_test, bind_no_restrictions_ip4) {
> +
> +	int sockfd;
> +
> +	sockfd = create_socket(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +
> +	/* Binds a socket to port[0] */

This comment is not very useful in this context considering the below 
line. It will be even more clear with the bind_variant() call.


> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
> +
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
> +TEST_F_FORK(socket_test, bind_no_restrictions_ip6) {
> +
> +	int sockfd;
> +
> +	sockfd = create_socket(_metadata, true, false);
> +	ASSERT_LE(0, sockfd);
> +
> +	/* Binds a socket to port[0] */
> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0])));
> +
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
> +TEST_F_FORK(socket_test, bind_with_restrictions_ip4) {
> +
> +	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. */

This comment is useful though because the below call is more complex.


> +	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(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[0] */
> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
> +
> +	/* Close bounded socket*/
> +	ASSERT_EQ(0, close(sockfd));
> +
> +	sockfd = create_socket(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[1] */
> +	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[1])));
> +	ASSERT_EQ(EACCES, errno);
> +
> +	sockfd = create_socket(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[2] */
> +	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[2], sizeof(self->addr4[2])));
> +	ASSERT_EQ(EACCES, errno);
> +}
> +
> +TEST_F_FORK(socket_test, bind_with_restrictions_ip6) {
> +
> +	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(_metadata, true, false);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[0] */
> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0])));
> +
> +	/* Close bounded socket*/
> +	ASSERT_EQ(0, close(sockfd));
> +
> +	sockfd = create_socket(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[1] */
> +	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[1], sizeof(self->addr6[1])));
> +	ASSERT_EQ(EACCES, errno);
> +
> +	sockfd = create_socket(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[2] */
> +	ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[2], sizeof(self->addr6[2])));
> +	ASSERT_EQ(EACCES, errno);
> +}
> +TEST_HARNESS_MAIN
> --
> 2.25.1
> 

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

* Re: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-16 15:20 ` [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring Konstantin Meskhidze
@ 2022-05-17  8:04   ` Mickaël Salaün
  2022-05-17  8:10     ` Mickaël Salaün
  2022-05-19  9:23     ` Konstantin Meskhidze
  0 siblings, 2 replies; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:04 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

You can rename the subject to "landlock: Refactor landlock_add_rule()"


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Landlock_add_rule syscall was refactored to support new
> rule types in future Landlock versions. Add_rule_path_beneath()

nit: add_rule_path_beneath(), not Add_rule_path_beneath()

> helper was added to support current filesystem rules. It is called
> by the switch case.

You can rephrase (all commit messages) in the present form:

Refactor the landlock_add_rule() syscall with add_rule_path_beneath() 
to support new…

Refactor the landlock_add_rule() syscall to easily support for a new 
rule type in a following commit. The new add_rule_path_beneath() helper 
supports current filesystem rules.


> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Split commit.
> * Refactoring landlock_add_rule syscall.
> 
> Changes since v4:
> * Refactoring add_rule_path_beneath() and landlock_add_rule() functions
> to optimize code usage.
> * Refactoring base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
> rule type in landlock_add_rule() call.
> 
> ---
>   security/landlock/syscalls.c                 | 105 ++++++++++---------
>   tools/testing/selftests/landlock/base_test.c |   4 +-
>   2 files changed, 59 insertions(+), 50 deletions(-)
> 
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 1db799d1a50b..412ced6c512f 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -274,67 +274,23 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>   	return err;
>   }
> 
> -/**
> - * 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_attr: Pointer to a rule (only of type &struct
> - *             landlock_path_beneath_attr for now).
> - * @flags: Must be 0.
> - *
> - * This system call enables to define a new rule and add it to an existing
> - * ruleset.
> - *
> - * Possible returned errors are:
> - *
> - * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
> - * - 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);
> - * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
> - * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
> - *   member of @rule_attr is not a file descriptor as expected;
> - * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
> - *   @rule_attr is not the expected file descriptor type;
> - * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
> - * - EFAULT: @rule_attr inconsistency.
> - */
> -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)
> +static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_attr)
>   {
>   	struct landlock_path_beneath_attr path_beneath_attr;
>   	struct path path;
>   	struct landlock_ruleset *ruleset;
>   	int res, err;
> 
> -	if (!landlock_initialized)
> -		return -EOPNOTSUPP;
> -
> -	/* No flag for now. */
> -	if (flags)
> -		return -EINVAL;
> -
>   	/* Gets and checks the ruleset. */

Like I already said, this needs to stay in landlock_add_rule(). I think 
there is some inconsistencies with other patches that rechange this 
part. Please review your patches and make clean patches that don't 
partially revert the previous ones.


>   	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>   	if (IS_ERR(ruleset))
>   		return PTR_ERR(ruleset);
> 
> -	if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
> -		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;
> -	}
> +				sizeof(path_beneath_attr));
> +	if (res)
> +		return -EFAULT;
> 
>   	/*
>   	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
> @@ -370,6 +326,59 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>   	return err;
>   }
> 
> +/**
> + * 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_attr: Pointer to a rule (only of type &struct
> + *             landlock_path_beneath_attr for now).
> + * @flags: Must be 0.
> + *
> + * This system call enables to define a new rule and add it to an existing
> + * ruleset.
> + *
> + * Possible returned errors are:
> + *
> + * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
> + * - 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 rule's
> + *   accesses);
> + * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
> + * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
> + *   member of @rule_attr is not a file descriptor as expected;
> + * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
> + *   @rule_attr is not the expected file descriptor type (e.g. file open
> + *   without O_PATH);
> + * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
> + * - EFAULT: @rule_attr inconsistency.
> + */
> +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)
> +{
> +	int err;
> +
> +	if (!landlock_initialized)
> +		return -EOPNOTSUPP;
> +
> +	/* No flag for now. */
> +	if (flags)
> +		return -EINVAL;
> +
> +	switch (rule_type) {
> +	case LANDLOCK_RULE_PATH_BENEATH:
> +		err = add_rule_path_beneath(ruleset_fd, rule_attr);
> +		break;
> +	default:
> +		err = -EINVAL;
> +		break;
> +	}
> +	return err;
> +}
> +
>   /* Enforcement */
> 
>   /**
> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index da9290817866..0c4c3a538d54 100644
> --- a/tools/testing/selftests/landlock/base_test.c
> +++ b/tools/testing/selftests/landlock/base_test.c
> @@ -156,11 +156,11 @@ TEST(add_rule_checks_ordering)
>   	ASSERT_LE(0, ruleset_fd);
> 
>   	/* Checks invalid flags. */
> -	ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
> +	ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, NULL, 1));

This must not be changed! I specifically added these tests to make sure 
no one change the argument ordering checks…


>   	ASSERT_EQ(EINVAL, errno);
> 
>   	/* Checks invalid ruleset FD. */
> -	ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0));
> +	ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, NULL, 0));
>   	ASSERT_EQ(EBADF, errno);
> 
>   	/* Checks invalid rule type. */
> --
> 2.25.1
> 

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

* Re: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-17  8:04   ` Mickaël Salaün
@ 2022-05-17  8:10     ` Mickaël Salaün
  2022-05-19  9:24       ` Konstantin Meskhidze
  2022-05-19  9:23     ` Konstantin Meskhidze
  1 sibling, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:10 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 17/05/2022 10:04, Mickaël Salaün wrote:
> You can rename the subject to "landlock: Refactor landlock_add_rule()"
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:

[...]

>> helper was added to support current filesystem rules. It is called
>> by the switch case.
> 
> You can rephrase (all commit messages) in the present form:
present *tense*
> 
> Refactor the landlock_add_rule() syscall to easily support for a new 
> rule type in a following commit. The new add_rule_path_beneath() helper 
> supports current filesystem rules.

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

* Re: [PATCH v5 01/15] landlock: access mask renaming
  2022-05-16 15:20 ` [PATCH v5 01/15] landlock: access mask renaming Konstantin Meskhidze
@ 2022-05-17  8:12   ` Mickaël Salaün
  2022-05-18  9:16     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:12 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Currently Landlock supports filesystem
> restrictions. To support network type rules,
> this modification extends and renames
> ruleset's access masks.
> This patch adds filesystem helper functions
> to set and get filesystem mask. Also the modification
> adds a helper structure landlock_access_mask to
> support managing multiple access mask.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---

[...]

> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index d43231b783e4..f27a79624962 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -20,6 +20,7 @@
>   #include "object.h"
> 
>   typedef u16 access_mask_t;
> +
>   /* Makes sure all filesystem access rights can be stored. */

Please don't add whitespaces.

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

* Re: [PATCH v5 03/15] landlock: merge and inherit function refactoring
  2022-05-16 15:20 ` [PATCH v5 03/15] landlock: merge and inherit function refactoring Konstantin Meskhidze
@ 2022-05-17  8:14   ` Mickaël Salaün
  2022-05-18  9:18     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:14 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Merge_ruleset() and inherit_ruleset() functions were
> refactored to support new rule types. This patch adds
> tree_merge() and tree_copy() helpers. Each has
> rule_type argument to choose a particular rb_tree
> structure in a ruleset.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Split commit.
> * Refactoring functions:
> 	-insert_rule.
> 	-merge_ruleset.
> 	-tree_merge.
> 	-inherit_ruleset.
> 	-tree_copy.
> 	-free_rule.
> 
> Changes since v4:
> * None
> 
> ---
>   security/landlock/ruleset.c | 144 ++++++++++++++++++++++++------------
>   1 file changed, 98 insertions(+), 46 deletions(-)
> 
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index f079a2a320f1..4b4c9953bb32 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -112,12 +112,16 @@ static struct landlock_rule *create_rule(
>   	return new_rule;
>   }
> 
> -static void free_rule(struct landlock_rule *const rule)
> +static void free_rule(struct landlock_rule *const rule, const u16 rule_type)
>   {
>   	might_sleep();
>   	if (!rule)
>   		return;
> -	landlock_put_object(rule->object.ptr);
> +	switch (rule_type) {
> +	case LANDLOCK_RULE_PATH_BENEATH:
> +		landlock_put_object(rule->object.ptr);
> +		break;
> +	}
>   	kfree(rule);
>   }
> 
> @@ -227,12 +231,12 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   			new_rule = create_rule(object_ptr, 0, &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_inode);
> +			free_rule(this, rule_type);
>   			break;
>   		}
> -		if (IS_ERR(new_rule))
> -			return PTR_ERR(new_rule);
> -		rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode);
> -		free_rule(this);
>   		return 0;
>   	}
> 
> @@ -243,13 +247,12 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   	switch (rule_type) {
>   	case LANDLOCK_RULE_PATH_BENEATH:
>   		new_rule = create_rule(object_ptr, 0, 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_inode);
>   		break;
>   	}
> -	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_inode);
> -	ruleset->num_rules++;

Why removing this last line?

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

* Re: [PATCH v5 07/15] landlock: add support network rules
  2022-05-16 15:20 ` [PATCH v5 07/15] landlock: add support network rules Konstantin Meskhidze
@ 2022-05-17  8:27   ` Mickaël Salaün
  2022-05-19  9:27     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:27 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> This modification adds network rules support
> in internal landlock functions (presented in ruleset.c)
> and landlock_create_ruleset syscall.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Split commit.
> * Add network rule support for internal landlock functions.
> * Add set_mask and get_mask for network.
> * Add rb_root root_net_port.
> 
> Changes since v4:
> * Refactoring landlock_create_ruleset() - splits ruleset and
> masks checks.
> * Refactoring landlock_create_ruleset() and landlock mask
> setters/getters to support two rule types.
> * Refactoring landlock_add_rule syscall add_rule_path_beneath
> function by factoring out get_ruleset_from_fd() and
> landlock_put_ruleset().
> 
> ---
>   security/landlock/limits.h   |  8 +++-
>   security/landlock/ruleset.c  | 82 +++++++++++++++++++++++++++++++-----
>   security/landlock/ruleset.h  | 34 +++++++++++++--
>   security/landlock/syscalls.c | 45 +++++++++++---------
>   4 files changed, 132 insertions(+), 37 deletions(-)
> 
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index b54184ab9439..23694bf05cb7 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -22,6 +22,12 @@
>   #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
> 
> -/* 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_MASK_SHIFT_NET		16
> +
> +#define LANDLOCK_RULE_TYPE_NUM		LANDLOCK_RULE_NET_SERVICE
> 
> +/* clang-format on */
>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index c4ed783d655b..ea9ecb3f471a 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -36,6 +36,7 @@ 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;
> +	new_ruleset->root_net_port = RB_ROOT;
>   	new_ruleset->num_layers = num_layers;
>   	/*
>   	 * hierarchy = NULL
> @@ -46,17 +47,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>   }
> 
>   struct landlock_ruleset *landlock_create_ruleset(
> -		const access_mask_t access_mask)
> +					const access_mask_t access_mask_fs,
> +					const access_mask_t access_mask_net)
>   {
>   	struct landlock_ruleset *new_ruleset;
> 
>   	/* Informs about useless ruleset. */
> -	if (!access_mask)
> +	if (!access_mask_fs && !access_mask_net)
>   		return ERR_PTR(-ENOMSG);
>   	new_ruleset = create_ruleset(1);
> -	if (!IS_ERR(new_ruleset))
> -		landlock_set_fs_access_mask(new_ruleset, access_mask, 0);
> -
> +	if (IS_ERR(new_ruleset))
> +		return new_ruleset;
> +	if (access_mask_fs)
> +		landlock_set_fs_access_mask(new_ruleset, access_mask_fs, 0);
> +	if (access_mask_net)
> +		landlock_set_net_access_mask(new_ruleset, access_mask_net, 0);
>   	return new_ruleset;
>   }
> 
> @@ -94,9 +99,11 @@ static struct landlock_rule *create_rule(
>   		return ERR_PTR(-ENOMEM);
>   	RB_CLEAR_NODE(&new_rule->node);
> 
> -	if (object_ptr) {
> +	if (object_ptr && !object_data) {
>   		landlock_get_object(object_ptr);
>   		new_rule->object.ptr = object_ptr;
> +	} else if (object_data && !object_ptr) {
> +		new_rule->object.data = object_data;
>   	} else if (object_ptr && object_data) {
>   		WARN_ON_ONCE(1);
>   		return ERR_PTR(-EINVAL);
> @@ -132,10 +139,12 @@ static void build_check_ruleset(void)
>   		.num_layers = ~0,
>   	};
>   	typeof(ruleset.access_masks[0]) fs_access_mask = ~0;
> +	typeof(ruleset.access_masks[0]) net_access_mask = ~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(net_access_mask < LANDLOCK_MASK_ACCESS_NET);
>   }
> 
>   /**
> @@ -183,6 +192,11 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   		object_data = (uintptr_t)object_ptr;
>   		root = &ruleset->root_inode;
>   		break;
> +	case LANDLOCK_RULE_NET_SERVICE:
> +		if (WARN_ON_ONCE(object_ptr))
> +			return -EINVAL;
> +		root = &ruleset->root_net_port;
> +		break;
>   	default:
>   		WARN_ON_ONCE(1);
>   		return -EINVAL;
> @@ -237,6 +251,16 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   					&ruleset->root_inode);
>   			free_rule(this, rule_type);
>   			break;
> +		case LANDLOCK_RULE_NET_SERVICE:
> +			new_rule = create_rule(NULL, object_data,
> +					       &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_net_port);
> +			free_rule(this, rule_type);
> +			break;
>   		}
>   		return 0;
>   	}
> @@ -254,6 +278,15 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>   		rb_link_node(&new_rule->node, parent_node, walker_node);
>   		rb_insert_color(&new_rule->node, &ruleset->root_inode);
>   		break;
> +	case LANDLOCK_RULE_NET_SERVICE:
> +		new_rule = create_rule(NULL, object_data, 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_net_port);
> +		ruleset->num_rules++;
> +		break;
>   	}
>   	return 0;
>   }
> @@ -315,6 +348,9 @@ static int tree_merge(struct landlock_ruleset *const src,
>   	case LANDLOCK_RULE_PATH_BENEATH:
>   		src_root = &src->root_inode;
>   		break;
> +	case LANDLOCK_RULE_NET_SERVICE:
> +		src_root = &src->root_net_port;
> +		break;
>   	default:
>   		return -EINVAL;
>   	}
> @@ -341,6 +377,11 @@ static int tree_merge(struct landlock_ruleset *const src,
>   					  rule_type, &layers,
>   					  ARRAY_SIZE(layers));
>   			break;
> +		case LANDLOCK_RULE_NET_SERVICE:
> +			err = insert_rule(dst, NULL, walker_rule->object.data,
> +					  rule_type, &layers,
> +					  ARRAY_SIZE(layers));
> +			break;
>   		}
>   		if (err)
>   			return err;
> @@ -376,6 +417,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   	err = tree_merge(src, dst, LANDLOCK_RULE_PATH_BENEATH);
>   	if (err)
>   		goto out_unlock;
> +	/* Merges the @src network tree. */
> +	err = tree_merge(src, dst, LANDLOCK_RULE_NET_SERVICE);
> +	if (err)
> +		goto out_unlock;
> 
>   out_unlock:
>   	mutex_unlock(&src->lock);
> @@ -395,6 +440,9 @@ static int tree_copy(struct landlock_ruleset *const parent,
>   	case LANDLOCK_RULE_PATH_BENEATH:
>   		parent_root = &parent->root_inode;
>   		break;
> +	case LANDLOCK_RULE_NET_SERVICE:
> +		parent_root = &parent->root_net_port;
> +		break;
>   	default:
>   		return -EINVAL;
>   	}
> @@ -407,6 +455,12 @@ static int tree_copy(struct landlock_ruleset *const parent,
>   					  rule_type, &walker_rule->layers,
>   					  walker_rule->num_layers);
>   			break;
> +		case LANDLOCK_RULE_NET_SERVICE:
> +			err = insert_rule(child, NULL,
> +					  walker_rule->object.data, rule_type,
> +					  &walker_rule->layers,
> +					  walker_rule->num_layers);
> +			break;
>   		}
>   		if (err)
>   			return err;
> @@ -429,6 +483,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
> 
>   	/* Copies the @parent inode tree. */
>   	err = tree_copy(parent, child, LANDLOCK_RULE_PATH_BENEATH);
> +	if (err)
> +		goto out_unlock;
> +	/* Copies the @parent inode tree. */

Not the inode tree this time.


> +	err = tree_copy(parent, child, LANDLOCK_RULE_NET_SERVICE);
>   	if (err)
>   		goto out_unlock;
> 
> @@ -463,9 +521,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
> 
>   	might_sleep();
>   	rbtree_postorder_for_each_entry_safe(freeme, next,
> -					     &ruleset->root_inode,
> -					     node)
> +					     &ruleset->root_inode, node)
>   		free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
> +	rbtree_postorder_for_each_entry_safe(freeme, next,
> +					     &ruleset->root_net_port, node)
> +		free_rule(freeme, LANDLOCK_RULE_NET_SERVICE);
>   	put_hierarchy(ruleset->hierarchy);
>   	kfree(ruleset);
>   }
> @@ -560,13 +620,13 @@ const struct landlock_rule *landlock_find_rule(
>   {
>   	const struct rb_node *node;
> 
> -	if (!object_data)
> -		return NULL;
> -
>   	switch (rule_type) {
>   	case LANDLOCK_RULE_PATH_BENEATH:
>   		node = ruleset->root_inode.rb_node;
>   		break;
> +	case LANDLOCK_RULE_NET_SERVICE:
> +		node = ruleset->root_net_port.rb_node;
> +		break;
>   	default:
>   		WARN_ON_ONCE(1);
>   		return NULL;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index f3cd890d0348..916b30b31c06 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -102,6 +102,12 @@ struct landlock_ruleset {
>   	 * tree is immutable until @usage reaches zero.
>   	 */
>   	struct rb_root root_inode;
> +	/**
> +	 * @root_net_port: Root of a red-black tree containing object nodes
> +	 * for 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;
>   	/**
>   	 * @hierarchy: Enables hierarchy identification even when a parent
>   	 * domain vanishes.  This is needed for the ptrace protection.
> @@ -157,7 +163,8 @@ struct landlock_ruleset {
>   };
> 
>   struct landlock_ruleset *landlock_create_ruleset(
> -		const access_mask_t access_mask);
> +					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);
> @@ -183,11 +190,12 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>   }
> 
>   /* A helper function to set a filesystem mask */
> -static inline void landlock_set_fs_access_mask(struct landlock_ruleset *ruleset,
> -				 const access_mask_t access_maskset,
> +static inline void landlock_set_fs_access_mask(
> +				 struct landlock_ruleset *ruleset,
> +				 const access_mask_t access_mask_fs,
>   				 u16 mask_level)
>   {
> -	ruleset->access_masks[mask_level] = access_maskset;
> +	ruleset->access_masks[mask_level] = access_mask_fs;
>   }
> 
>   /* A helper function to get a filesystem mask */
> @@ -198,6 +206,24 @@ static inline u32 landlock_get_fs_access_mask(
>   	return (ruleset->access_masks[mask_level] & LANDLOCK_MASK_ACCESS_FS);
>   }
> 
> +/* A helper function to set a network mask */
> +static inline void landlock_set_net_access_mask(
> +				  struct landlock_ruleset *ruleset,
> +				  const access_mask_t access_mask_net,
> +				  u16 mask_level)
> +{
> +	ruleset->access_masks[mask_level] |= (access_mask_net <<
> +					      LANDLOCK_MASK_SHIFT_NET);
> +}
> +
> +/* A helper function to get a network mask */
> +static inline u32 landlock_get_net_access_mask(
> +				const struct landlock_ruleset *ruleset,
> +				u16 mask_level)
> +{
> +	return (ruleset->access_masks[mask_level] >> LANDLOCK_MASK_SHIFT_NET);
> +}
> +
>   access_mask_t get_handled_accesses(
>   		const struct landlock_ruleset *const domain,
>   		u16 rule_type, u16 num_access);
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index 31f9facec123..812541f4e155 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -189,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);
> 
> @@ -275,21 +281,17 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>   	return err;
>   }
> 
> -static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_attr)
> +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
> +				 const void *const rule_attr)
>   {
>   	struct landlock_path_beneath_attr path_beneath_attr;
>   	struct path path;
> -	struct landlock_ruleset *ruleset;
>   	int res, err;
> -
> -	/* Gets and checks the ruleset. */
> -	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
> -	if (IS_ERR(ruleset))
> -		return PTR_ERR(ruleset);
> +	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));
> +			sizeof(path_beneath_attr));
>   	if (res)
>   		return -EFAULT;
> 
> @@ -298,32 +300,26 @@ static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_at
>   	 * are ignored in path walks.
>   	 */
>   	if (!path_beneath_attr.allowed_access) {
> -		err = -ENOMSG;
> -		goto out_put_ruleset;
> +		return -ENOMSG;
>   	}
>   	/*
>   	 * 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;
> -	}
> +	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)
> -		goto out_put_ruleset;
> +		return err;
> 
>   	/* 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;
>   }
> 
> @@ -360,6 +356,7 @@ 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_ruleset *ruleset;
>   	int err;
> 
>   	if (!landlock_initialized)
> @@ -369,14 +366,20 @@ SYSCALL_DEFINE4(landlock_add_rule,
>   	if (flags)
>   		return -EINVAL;
> 
> +	/* Gets and checks the ruleset. */
> +	ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
> +	if (IS_ERR(ruleset))
> +		return PTR_ERR(ruleset);

This shouldn't be part of this patch.


> +
>   	switch (rule_type) {
>   	case LANDLOCK_RULE_PATH_BENEATH:
> -		err = add_rule_path_beneath(ruleset_fd, rule_attr);
> +		err = add_rule_path_beneath(ruleset, rule_attr);
>   		break;
>   	default:
>   		err = -EINVAL;
>   		break;
>   	}
> +	landlock_put_ruleset(ruleset);
>   	return err;
>   }
> 
> --
> 2.25.1
> 

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

* Re: [PATCH v5 08/15] landlock: TCP network hooks implementation
  2022-05-16 15:20 ` [PATCH v5 08/15] landlock: TCP network hooks implementation Konstantin Meskhidze
@ 2022-05-17  8:51   ` Mickaël Salaün
  2022-05-19 11:40     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:51 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Support of socket_bind() and socket_connect() hooks.
> Its possible to restrict binding and connecting of TCP
> types of sockets to particular ports. Its just basic idea
> how Landlock could support network confinement.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Split commit.
> * Add SECURITY_NETWORK in config.
> * Add IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
> * Add hook_socket_bind and hook_socket_connect hooks.
> 
> Changes since v4:
> * Factors out CONFIG_INET into make file.
> * Refactoring 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.
> * Refactoring add_rule_net_service() and landlock_add_rule
> syscall to support network rule inserting.
> * Refactoring init_layer_masks() to support network rules.
> 
> ---
>   security/landlock/Kconfig    |   1 +
>   security/landlock/Makefile   |   2 +
>   security/landlock/net.c      | 159 +++++++++++++++++++++++++++++++++++
>   security/landlock/net.h      |  25 ++++++
>   security/landlock/ruleset.c  |  15 +++-
>   security/landlock/setup.c    |   2 +
>   security/landlock/syscalls.c |  63 ++++++++++++--
>   7 files changed, 261 insertions(+), 6 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..9302e5891991
> --- /dev/null
> +++ b/security/landlock/net.c
> @@ -0,0 +1,159 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock LSM - Network management and hooks
> + *
> + * Copyright (C) 2022 Huawei Tech. Co., Ltd.
> + */
> +
> +#include <linux/in.h>
> +#include <linux/net.h>
> +#include <linux/socket.h>
> +#include <net/ipv6.h>
> +
> +#include "cred.h"
> +#include "limits.h"
> +#include "net.h"
> +
> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> +			     u16 port, u32 access_rights)
> +{
> +	int err;
> +
> +	/* Transforms relative access rights to absolute ones. */
> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
> +			 ~landlock_get_net_access_mask(ruleset, 0);
> +
> +	BUILD_BUG_ON(sizeof(port) > sizeof(uintptr_t));
> +	mutex_lock(&ruleset->lock);
> +	err = landlock_insert_rule(ruleset, NULL, port,
> +				access_rights, LANDLOCK_RULE_NET_SERVICE);
> +	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;
> +
> +	if (WARN_ON_ONCE(!domain))
> +		return 0;
> +	if (WARN_ON_ONCE(domain->num_layers < 1))
> +		return -EACCES;
> +
> +	rule = landlock_find_rule(domain, port,
> +					LANDLOCK_RULE_NET_SERVICE);
> +
> +	handled_access = init_layer_masks(domain, access_request,
> +			&layer_masks, sizeof(layer_masks),
> +			LANDLOCK_RULE_NET_SERVICE);
> +	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:

Are you sure about that?

Please write a test for this case.


> +	case AF_INET:
> +	{

You don't need these braces (except if it is required by checkpatch.pl).


> +		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
> +	}

You missed some part of my patch… We should not get the port for a 
protocol we don't know, hence the WARN_ON_ONCE.


> +	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;
> +
> +	/* Get port value in host byte order */

I moved/removed this in my patch against v4 for a reason. Please, ask if 
you don't understand or if you don't agree with something I said.


> +	switch (address->sa_family) {
> +	case AF_UNSPEC:

Is this correct?

Please write a test for this case.

> +	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;
> +
> +	/* Get port value in host byte order */
> +	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..da5ce8fa04cc
> --- /dev/null
> +++ b/security/landlock/net.h
> @@ -0,0 +1,25 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock LSM - Network management and hooks
> + *
> + * Copyright (C) 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,
> +				u16 port, u32 access_hierarchy);
> +#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/ruleset.c b/security/landlock/ruleset.c
> index ea9ecb3f471a..317cf98890f6 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -671,7 +671,7 @@ access_mask_t get_handled_accesses(
>   		}
>   		break;
>   	default:
> -		break;
> +		return 0;

Why?


>   	}
>   	return access_dom;
>   }
> @@ -763,6 +763,19 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain,
>   				}
>   			}
>   			break;
> +		case LANDLOCK_RULE_NET_SERVICE:
> +			for_each_set_bit(access_bit, &access_req,
> +					LANDLOCK_NUM_ACCESS_NET) {
> +				if (landlock_get_net_access_mask(domain,
> +								 layer_level) &
> +						BIT_ULL(access_bit)) {
> +					(*layer_masks)[access_bit] |=
> +						BIT_ULL(layer_level);
> +					handled_accesses |=
> +							   BIT_ULL(access_bit);
> +				}
> +			}
> +			break;
>   		default:
>   			return 0;
>   		}
> diff --git a/security/landlock/setup.c b/security/landlock/setup.c
> index f8e8e980454c..8059dc0b47d3 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;
> 
> @@ -28,6 +29,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 812541f4e155..9454c6361011 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 */
> @@ -299,9 +306,9 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>   	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
>   	 * are ignored in path walks.
>   	 */
> -	if (!path_beneath_attr.allowed_access) {
> +	if (!path_beneath_attr.allowed_access)

Why?


>   		return -ENOMSG;
> -	}
> +
>   	/*
>   	 * Checks that allowed_access matches the @ruleset constraints
>   	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
> @@ -323,13 +330,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 *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.
> @@ -340,6 +388,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 rule's
>    *   accesses);
> @@ -375,6 +425,9 @@ SYSCALL_DEFINE4(landlock_add_rule,
>   	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] 56+ messages in thread

* Re: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-16 15:20 ` [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests Konstantin Meskhidze
@ 2022-05-17  8:55   ` Mickaël Salaün
  2022-05-19 12:31     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  8:55 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov

I guess these tests would also work with IPv6. You can then use the 
"alternative" tests I explained.

On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Adds two selftests for connect() action with
> AF_UNSPEC family flag.
> The one is with no landlock restrictions
> allows to disconnect already conneted socket
> with connect(..., AF_UNSPEC, ...):
>      - connect_afunspec_no_restictions;
> The second one refuses landlocked process
> to disconnect already connected socket:
>      - connect_afunspec_with_restictions;
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Add connect_afunspec_no_restictions test.
> * Add connect_afunspec_with_restictions test.
> 
> Changes since v4:
> * Refactoring code with self->port, self->addr4 variables.
> * Adds bind() hook check for with AF_UNSPEC family.
> 
> ---
>   tools/testing/selftests/landlock/net_test.c | 121 ++++++++++++++++++++
>   1 file changed, 121 insertions(+)
> 
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index cf914d311eb3..bf8e49466d1d 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -449,6 +449,7 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
>   	int new_fd;
>   	int sockfd_1, sockfd_2;
>   	pid_t child_1, child_2;
> +
>   	int status;
> 
>   	struct landlock_ruleset_attr ruleset_attr = {
> @@ -467,10 +468,12 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
> 
>   	const int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>   			sizeof(ruleset_attr), 0);
> +

Please no…


>   	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,
> +

ditto

>   				&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,
> @@ -480,6 +483,7 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
>   	enforce_ruleset(_metadata, ruleset_fd);
> 
>   	/* Creates a server socket 1 */
> +
>   	sockfd_1 = create_socket(_metadata, true, false);
>   	ASSERT_LE(0, sockfd_1);
> 
> @@ -556,4 +560,121 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) {
>   	ASSERT_EQ(1, WIFEXITED(status));
>   	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
>   }
> +
> +TEST_F_FORK(socket_test, connect_afunspec_no_restictions) {
> +
> +	int sockfd;
> +	pid_t child;
> +	int status;
> +
> +	/* Creates a server socket 1 */
> +	sockfd = create_socket(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +
> +	/* Binds the socket 1 to address with port[0] with AF_UNSPEC family */
> +	self->addr4[0].sin_family = AF_UNSPEC;
> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
> +
> +	/* Makes connection to socket with port[0] */
> +	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
> +						   sizeof(self->addr4[0])));
> +
> +	child = fork();
> +	ASSERT_LE(0, child);
> +	if (child == 0) {
> +		struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC};
> +
> +		/* 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_test, connect_afunspec_with_restictions) {
> +
> +	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(_metadata, false, false);
> +	ASSERT_LE(0, sockfd);
> +
> +	/* Binds the socket 1 to address with port[0] with AF_UNSPEC family */
> +	self->addr4[0].sin_family = AF_UNSPEC;
> +	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0])));
> +
> +	/* Makes connection to socket with port[0] */
> +	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
> +						   sizeof(self->addr4[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) {
> +		struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC};
> +
> +		/* 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_HARNESS_MAIN
> --
> 2.25.1
> 

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

* Re: [PATCH v5 15/15] samples/landlock: adds network demo
  2022-05-16 15:20 ` [PATCH v5 15/15] samples/landlock: adds network demo Konstantin Meskhidze
@ 2022-05-17  9:19   ` Mickaël Salaün
  2022-05-19 13:33     ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-17  9:19 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> This commit adds network demo. It's possible to
> allow a sandoxer to bind/connect to a list of
> particular ports restricting networks actions to
> the rest of ports.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> 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.
> * Refactoring main() to support network sandboxing.
> 
> ---
>   samples/landlock/sandboxer.c | 105 +++++++++++++++++++++++++++++++----
>   security/landlock/ruleset.h  |   4 +-
>   2 files changed, 95 insertions(+), 14 deletions(-)
> 
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index 3e404e51ec64..4006c42eec1c 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 ( \
> @@ -80,7 +96,7 @@ 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,
> +static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
>   			    const __u64 allowed_access)
>   {
>   	int num_paths, i, ret = 1;
> @@ -142,6 +158,49 @@ 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) {
> +		/* Prevents users to forget a setting. */
> +		fprintf(stderr, "Missing environment variable %s\n", env_var);
> +		return 1;

I think network ports should be optional to be able to test without that 
(and not break compatibility). You can pass &ruleset_attr as argument to 
update it accordingly:
- without environment variable: no network restriction;
- with empty environment variable: all connect (or bind) denied;
- otherwise: only allow the listed ports.


> +	}
> +	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 ( \
> @@ -173,19 +232,24 @@ int main(const int argc, char *const argv[], char *const *const envp)
>   	char *const *cmd_argv;
>   	int ruleset_fd, abi;
>   	__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 = LANDLOCK_ACCESS_NET_BIND_TCP |
> +					LANDLOCK_ACCESS_NET_CONNECT_TCP;
>   	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);
> @@ -193,11 +257,19 @@ int main(const int argc, char *const argv[], char *const *const envp)
>   			"* %s: list of paths allowed to be used in a read-write way.\n",
>   			ENV_FS_RW_NAME);
>   		fprintf(stderr,
> -			"\nexample:\n"
> +			"* %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);

This is good and will be better with clang-format. ;)

> +		fprintf(stderr, "\nexample:\n"
>   			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
>   			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> +			"%s=\"15000:16000\" "

Bind ports example should reference unprivileged ports such as "9418" 
(git, not well-known but OK).


> +			"%s=\"10000:12000\" "

Connect ports example should reference well-known ports such as "80:443".

>   			"%s bash -i\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]);
>   		return 1;
>   	}
> 
> @@ -234,16 +306,25 @@ int main(const int argc, char *const argv[], char *const *const envp)
> 
>   	ruleset_fd =
>   		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> +

Why?


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

Why? I know that checkpatch.pl prints a warning for that but I 
delibirately chooe to use curly braces even for "if" statements with one 
line because it is safer. This code may be copied/pasted and I'd like 
others to avoid introducing goto-fail-like issues.



> -	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
> +
> +	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))

So please use curly braces here too.

> +		goto err_close_ruleset;
> +
> +	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)) {
>   		perror("Failed to restrict privileges");
>   		goto err_close_ruleset;
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 916b30b31c06..e1ff40f238a6 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -19,7 +19,7 @@
>   #include "limits.h"
>   #include "object.h"
> 
> -typedef u16 access_mask_t;
> +typedef u32 access_mask_t;

What‽


> 
>   /* Makes sure all filesystem access rights can be stored. */
>   static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
> @@ -157,7 +157,7 @@ struct landlock_ruleset {
>   			 * layers are set once and never changed for the
>   			 * lifetime of the ruleset.
>   			 */
> -			u32 access_masks[];
> +			access_mask_t access_masks[];
>   		};
>   	};
>   };
> --
> 2.25.1
> 

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

* Re: [PATCH v5 04/15] landlock: helper functions refactoring
  2022-05-16 18:28       ` Mickaël Salaün
@ 2022-05-18  9:14         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-18  9:14 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/16/2022 9:28 PM, Mickaël Salaün пишет:
> 
> On 16/05/2022 19:43, Konstantin Meskhidze wrote:
>>
>>
>> 5/16/2022 8:14 PM, Mickaël Salaün пишет:
>>>
>>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>>> Unmask_layers(), init_layer_masks() and
>>>> get_handled_accesses() helper functions move to
>>>> ruleset.c and rule_type argument is added.
>>>> This modification supports implementing new rule
>>>> types into next landlock versions.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> 
> [...]
> 
>>>> -/*
>>>> - * @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])
>>>
>>> Moving these entire blocks of code make the review/diff impossible. 
>>> Why moving these helpers?
>>
>>    Cause these helpers are going to be used both for filesystem and 
>> network. I moved them into ruleset.c/h
> 
> Right. Please create a commit which only moves these helpers without 
> modifying them, and explain in the commit message that this removes 
> inlined code. We'll see later if this adds a visible performance impact.
> 
    Ok. I will create towo commits - the first one moves helpers to 
ruleset.c/h and the second one changes helpers to support network.
> [...]
> 
>>>> @@ -519,17 +413,25 @@ static int check_access_path_dual(
>>>>
>>>>       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);
>>>> +                init_layer_masks(domain,
>>>> +                    LANDLOCK_MASK_ACCESS_FS,
>>>> +                    &_layer_masks_child1,
>>>> +                    sizeof(_layer_masks_child1),
>>>> +                    LANDLOCK_RULE_PATH_BENEATH),
>>>> +                &_layer_masks_child1,
>>>> +                ARRAY_SIZE(_layer_masks_child1));
>>>
>>> There is a lot of formatting diff and that makes the review 
>>> difficult. Please format everything with clang-format-14.
>>
>>    Ok. Do you have some tool that helps you with editing code with 
>> clang format?
> 
> I just run `clang-format-14 -i` on files before each commit. Some 
> editors such as VSCode can handle the clang-format configuration (which 
> is in the Linux source tree).
> 
  Ok. I have updated installed cloang-format-14 executable and setup my 
VSCode to use .clang-format file.
> 
>>>
>>>>           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);
>>>> +                init_layer_masks(domain,
>>>> +                    LANDLOCK_MASK_ACCESS_FS,
>>>> +                    &_layer_masks_child2,
>>>> +                    sizeof(_layer_masks_child2),
>>>> +                    LANDLOCK_RULE_PATH_BENEATH),
>>>> +                &_layer_masks_child2,
>>>> +                ARRAY_SIZE(_layer_masks_child2));
>>>>           layer_masks_child2 = &_layer_masks_child2;
>>>>           child2_is_directory = d_is_dir(dentry_child2);
>>>>       }
>>>> @@ -582,14 +484,15 @@ static int check_access_path_dual(
>>>>
>>>>           rule = find_rule(domain, walker_path.dentry);
>>>>           allowed_parent1 = unmask_layers(rule, access_masked_parent1,
>>>> -                        layer_masks_parent1);
>>>> +                layer_masks_parent1,
>>>> +                ARRAY_SIZE(*layer_masks_parent1));
>>>>           allowed_parent2 = unmask_layers(rule, access_masked_parent2,
>>>> -                        layer_masks_parent2);
>>>> +                layer_masks_parent2,
>>>> +                ARRAY_SIZE(*layer_masks_parent2));
>>>>
>>>>           /* Stops when a rule from each layer grants access. */
>>>>           if (allowed_parent1 && allowed_parent2)
>>>>               break;
>>>> -
>>>
>>> There is no place for such formatting/whitespace patches.
>>>
>>    I missed that. scripts/checkpatch.pl did not show any problem here.
> 
> checkpatch.pl doesn't warn about whitespace changes.

   yep. I will use Vscode clang plugin to follow the required code style.
> 
> 
>>    I will fix it. Thanks.
>>>
>>>>   jump_up:
>>>>           if (walker_path.dentry == walker_path.mnt->mnt_root) {
>>>>               if (follow_up(&walker_path)) {
>>>> @@ -645,7 +548,9 @@ 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, sizeof(layer_masks),
>>>> +            LANDLOCK_RULE_PATH_BENEATH);
>>>>       return check_access_path_dual(domain, path, access_request,
>>>>                         &layer_masks, NULL, 0, NULL, NULL);
>>>>   }
>>>> @@ -729,7 +634,8 @@ static bool collect_domain_accesses(
>>>>           return true;
>>>>
>>>>       access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>>>> -                      layer_masks_dom);
>>>> +            layer_masks_dom, sizeof(*layer_masks_dom),
>>>> +            LANDLOCK_RULE_PATH_BENEATH);
>>>>
>>>>       dget(dir);
>>>>       while (true) {
>>>> @@ -737,7 +643,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.
>>>> @@ -851,9 +758,10 @@ static int current_check_refer_path(struct 
>>>> dentry *const old_dentry,
>>>>            * The LANDLOCK_ACCESS_FS_REFER access right is not required
>>>>            * for same-directory referer (i.e. no reparenting).
>>>>            */
>>>> -        access_request_parent1 = init_layer_masks(
>>>> -            dom, access_request_parent1 | access_request_parent2,
>>>> -            &layer_masks_parent1);
>>>> +        access_request_parent1 = init_layer_masks(dom,
>>>> +                access_request_parent1 | access_request_parent2,
>>>> +                &layer_masks_parent1, sizeof(layer_masks_parent1),
>>>> +                LANDLOCK_RULE_PATH_BENEATH);
>>>>           return check_access_path_dual(dom, new_dir,
>>>>                             access_request_parent1,
>>>>                             &layer_masks_parent1, NULL, 0,
>>>> @@ -861,7 +769,9 @@ static int current_check_refer_path(struct 
>>>> dentry *const old_dentry,
>>>>       }
>>>>
>>>>       /* Backward compatibility: no reparenting support. */
>>>> -    if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER))
>>>> +    if (!(get_handled_accesses(dom, LANDLOCK_RULE_PATH_BENEATH,
>>>> +                   LANDLOCK_NUM_ACCESS_FS) &
>>>> +                        LANDLOCK_ACCESS_FS_REFER))
>>>>           return -EXDEV;
>>>>
>>>>       access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
>>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>>> index 4b4c9953bb32..c4ed783d655b 100644
>>>> --- a/security/landlock/ruleset.c
>>>> +++ b/security/landlock/ruleset.c
>>>> @@ -233,7 +233,8 @@ static int insert_rule(struct landlock_ruleset 
>>>> *const ruleset,
>>>>                              &(*layers)[0]);
>>>>               if (IS_ERR(new_rule))
>>>>                   return PTR_ERR(new_rule);
>>>> -            rb_replace_node(&this->node, &new_rule->node, 
>>>> &ruleset->root_inode);
>>>> +            rb_replace_node(&this->node, &new_rule->node,
>>>> +                    &ruleset->root_inode);
>>>
>>> This is a pure formatting hunk. :/
>>>
>>    Thats strange, cause in my editor I have normal aligment of arguments.
>>    Could please share clang-format-14 tab size and other format 
>> parameters?
> 
> They are in the .clang-format file. It would be much easier to just run 
> clang-format-14 -i on your changed files. I guess you had different 
> changes between consecutive commits.

  Yep. Thnank you for help here.
> .

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

* Re: [PATCH v5 01/15] landlock: access mask renaming
  2022-05-17  8:12   ` Mickaël Salaün
@ 2022-05-18  9:16     ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-18  9:16 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:12 AM, Mickaël Salaün пишет:
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Currently Landlock supports filesystem
>> restrictions. To support network type rules,
>> this modification extends and renames
>> ruleset's access masks.
>> This patch adds filesystem helper functions
>> to set and get filesystem mask. Also the modification
>> adds a helper structure landlock_access_mask to
>> support managing multiple access mask.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
> 
> [...]
> 
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index d43231b783e4..f27a79624962 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -20,6 +20,7 @@
>>   #include "object.h"
>>
>>   typedef u16 access_mask_t;
>> +
>>   /* Makes sure all filesystem access rights can be stored. */
> 
> Please don't add whitespaces.

  Ok. Sorry for silly mistakes. I will fix it.
> .

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

* Re: [PATCH v5 03/15] landlock: merge and inherit function refactoring
  2022-05-17  8:14   ` Mickaël Salaün
@ 2022-05-18  9:18     ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-18  9:18 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:14 AM, Mickaël Salaün пишет:
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Merge_ruleset() and inherit_ruleset() functions were
>> refactored to support new rule types. This patch adds
>> tree_merge() and tree_copy() helpers. Each has
>> rule_type argument to choose a particular rb_tree
>> structure in a ruleset.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Split commit.
>> * Refactoring functions:
>>     -insert_rule.
>>     -merge_ruleset.
>>     -tree_merge.
>>     -inherit_ruleset.
>>     -tree_copy.
>>     -free_rule.
>>
>> Changes since v4:
>> * None
>>
>> ---
>>   security/landlock/ruleset.c | 144 ++++++++++++++++++++++++------------
>>   1 file changed, 98 insertions(+), 46 deletions(-)
>>
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index f079a2a320f1..4b4c9953bb32 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -112,12 +112,16 @@ static struct landlock_rule *create_rule(
>>       return new_rule;
>>   }
>>
>> -static void free_rule(struct landlock_rule *const rule)
>> +static void free_rule(struct landlock_rule *const rule, const u16 
>> rule_type)
>>   {
>>       might_sleep();
>>       if (!rule)
>>           return;
>> -    landlock_put_object(rule->object.ptr);
>> +    switch (rule_type) {
>> +    case LANDLOCK_RULE_PATH_BENEATH:
>> +        landlock_put_object(rule->object.ptr);
>> +        break;
>> +    }
>>       kfree(rule);
>>   }
>>
>> @@ -227,12 +231,12 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>               new_rule = create_rule(object_ptr, 0, &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_inode);
>> +            free_rule(this, rule_type);
>>               break;
>>           }
>> -        if (IS_ERR(new_rule))
>> -            return PTR_ERR(new_rule);
>> -        rb_replace_node(&this->node, &new_rule->node, 
>> &ruleset->root_inode);
>> -        free_rule(this);
>>           return 0;
>>       }
>>
>> @@ -243,13 +247,12 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>       switch (rule_type) {
>>       case LANDLOCK_RULE_PATH_BENEATH:
>>           new_rule = create_rule(object_ptr, 0, 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_inode);
>>           break;
>>       }
>> -    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_inode);
>> -    ruleset->num_rules++;
> 
> Why removing this last line?

  Thank you for noticing that. Its my mistake during refactoring the 
code. Selftests did not show it.

> .

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

* Re: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-17  8:04   ` Mickaël Salaün
  2022-05-17  8:10     ` Mickaël Salaün
@ 2022-05-19  9:23     ` Konstantin Meskhidze
  2022-05-19 14:37       ` Mickaël Salaün
  1 sibling, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19  9:23 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:04 AM, Mickaël Salaün пишет:
> You can rename the subject to "landlock: Refactor landlock_add_rule()"
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Landlock_add_rule syscall was refactored to support new
>> rule types in future Landlock versions. Add_rule_path_beneath()
> 
> nit: add_rule_path_beneath(), not Add_rule_path_beneath()
> 
   Ok. Thanks. Will be renamed.

>> helper was added to support current filesystem rules. It is called
>> by the switch case.
> 
> You can rephrase (all commit messages) in the present form:
> 
> Refactor the landlock_add_rule() syscall with add_rule_path_beneath() to 
> support new…
> 
> Refactor the landlock_add_rule() syscall to easily support for a new 
> rule type in a following commit. The new add_rule_path_beneath() helper 
> supports current filesystem rules.
> 
   Ok. I will fix it.
> 
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Split commit.
>> * Refactoring landlock_add_rule syscall.
>>
>> Changes since v4:
>> * Refactoring add_rule_path_beneath() and landlock_add_rule() functions
>> to optimize code usage.
>> * Refactoring base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
>> rule type in landlock_add_rule() call.
>>
>> ---
>>   security/landlock/syscalls.c                 | 105 ++++++++++---------
>>   tools/testing/selftests/landlock/base_test.c |   4 +-
>>   2 files changed, 59 insertions(+), 50 deletions(-)
>>
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index 1db799d1a50b..412ced6c512f 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -274,67 +274,23 @@ static int get_path_from_fd(const s32 fd, struct 
>> path *const path)
>>       return err;
>>   }
>>
>> -/**
>> - * 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_attr: Pointer to a rule (only of type &struct
>> - *             landlock_path_beneath_attr for now).
>> - * @flags: Must be 0.
>> - *
>> - * This system call enables to define a new rule and add it to an 
>> existing
>> - * ruleset.
>> - *
>> - * Possible returned errors are:
>> - *
>> - * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at 
>> boot time;
>> - * - 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);
>> - * - ENOMSG: Empty accesses (e.g. 
>> &landlock_path_beneath_attr.allowed_access);
>> - * - EBADF: @ruleset_fd is not a file descriptor for the current 
>> thread, or a
>> - *   member of @rule_attr is not a file descriptor as expected;
>> - * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a 
>> member of
>> - *   @rule_attr is not the expected file descriptor type;
>> - * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
>> - * - EFAULT: @rule_attr inconsistency.
>> - */
>> -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)
>> +static int add_rule_path_beneath(const int ruleset_fd, const void 
>> *const rule_attr)
>>   {
>>       struct landlock_path_beneath_attr path_beneath_attr;
>>       struct path path;
>>       struct landlock_ruleset *ruleset;
>>       int res, err;
>>
>> -    if (!landlock_initialized)
>> -        return -EOPNOTSUPP;
>> -
>> -    /* No flag for now. */
>> -    if (flags)
>> -        return -EINVAL;
>> -
>>       /* Gets and checks the ruleset. */
> 
> Like I already said, this needs to stay in landlock_add_rule(). I think 
> there is some inconsistencies with other patches that rechange this 
> part. Please review your patches and make clean patches that don't 
> partially revert the previous ones.
> 
   Do you mean to leave this code as it its till adding network part
in commit landlock: TCP network hooks implementation?
  In this case this patch can be dropped.
> 
>>       ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>       if (IS_ERR(ruleset))
>>           return PTR_ERR(ruleset);
>>
>> -    if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
>> -        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;
>> -    }
>> +                sizeof(path_beneath_attr));
>> +    if (res)
>> +        return -EFAULT;
>>
>>       /*
>>        * Informs about useless rule: empty allowed_access (i.e. deny 
>> rules)
>> @@ -370,6 +326,59 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, 
>> ruleset_fd,
>>       return err;
>>   }
>>
>> +/**
>> + * 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_attr: Pointer to a rule (only of type &struct
>> + *             landlock_path_beneath_attr for now).
>> + * @flags: Must be 0.
>> + *
>> + * This system call enables to define a new rule and add it to an 
>> existing
>> + * ruleset.
>> + *
>> + * Possible returned errors are:
>> + *
>> + * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at 
>> boot time;
>> + * - 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 rule's
>> + *   accesses);
>> + * - ENOMSG: Empty accesses (e.g. 
>> &landlock_path_beneath_attr.allowed_access);
>> + * - EBADF: @ruleset_fd is not a file descriptor for the current 
>> thread, or a
>> + *   member of @rule_attr is not a file descriptor as expected;
>> + * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a 
>> member of
>> + *   @rule_attr is not the expected file descriptor type (e.g. file open
>> + *   without O_PATH);
>> + * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
>> + * - EFAULT: @rule_attr inconsistency.
>> + */
>> +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)
>> +{
>> +    int err;
>> +
>> +    if (!landlock_initialized)
>> +        return -EOPNOTSUPP;
>> +
>> +    /* No flag for now. */
>> +    if (flags)
>> +        return -EINVAL;
>> +
>> +    switch (rule_type) {
>> +    case LANDLOCK_RULE_PATH_BENEATH:
>> +        err = add_rule_path_beneath(ruleset_fd, rule_attr);
>> +        break;
>> +    default:
>> +        err = -EINVAL;
>> +        break;
>> +    }
>> +    return err;
>> +}
>> +
>>   /* Enforcement */
>>
>>   /**
>> diff --git a/tools/testing/selftests/landlock/base_test.c 
>> b/tools/testing/selftests/landlock/base_test.c
>> index da9290817866..0c4c3a538d54 100644
>> --- a/tools/testing/selftests/landlock/base_test.c
>> +++ b/tools/testing/selftests/landlock/base_test.c
>> @@ -156,11 +156,11 @@ TEST(add_rule_checks_ordering)
>>       ASSERT_LE(0, ruleset_fd);
>>
>>       /* Checks invalid flags. */
>> -    ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
>> +    ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 
>> NULL, 1));
> 
> This must not be changed! I specifically added these tests to make sure 
> no one change the argument ordering checks…

   I updated this code cause I got error in base_test.
   Ok. But in future commints I will order funtions calls in
   landlock_add_rule() so that base_test runs smoothly (ordering checks).

> 
> 
>>       ASSERT_EQ(EINVAL, errno);
>>
>>       /* Checks invalid ruleset FD. */
>> -    ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0));
>> +    ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 
>> NULL, 0));
>>       ASSERT_EQ(EBADF, errno);
>>
>>       /* Checks invalid rule type. */
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-17  8:10     ` Mickaël Salaün
@ 2022-05-19  9:24       ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19  9:24 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:10 AM, Mickaël Salaün пишет:
> 
> On 17/05/2022 10:04, Mickaël Salaün wrote:
>> You can rename the subject to "landlock: Refactor landlock_add_rule()"
>>
>>
>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> 
> [...]
> 
>>> helper was added to support current filesystem rules. It is called
>>> by the switch case.
>>
>> You can rephrase (all commit messages) in the present form:
> present *tense*

  Ok. I got it. Thanks.
>>
>> Refactor the landlock_add_rule() syscall to easily support for a new 
>> rule type in a following commit. The new add_rule_path_beneath() 
>> helper supports current filesystem rules.
> .

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

* Re: [PATCH v5 07/15] landlock: add support network rules
  2022-05-17  8:27   ` Mickaël Salaün
@ 2022-05-19  9:27     ` Konstantin Meskhidze
  2022-05-19 14:42       ` Mickaël Salaün
  0 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19  9:27 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:27 AM, Mickaël Salaün пишет:
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> This modification adds network rules support
>> in internal landlock functions (presented in ruleset.c)
>> and landlock_create_ruleset syscall.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Split commit.
>> * Add network rule support for internal landlock functions.
>> * Add set_mask and get_mask for network.
>> * Add rb_root root_net_port.
>>
>> Changes since v4:
>> * Refactoring landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactoring landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactoring landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>>
>> ---
>>   security/landlock/limits.h   |  8 +++-
>>   security/landlock/ruleset.c  | 82 +++++++++++++++++++++++++++++++-----
>>   security/landlock/ruleset.h  | 34 +++++++++++++--
>>   security/landlock/syscalls.c | 45 +++++++++++---------
>>   4 files changed, 132 insertions(+), 37 deletions(-)
>>
>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>> index b54184ab9439..23694bf05cb7 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -22,6 +22,12 @@
>>   #define LANDLOCK_MASK_ACCESS_FS        ((LANDLOCK_LAST_ACCESS_FS << 
>> 1) - 1)
>>   #define LANDLOCK_NUM_ACCESS_FS        
>> __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>
>> -/* 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_MASK_SHIFT_NET        16
>> +
>> +#define LANDLOCK_RULE_TYPE_NUM        LANDLOCK_RULE_NET_SERVICE
>>
>> +/* clang-format on */
>>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index c4ed783d655b..ea9ecb3f471a 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -36,6 +36,7 @@ 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;
>> +    new_ruleset->root_net_port = RB_ROOT;
>>       new_ruleset->num_layers = num_layers;
>>       /*
>>        * hierarchy = NULL
>> @@ -46,17 +47,21 @@ static struct landlock_ruleset 
>> *create_ruleset(const u32 num_layers)
>>   }
>>
>>   struct landlock_ruleset *landlock_create_ruleset(
>> -        const access_mask_t access_mask)
>> +                    const access_mask_t access_mask_fs,
>> +                    const access_mask_t access_mask_net)
>>   {
>>       struct landlock_ruleset *new_ruleset;
>>
>>       /* Informs about useless ruleset. */
>> -    if (!access_mask)
>> +    if (!access_mask_fs && !access_mask_net)
>>           return ERR_PTR(-ENOMSG);
>>       new_ruleset = create_ruleset(1);
>> -    if (!IS_ERR(new_ruleset))
>> -        landlock_set_fs_access_mask(new_ruleset, access_mask, 0);
>> -
>> +    if (IS_ERR(new_ruleset))
>> +        return new_ruleset;
>> +    if (access_mask_fs)
>> +        landlock_set_fs_access_mask(new_ruleset, access_mask_fs, 0);
>> +    if (access_mask_net)
>> +        landlock_set_net_access_mask(new_ruleset, access_mask_net, 0);
>>       return new_ruleset;
>>   }
>>
>> @@ -94,9 +99,11 @@ static struct landlock_rule *create_rule(
>>           return ERR_PTR(-ENOMEM);
>>       RB_CLEAR_NODE(&new_rule->node);
>>
>> -    if (object_ptr) {
>> +    if (object_ptr && !object_data) {
>>           landlock_get_object(object_ptr);
>>           new_rule->object.ptr = object_ptr;
>> +    } else if (object_data && !object_ptr) {
>> +        new_rule->object.data = object_data;
>>       } else if (object_ptr && object_data) {
>>           WARN_ON_ONCE(1);
>>           return ERR_PTR(-EINVAL);
>> @@ -132,10 +139,12 @@ static void build_check_ruleset(void)
>>           .num_layers = ~0,
>>       };
>>       typeof(ruleset.access_masks[0]) fs_access_mask = ~0;
>> +    typeof(ruleset.access_masks[0]) net_access_mask = ~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(net_access_mask < LANDLOCK_MASK_ACCESS_NET);
>>   }
>>
>>   /**
>> @@ -183,6 +192,11 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>           object_data = (uintptr_t)object_ptr;
>>           root = &ruleset->root_inode;
>>           break;
>> +    case LANDLOCK_RULE_NET_SERVICE:
>> +        if (WARN_ON_ONCE(object_ptr))
>> +            return -EINVAL;
>> +        root = &ruleset->root_net_port;
>> +        break;
>>       default:
>>           WARN_ON_ONCE(1);
>>           return -EINVAL;
>> @@ -237,6 +251,16 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>                       &ruleset->root_inode);
>>               free_rule(this, rule_type);
>>               break;
>> +        case LANDLOCK_RULE_NET_SERVICE:
>> +            new_rule = create_rule(NULL, object_data,
>> +                           &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_net_port);
>> +            free_rule(this, rule_type);
>> +            break;
>>           }
>>           return 0;
>>       }
>> @@ -254,6 +278,15 @@ static int insert_rule(struct landlock_ruleset 
>> *const ruleset,
>>           rb_link_node(&new_rule->node, parent_node, walker_node);
>>           rb_insert_color(&new_rule->node, &ruleset->root_inode);
>>           break;
>> +    case LANDLOCK_RULE_NET_SERVICE:
>> +        new_rule = create_rule(NULL, object_data, 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_net_port);
>> +        ruleset->num_rules++;
>> +        break;
>>       }
>>       return 0;
>>   }
>> @@ -315,6 +348,9 @@ static int tree_merge(struct landlock_ruleset 
>> *const src,
>>       case LANDLOCK_RULE_PATH_BENEATH:
>>           src_root = &src->root_inode;
>>           break;
>> +    case LANDLOCK_RULE_NET_SERVICE:
>> +        src_root = &src->root_net_port;
>> +        break;
>>       default:
>>           return -EINVAL;
>>       }
>> @@ -341,6 +377,11 @@ static int tree_merge(struct landlock_ruleset 
>> *const src,
>>                         rule_type, &layers,
>>                         ARRAY_SIZE(layers));
>>               break;
>> +        case LANDLOCK_RULE_NET_SERVICE:
>> +            err = insert_rule(dst, NULL, walker_rule->object.data,
>> +                      rule_type, &layers,
>> +                      ARRAY_SIZE(layers));
>> +            break;
>>           }
>>           if (err)
>>               return err;
>> @@ -376,6 +417,10 @@ static int merge_ruleset(struct landlock_ruleset 
>> *const dst,
>>       err = tree_merge(src, dst, LANDLOCK_RULE_PATH_BENEATH);
>>       if (err)
>>           goto out_unlock;
>> +    /* Merges the @src network tree. */
>> +    err = tree_merge(src, dst, LANDLOCK_RULE_NET_SERVICE);
>> +    if (err)
>> +        goto out_unlock;
>>
>>   out_unlock:
>>       mutex_unlock(&src->lock);
>> @@ -395,6 +440,9 @@ static int tree_copy(struct landlock_ruleset 
>> *const parent,
>>       case LANDLOCK_RULE_PATH_BENEATH:
>>           parent_root = &parent->root_inode;
>>           break;
>> +    case LANDLOCK_RULE_NET_SERVICE:
>> +        parent_root = &parent->root_net_port;
>> +        break;
>>       default:
>>           return -EINVAL;
>>       }
>> @@ -407,6 +455,12 @@ static int tree_copy(struct landlock_ruleset 
>> *const parent,
>>                         rule_type, &walker_rule->layers,
>>                         walker_rule->num_layers);
>>               break;
>> +        case LANDLOCK_RULE_NET_SERVICE:
>> +            err = insert_rule(child, NULL,
>> +                      walker_rule->object.data, rule_type,
>> +                      &walker_rule->layers,
>> +                      walker_rule->num_layers);
>> +            break;
>>           }
>>           if (err)
>>               return err;
>> @@ -429,6 +483,10 @@ static int inherit_ruleset(struct 
>> landlock_ruleset *const parent,
>>
>>       /* Copies the @parent inode tree. */
>>       err = tree_copy(parent, child, LANDLOCK_RULE_PATH_BENEATH);
>> +    if (err)
>> +        goto out_unlock;
>> +    /* Copies the @parent inode tree. */
> 
> Not the inode tree this time.

   Right. Thanks for noticing.
> 
> 
>> +    err = tree_copy(parent, child, LANDLOCK_RULE_NET_SERVICE);
>>       if (err)
>>           goto out_unlock;
>>
>> @@ -463,9 +521,11 @@ static void free_ruleset(struct landlock_ruleset 
>> *const ruleset)
>>
>>       might_sleep();
>>       rbtree_postorder_for_each_entry_safe(freeme, next,
>> -                         &ruleset->root_inode,
>> -                         node)
>> +                         &ruleset->root_inode, node)
>>           free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH);
>> +    rbtree_postorder_for_each_entry_safe(freeme, next,
>> +                         &ruleset->root_net_port, node)
>> +        free_rule(freeme, LANDLOCK_RULE_NET_SERVICE);
>>       put_hierarchy(ruleset->hierarchy);
>>       kfree(ruleset);
>>   }
>> @@ -560,13 +620,13 @@ const struct landlock_rule *landlock_find_rule(
>>   {
>>       const struct rb_node *node;
>>
>> -    if (!object_data)
>> -        return NULL;
>> -
>>       switch (rule_type) {
>>       case LANDLOCK_RULE_PATH_BENEATH:
>>           node = ruleset->root_inode.rb_node;
>>           break;
>> +    case LANDLOCK_RULE_NET_SERVICE:
>> +        node = ruleset->root_net_port.rb_node;
>> +        break;
>>       default:
>>           WARN_ON_ONCE(1);
>>           return NULL;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index f3cd890d0348..916b30b31c06 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -102,6 +102,12 @@ struct landlock_ruleset {
>>        * tree is immutable until @usage reaches zero.
>>        */
>>       struct rb_root root_inode;
>> +    /**
>> +     * @root_net_port: Root of a red-black tree containing object nodes
>> +     * for 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;
>>       /**
>>        * @hierarchy: Enables hierarchy identification even when a parent
>>        * domain vanishes.  This is needed for the ptrace protection.
>> @@ -157,7 +163,8 @@ struct landlock_ruleset {
>>   };
>>
>>   struct landlock_ruleset *landlock_create_ruleset(
>> -        const access_mask_t access_mask);
>> +                    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);
>> @@ -183,11 +190,12 @@ static inline void landlock_get_ruleset(struct 
>> landlock_ruleset *const ruleset)
>>   }
>>
>>   /* A helper function to set a filesystem mask */
>> -static inline void landlock_set_fs_access_mask(struct 
>> landlock_ruleset *ruleset,
>> -                 const access_mask_t access_maskset,
>> +static inline void landlock_set_fs_access_mask(
>> +                 struct landlock_ruleset *ruleset,
>> +                 const access_mask_t access_mask_fs,
>>                    u16 mask_level)
>>   {
>> -    ruleset->access_masks[mask_level] = access_maskset;
>> +    ruleset->access_masks[mask_level] = access_mask_fs;
>>   }
>>
>>   /* A helper function to get a filesystem mask */
>> @@ -198,6 +206,24 @@ static inline u32 landlock_get_fs_access_mask(
>>       return (ruleset->access_masks[mask_level] & 
>> LANDLOCK_MASK_ACCESS_FS);
>>   }
>>
>> +/* A helper function to set a network mask */
>> +static inline void landlock_set_net_access_mask(
>> +                  struct landlock_ruleset *ruleset,
>> +                  const access_mask_t access_mask_net,
>> +                  u16 mask_level)
>> +{
>> +    ruleset->access_masks[mask_level] |= (access_mask_net <<
>> +                          LANDLOCK_MASK_SHIFT_NET);
>> +}
>> +
>> +/* A helper function to get a network mask */
>> +static inline u32 landlock_get_net_access_mask(
>> +                const struct landlock_ruleset *ruleset,
>> +                u16 mask_level)
>> +{
>> +    return (ruleset->access_masks[mask_level] >> 
>> LANDLOCK_MASK_SHIFT_NET);
>> +}
>> +
>>   access_mask_t get_handled_accesses(
>>           const struct landlock_ruleset *const domain,
>>           u16 rule_type, u16 num_access);
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index 31f9facec123..812541f4e155 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -189,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);
>>
>> @@ -275,21 +281,17 @@ static int get_path_from_fd(const s32 fd, struct 
>> path *const path)
>>       return err;
>>   }
>>
>> -static int add_rule_path_beneath(const int ruleset_fd, const void 
>> *const rule_attr)
>> +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>> +                 const void *const rule_attr)
>>   {
>>       struct landlock_path_beneath_attr path_beneath_attr;
>>       struct path path;
>> -    struct landlock_ruleset *ruleset;
>>       int res, err;
>> -
>> -    /* Gets and checks the ruleset. */
>> -    ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>> -    if (IS_ERR(ruleset))
>> -        return PTR_ERR(ruleset);
>> +    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));
>> +            sizeof(path_beneath_attr));
>>       if (res)
>>           return -EFAULT;
>>
>> @@ -298,32 +300,26 @@ static int add_rule_path_beneath(const int 
>> ruleset_fd, const void *const rule_at
>>        * are ignored in path walks.
>>        */
>>       if (!path_beneath_attr.allowed_access) {
>> -        err = -ENOMSG;
>> -        goto out_put_ruleset;
>> +        return -ENOMSG;
>>       }
>>       /*
>>        * 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;
>> -    }
>> +    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)
>> -        goto out_put_ruleset;
>> +        return err;
>>
>>       /* 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;
>>   }
>>
>> @@ -360,6 +356,7 @@ 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_ruleset *ruleset;
>>       int err;
>>
>>       if (!landlock_initialized)
>> @@ -369,14 +366,20 @@ SYSCALL_DEFINE4(landlock_add_rule,
>>       if (flags)
>>           return -EINVAL;
>>
>> +    /* Gets and checks the ruleset. */
>> +    ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>> +    if (IS_ERR(ruleset))
>> +        return PTR_ERR(ruleset);
> 
> This shouldn't be part of this patch.
> 
   I agree. I will move it into another patch.
> 
>> +
>>       switch (rule_type) {
>>       case LANDLOCK_RULE_PATH_BENEATH:
>> -        err = add_rule_path_beneath(ruleset_fd, rule_attr);
>> +        err = add_rule_path_beneath(ruleset, rule_attr);
>>           break;
>>       default:
>>           err = -EINVAL;
>>           break;
>>       }
>> +    landlock_put_ruleset(ruleset);
>>       return err;
>>   }
>>
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 08/15] landlock: TCP network hooks implementation
  2022-05-17  8:51   ` Mickaël Salaün
@ 2022-05-19 11:40     ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19 11:40 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:51 AM, Mickaël Salaün пишет:
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Support of socket_bind() and socket_connect() hooks.
>> Its possible to restrict binding and connecting of TCP
>> types of sockets to particular ports. Its just basic idea
>> how Landlock could support network confinement.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Split commit.
>> * Add SECURITY_NETWORK in config.
>> * Add IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration.
>> * Add hook_socket_bind and hook_socket_connect hooks.
>>
>> Changes since v4:
>> * Factors out CONFIG_INET into make file.
>> * Refactoring 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.
>> * Refactoring add_rule_net_service() and landlock_add_rule
>> syscall to support network rule inserting.
>> * Refactoring init_layer_masks() to support network rules.
>>
>> ---
>>   security/landlock/Kconfig    |   1 +
>>   security/landlock/Makefile   |   2 +
>>   security/landlock/net.c      | 159 +++++++++++++++++++++++++++++++++++
>>   security/landlock/net.h      |  25 ++++++
>>   security/landlock/ruleset.c  |  15 +++-
>>   security/landlock/setup.c    |   2 +
>>   security/landlock/syscalls.c |  63 ++++++++++++--
>>   7 files changed, 261 insertions(+), 6 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..9302e5891991
>> --- /dev/null
>> +++ b/security/landlock/net.c
>> @@ -0,0 +1,159 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright (C) 2022 Huawei Tech. Co., Ltd.
>> + */
>> +
>> +#include <linux/in.h>
>> +#include <linux/net.h>
>> +#include <linux/socket.h>
>> +#include <net/ipv6.h>
>> +
>> +#include "cred.h"
>> +#include "limits.h"
>> +#include "net.h"
>> +
>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>> +                 u16 port, u32 access_rights)
>> +{
>> +    int err;
>> +
>> +    /* Transforms relative access rights to absolute ones. */
>> +    access_rights |= LANDLOCK_MASK_ACCESS_NET &
>> +             ~landlock_get_net_access_mask(ruleset, 0);
>> +
>> +    BUILD_BUG_ON(sizeof(port) > sizeof(uintptr_t));
>> +    mutex_lock(&ruleset->lock);
>> +    err = landlock_insert_rule(ruleset, NULL, port,
>> +                access_rights, LANDLOCK_RULE_NET_SERVICE);
>> +    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;
>> +
>> +    if (WARN_ON_ONCE(!domain))
>> +        return 0;
>> +    if (WARN_ON_ONCE(domain->num_layers < 1))
>> +        return -EACCES;
>> +
>> +    rule = landlock_find_rule(domain, port,
>> +                    LANDLOCK_RULE_NET_SERVICE);
>> +
>> +    handled_access = init_layer_masks(domain, access_request,
>> +            &layer_masks, sizeof(layer_masks),
>> +            LANDLOCK_RULE_NET_SERVICE);
>> +    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:
> 
> Are you sure about that?
> 
> Please write a test for this case.
> 
   I checked the logic in Selinux LSM in a bind() hook and AF_UNSPEC
family is treated as AF_INET (only for INADDR_ANY address)  but we can 
discuss it.
> 
>> +    case AF_INET:
>> +    {
> 
> You don't need these braces (except if it is required by checkpatch.pl).
> 
   I got compilation error without braces:
...
error: a label can only be part of a statement and a declaration is not 
a statement
    67 |   const struct sockaddr_in *const sockaddr =
...
  so I added braces.
> 
>> +        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
>> +    }
> 
> You missed some part of my patch… We should not get the port for a 
> protocol we don't know, hence the WARN_ON_ONCE.
> 
  Sorry. I manually updated the code and missed that. I will fix it.
> 
>> +    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;
>> +
>> +    /* Get port value in host byte order */
> 
> I moved/removed this in my patch against v4 for a reason. Please, ask if 
> you don't understand or if you don't agree with something I said.
> 
   Sorry. I updated your changed manually and missed that.
   Will be fixed.
> 
>> +    switch (address->sa_family) {
>> +    case AF_UNSPEC:
> 
> Is this correct?

Actually I did'not find any issue with bind() using AF_UNSPEC family.
(instead of connect() one and we came to an agreement that it must be 
banned). So I checked athoner LSMs like Selinux and found out that
it treats bind() hook with AF_UNSPEC family as with AF_INET one.
But Selinux allows AF_UNSPEC family only for INADDR_ANY address and
I cant understand the reason now. So I let AF_UNSPEC family bind()
hook for any address, but we can discuss it. What do you think?
> 
> Please write a test for this case.
> 
  I have one. Please check it in connect_afunspec_with_restictions
  test:

...
  /* Enforces the ruleset. */
	enforce_ruleset(_metadata, ruleset_fd_1);

	/* Creates a server socket 1 */
	sockfd = create_socket(_metadata, false, false);
	ASSERT_LE(0, sockfd);

	/* Binds the socket 1 to address with port[0] with AF_UNSPEC
            family */
	self->addr4[0].sin_family = AF_UNSPEC;
	ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0],
                           sizeof(self->addr4[0])));

	/* Makes connection to socket with port[0] */
	ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
			     sizeof(self->addr4[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));
...
>> +    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;
>> +
>> +    /* Get port value in host byte order */
>> +    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..da5ce8fa04cc
>> --- /dev/null
>> +++ b/security/landlock/net.h
>> @@ -0,0 +1,25 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright (C) 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,
>> +                u16 port, u32 access_hierarchy);
>> +#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/ruleset.c b/security/landlock/ruleset.c
>> index ea9ecb3f471a..317cf98890f6 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -671,7 +671,7 @@ access_mask_t get_handled_accesses(
>>           }
>>           break;
>>       default:
>> -        break;
>> +        return 0;
> 
> Why?

   You are right - this is useless code, cause anyway 0 is retured in 
default rule_type. Will be fixed.
> 
> 
>>       }
>>       return access_dom;
>>   }
>> @@ -763,6 +763,19 @@ access_mask_t init_layer_masks(const struct 
>> landlock_ruleset *const domain,
>>                   }
>>               }
>>               break;
>> +        case LANDLOCK_RULE_NET_SERVICE:
>> +            for_each_set_bit(access_bit, &access_req,
>> +                    LANDLOCK_NUM_ACCESS_NET) {
>> +                if (landlock_get_net_access_mask(domain,
>> +                                 layer_level) &
>> +                        BIT_ULL(access_bit)) {
>> +                    (*layer_masks)[access_bit] |=
>> +                        BIT_ULL(layer_level);
>> +                    handled_accesses |=
>> +                               BIT_ULL(access_bit);
>> +                }
>> +            }
>> +            break;
>>           default:
>>               return 0;
>>           }
>> diff --git a/security/landlock/setup.c b/security/landlock/setup.c
>> index f8e8e980454c..8059dc0b47d3 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;
>>
>> @@ -28,6 +29,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 812541f4e155..9454c6361011 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 */
>> @@ -299,9 +306,9 @@ static int add_rule_path_beneath(struct 
>> landlock_ruleset *const ruleset,
>>        * Informs about useless rule: empty allowed_access (i.e. deny 
>> rules)
>>        * are ignored in path walks.
>>        */
>> -    if (!path_beneath_attr.allowed_access) {
>> +    if (!path_beneath_attr.allowed_access)
> 
> Why?

  Unnecessary braces for single statement blocks.
  I got a warning by scripts/checkpatch.pl:

   WARNING: braces {} are not necessary for single statement blocks
#309: FILE: security/landlock/syscalls.c:309:
+       if (!path_beneath_attr.allowed_access) {
+               return -ENOMSG;
+       }

> 
> 
>>           return -ENOMSG;
>> -    }
>> +
>>       /*
>>        * Checks that allowed_access matches the @ruleset constraints
>>        * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>> @@ -323,13 +330,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 *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.
>> @@ -340,6 +388,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 rule's
>>    *   accesses);
>> @@ -375,6 +425,9 @@ SYSCALL_DEFINE4(landlock_add_rule,
>>       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] 56+ messages in thread

* Re: [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks
  2022-05-16 21:11   ` Mickaël Salaün
@ 2022-05-19 12:10     ` Konstantin Meskhidze
  2022-05-19 14:29       ` Mickaël Salaün
  0 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19 12:10 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 12:11 AM, Mickaël Salaün пишет:
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Adds selftests for bind socket action.
>> The first is with no landlock restrictions:
>>      - bind_no_restrictions_ip4;
>>      - bind_no_restrictions_ip6;
>> The second ones is with mixed landlock rules:
>>      - bind_with_restrictions_ip4;
>>      - bind_with_restrictions_ip6;
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Split commit.
>> * Add helper create_socket.
>> * Add FIXTURE_SETUP.
>>
>> Changes since v4:
>> * Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4
>> and struct sockaddr_in addr6 in FIXTURE.
>> * Refactoring FIXTURE_SETUP:
>>      - initializing self->port, self->addr4 and self->addr6.
>>      - adding network namespace.
>> * Refactoring code with self->port, self->addr4 and
>> self->addr6 variables.
>> * Adds selftests for IP6 family:
>>      - bind_no_restrictions_ip6.
>>      - bind_with_restrictions_ip6.
>> * Refactoring selftests/landlock/config
>> * Moves enforce_ruleset() into common.h
>>
>> ---
>>   tools/testing/selftests/landlock/common.h   |   9 +
>>   tools/testing/selftests/landlock/config     |   5 +-
>>   tools/testing/selftests/landlock/fs_test.c  |  10 -
>>   tools/testing/selftests/landlock/net_test.c | 237 ++++++++++++++++++++
>>   4 files changed, 250 insertions(+), 11 deletions(-)
>>   create mode 100644 tools/testing/selftests/landlock/net_test.c
>>
>> diff --git a/tools/testing/selftests/landlock/common.h 
>> b/tools/testing/selftests/landlock/common.h
>> index 7ba18eb23783..c5381e641dfd 100644
>> --- a/tools/testing/selftests/landlock/common.h
>> +++ b/tools/testing/selftests/landlock/common.h
>> @@ -102,6 +102,15 @@ static inline int landlock_restrict_self(const 
>> int ruleset_fd,
>>   }
>>   #endif
>>
>> +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));
>> +    }
>> +}
>> +
> 
> Please create a commit which moves all the needed code for all network 
> tests. I think there is only this helper though.

   Ok. I will create one additional commit for moving this helper.
   But after I have moved the helper to common.h, I got warnings while 
compiling seltests where I don't use the one (base_test and ptrace_test)

> 
> 
>>   static void _init_caps(struct __test_metadata *const _metadata, bool 
>> drop_all)
>>   {
>>       cap_t cap_p;
>> diff --git a/tools/testing/selftests/landlock/config 
>> b/tools/testing/selftests/landlock/config
>> index 0f0a65287bac..b56f3274d3f5 100644
>> --- a/tools/testing/selftests/landlock/config
>> +++ b/tools/testing/selftests/landlock/config
>> @@ -1,7 +1,10 @@
>> +CONFIG_INET=y
>> +CONFIG_IPV6=y
>> +CONFIG_NET=y
>>   CONFIG_OVERLAY_FS=y
>>   CONFIG_SECURITY_LANDLOCK=y
>>   CONFIG_SECURITY_PATH=y
>>   CONFIG_SECURITY=y
>>   CONFIG_SHMEM=y
>>   CONFIG_TMPFS_XATTR=y
>> -CONFIG_TMPFS=y
>> +CONFIG_TMPFS=y
>> \ No newline at end of file
> 
> You add whitespace changes.
> 
   OK. I will fix it. Thank you.
> 
>> diff --git a/tools/testing/selftests/landlock/fs_test.c 
>> b/tools/testing/selftests/landlock/fs_test.c
>> index 21a2ce8fa739..036dd6f8f9ea 100644
>> --- a/tools/testing/selftests/landlock/fs_test.c
>> +++ b/tools/testing/selftests/landlock/fs_test.c
>> @@ -551,16 +551,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[] = {
>> diff --git a/tools/testing/selftests/landlock/net_test.c 
>> b/tools/testing/selftests/landlock/net_test.c
>> new file mode 100644
>> index 000000000000..478ef2eff559
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -0,0 +1,237 @@
>> +// 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
> 
> "Number of pending connection queues to be hold." maybe? This is not use 
> in this patch so it shouldn't be added by this patch.
> 
   You are right. I will move it in the patch where listen() function 
appear. Thank you for noticing.
> 
>> +
>> +static int create_socket(struct __test_metadata *const _metadata,
>> +            bool ip6, bool reuse_addr)
> 
> This helper is good and I think you can improve it by leveraging test 
> variants. You could even factor out all the ipv4/ipv6 tests thanks to 
> new helpers such as bind_variant() and connect_variant(). No need to add 
> _metadata to those though. This would avoid duplicating all ipv4/ipv6 
> tests and even simplifying bind() and connect() calls. Something like this:
> 
> // rename "socket_test" to "socket" (no need to duplicate "test")
> 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 socket_variant(const 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);
> }
> 
> socket_variant(variant, SOCK_STREAM);
> // this could be used to create UDP sockets too
> 
> 
> static int bind_variant(const FIXTURE_VARIANT(socket) *const variant, 
> const int sockfd, const FIXTURE_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]));
> }
> 
> bind_variant(variant, sockfd, self, 0);
> 
  Ok. Thank you for this suggestion.
> 
>> +{
>> +        int sockfd;
>> +        int one = 1;
>> +
>> +        if (ip6)
>> +            sockfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
>> +        else
>> +            sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>> +
>> +        ASSERT_LE(0, sockfd);
>> +        /* Allows to reuse of local address */
>> +        if (reuse_addr)
>> +            ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET,
>> +                    SO_REUSEADDR, &one, sizeof(one)));
> 
> This reuse_addr part is not used in this patch and I think it would 
> simplify this helper to not add reuse_addr but to explicitely call 
> setsockopt() when required. This also enables to get rid of _metadata in 
> this helper.
> 
   Yep. You are right. I will fix it.
> 
>> +        return sockfd;
>> +}
>> +
>> +FIXTURE(socket_test) {
>> +    uint port[MAX_SOCKET_NUM];
>> +    struct sockaddr_in addr4[MAX_SOCKET_NUM];
>> +    struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
>> +};
>> +
>> +FIXTURE_SETUP(socket_test)
>> +{
>> +    int i;
>> +    /* Creates IP4 socket addresses */
>> +    for (i = 0; i < MAX_SOCKET_NUM; i++) {
> 
> Nice!
> 
>> +        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 = htonl(INADDR_ANY);
> 
> Could you use the local addr (127.0.0.1) instead?

   Why cant I use INADDR_ANY here?
> 
>> +        memset(&(self->addr4[i].sin_zero), '\0', 8);
>> +    }
>> +
>> +    /* Creates IP6 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]);
>> +        self->addr6[i].sin6_addr = in6addr_any;
> 
> ditto

   Why cant I use in6addr_any here?

> 
>> +    }
>> +
>> +    set_cap(_metadata, CAP_SYS_ADMIN);
>> +    ASSERT_EQ(0, unshare(CLONE_NEWNET));
>> +    ASSERT_EQ(0, system("ip link set dev lo up"));
> 
> If this is really required, could you avoid calling system() but set up 
> the network in C? You can strace it to see what is going on underneath.
> 
  I did check. It's a lot of code to be run under the hood (more than 
one line) and it will just will complicate the test so I suggest to 
leave just ONE line of code here.
> 
>> +    clear_cap(_metadata, CAP_SYS_ADMIN);
>> +}
>> +
>> +FIXTURE_TEARDOWN(socket_test)
>> +{ }
>> +
>> +TEST_F_FORK(socket_test, bind_no_restrictions_ip4) {
>> +
>> +    int sockfd;
>> +
>> +    sockfd = create_socket(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +
>> +    /* Binds a socket to port[0] */
> 
> This comment is not very useful in this context considering the below 
> line. It will be even more clear with the bind_variant() call.
> 
  Ok. I will fix it.
> 
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>> sizeof(self->addr4[0])));
>> +
>> +    ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>> +TEST_F_FORK(socket_test, bind_no_restrictions_ip6) {
>> +
>> +    int sockfd;
>> +
>> +    sockfd = create_socket(_metadata, true, false);
>> +    ASSERT_LE(0, sockfd);
>> +
>> +    /* Binds a socket to port[0] */
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], 
>> sizeof(self->addr6[0])));
>> +
>> +    ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>> +TEST_F_FORK(socket_test, bind_with_restrictions_ip4) {
>> +
>> +    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. */
> 
> This comment is useful though because the below call is more complex.
> 
   So I can leave it as it's, cant I?
> 
>> +    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(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +    /* Binds a socket to port[0] */
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>> sizeof(self->addr4[0])));
>> +
>> +    /* Close bounded socket*/
>> +    ASSERT_EQ(0, close(sockfd));
>> +
>> +    sockfd = create_socket(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +    /* Binds a socket to port[1] */
>> +    ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[1], 
>> sizeof(self->addr4[1])));
>> +    ASSERT_EQ(EACCES, errno);
>> +
>> +    sockfd = create_socket(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +    /* Binds a socket to port[2] */
>> +    ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[2], 
>> sizeof(self->addr4[2])));
>> +    ASSERT_EQ(EACCES, errno);
>> +}
>> +
>> +TEST_F_FORK(socket_test, bind_with_restrictions_ip6) {
>> +
>> +    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(_metadata, true, false);
>> +    ASSERT_LE(0, sockfd);
>> +    /* Binds a socket to port[0] */
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], 
>> sizeof(self->addr6[0])));
>> +
>> +    /* Close bounded socket*/
>> +    ASSERT_EQ(0, close(sockfd));
>> +
>> +    sockfd = create_socket(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +    /* Binds a socket to port[1] */
>> +    ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[1], 
>> sizeof(self->addr6[1])));
>> +    ASSERT_EQ(EACCES, errno);
>> +
>> +    sockfd = create_socket(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +    /* Binds a socket to port[2] */
>> +    ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[2], 
>> sizeof(self->addr6[2])));
>> +    ASSERT_EQ(EACCES, errno);
>> +}
>> +TEST_HARNESS_MAIN
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 12/15] seltests/landlock: rules overlapping test
  2022-05-16 17:41   ` Mickaël Salaün
@ 2022-05-19 12:24     ` Konstantin Meskhidze
  2022-05-19 15:04       ` Mickaël Salaün
  0 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19 12:24 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/16/2022 8:41 PM, Mickaël Salaün пишет:
> Please fix these kind of subjects (selftests). I'd also like the subject 
> description to (quickly) describe what is done (with a verb), to start 
> with a capital (like a title), and to contain "network", something like 
> this:
> selftests/landlock: Add test for overlapping network rules
> 
> This is a good test though.
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> This patch adds overlapping rules for one port.
>> First rule adds just bind() access right for a port.
>> The second one adds both bind() and connect()
>> access rights for the same port.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Add ruleset_overlap test.
>>
>> Changes since v4:
>> * Refactoring code with self->port, self->addr4 variables.
>>
>> ---
>>   tools/testing/selftests/landlock/net_test.c | 51 +++++++++++++++++++++
>>   1 file changed, 51 insertions(+)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c 
>> b/tools/testing/selftests/landlock/net_test.c
>> index bf8e49466d1d..1d8c9dfdbd48 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -677,4 +677,55 @@ TEST_F_FORK(socket_test, 
>> connect_afunspec_with_restictions) {
>>       ASSERT_EQ(1, WIFEXITED(status));
>>       ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
>>   }
>> +
>> +TEST_F_FORK(socket_test, ruleset_overlap) {
> 
> Please run clang-format-14 on all files (and all commits).
> 
   Yep. I already have updated clang-format executable on my Ubuntu and
setup Vscode to use .clang-format file.
>> +
>> +    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,
>> +
>> +        .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],
>> +    };
>> +
>> +    const 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 */
> 
> Please ends this kind of comments with a final dot (all files/commits).
> 
   Ok. I will.
>> +    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(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +
>> +    /* Binds the socket to address with port[0] */
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>> sizeof(self->addr4[0])));
>> +
>> +    /* Makes connection to socket with port[0] */
>> +    ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
> 
> Can you please get rid of this (struct sockaddr *) type casting please 
> (without compiler warning)?
> 
   Do you have a warning here? Cause I don't.
>> +                           sizeof(self->addr4[0])));
> 
> Here, you can enforce a new ruleset with net_service_1 and check that 
> bind() is still allowed but not connect().
> 
  Ok. Thank you for advice.
>> +
>> +    /* Closes socket */
>> +    ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>>   TEST_HARNESS_MAIN
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-17  8:55   ` Mickaël Salaün
@ 2022-05-19 12:31     ` Konstantin Meskhidze
  2022-05-19 15:00       ` Mickaël Salaün
  2022-05-19 15:02       ` Mickaël Salaün
  0 siblings, 2 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19 12:31 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 11:55 AM, Mickaël Salaün пишет:
> I guess these tests would also work with IPv6. You can then use the 
> "alternative" tests I explained.
> 
   Do you mean adding new helpers such as bind_variant() and 
connect_variant()??
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Adds two selftests for connect() action with
>> AF_UNSPEC family flag.
>> The one is with no landlock restrictions
>> allows to disconnect already conneted socket
>> with connect(..., AF_UNSPEC, ...):
>>      - connect_afunspec_no_restictions;
>> The second one refuses landlocked process
>> to disconnect already connected socket:
>>      - connect_afunspec_with_restictions;
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Add connect_afunspec_no_restictions test.
>> * Add connect_afunspec_with_restictions test.
>>
>> Changes since v4:
>> * Refactoring code with self->port, self->addr4 variables.
>> * Adds bind() hook check for with AF_UNSPEC family.
>>
>> ---
>>   tools/testing/selftests/landlock/net_test.c | 121 ++++++++++++++++++++
>>   1 file changed, 121 insertions(+)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c 
>> b/tools/testing/selftests/landlock/net_test.c
>> index cf914d311eb3..bf8e49466d1d 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -449,6 +449,7 @@ TEST_F_FORK(socket_test, 
>> connect_with_restrictions_ip6) {
>>       int new_fd;
>>       int sockfd_1, sockfd_2;
>>       pid_t child_1, child_2;
>> +
>>       int status;
>>
>>       struct landlock_ruleset_attr ruleset_attr = {
>> @@ -467,10 +468,12 @@ TEST_F_FORK(socket_test, 
>> connect_with_restrictions_ip6) {
>>
>>       const int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>>               sizeof(ruleset_attr), 0);
>> +
> 
> Please no…
> 
  Sorry for that. I will apply clang-format-14.
> 
>>       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,
>> +
> 
> ditto

   Ditto. Will be fixed with clang-format.
> 
>>                   &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,
>> @@ -480,6 +483,7 @@ TEST_F_FORK(socket_test, 
>> connect_with_restrictions_ip6) {
>>       enforce_ruleset(_metadata, ruleset_fd);
>>
>>       /* Creates a server socket 1 */
>> +
>>       sockfd_1 = create_socket(_metadata, true, false);
>>       ASSERT_LE(0, sockfd_1);
>>
>> @@ -556,4 +560,121 @@ TEST_F_FORK(socket_test, 
>> connect_with_restrictions_ip6) {
>>       ASSERT_EQ(1, WIFEXITED(status));
>>       ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
>>   }
>> +
>> +TEST_F_FORK(socket_test, connect_afunspec_no_restictions) {
>> +
>> +    int sockfd;
>> +    pid_t child;
>> +    int status;
>> +
>> +    /* Creates a server socket 1 */
>> +    sockfd = create_socket(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +
>> +    /* Binds the socket 1 to address with port[0] with AF_UNSPEC 
>> family */
>> +    self->addr4[0].sin_family = AF_UNSPEC;
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>> sizeof(self->addr4[0])));
>> +
>> +    /* Makes connection to socket with port[0] */
>> +    ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
>> +                           sizeof(self->addr4[0])));
>> +
>> +    child = fork();
>> +    ASSERT_LE(0, child);
>> +    if (child == 0) {
>> +        struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC};
>> +
>> +        /* 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_test, connect_afunspec_with_restictions) {
>> +
>> +    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(_metadata, false, false);
>> +    ASSERT_LE(0, sockfd);
>> +
>> +    /* Binds the socket 1 to address with port[0] with AF_UNSPEC 
>> family */
>> +    self->addr4[0].sin_family = AF_UNSPEC;
>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>> sizeof(self->addr4[0])));
>> +
>> +    /* Makes connection to socket with port[0] */
>> +    ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
>> +                           sizeof(self->addr4[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) {
>> +        struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC};
>> +
>> +        /* 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_HARNESS_MAIN
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 15/15] samples/landlock: adds network demo
  2022-05-17  9:19   ` Mickaël Salaün
@ 2022-05-19 13:33     ` Konstantin Meskhidze
  2022-05-19 15:09       ` Mickaël Salaün
  0 siblings, 1 reply; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-19 13:33 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/17/2022 12:19 PM, Mickaël Salaün пишет:
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> This commit adds network demo. It's possible to
>> allow a sandoxer to bind/connect to a list of
>> particular ports restricting networks actions to
>> the rest of ports.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> 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.
>> * Refactoring main() to support network sandboxing.
>>
>> ---
>>   samples/landlock/sandboxer.c | 105 +++++++++++++++++++++++++++++++----
>>   security/landlock/ruleset.h  |   4 +-
>>   2 files changed, 95 insertions(+), 14 deletions(-)
>>
>> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
>> index 3e404e51ec64..4006c42eec1c 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 ( \
>> @@ -80,7 +96,7 @@ 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,
>> +static int populate_ruleset_fs(const char *const env_var, const int 
>> ruleset_fd,
>>                   const __u64 allowed_access)
>>   {
>>       int num_paths, i, ret = 1;
>> @@ -142,6 +158,49 @@ 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) {
>> +        /* Prevents users to forget a setting. */
>> +        fprintf(stderr, "Missing environment variable %s\n", env_var);
>> +        return 1;
> 
> I think network ports should be optional to be able to test without that 
> (and not break compatibility). You can pass &ruleset_attr as argument to 
> update it accordingly:
> - without environment variable: no network restriction;
> - with empty environment variable: all connect (or bind) denied;
> - otherwise: only allow the listed ports.
> 
   Great. That makes sense. Cause anyway fs restrictions are major ones.
> 
>> +    }
>> +    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 ( \
>> @@ -173,19 +232,24 @@ int main(const int argc, char *const argv[], 
>> char *const *const envp)
>>       char *const *cmd_argv;
>>       int ruleset_fd, abi;
>>       __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 = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +                    LANDLOCK_ACCESS_NET_CONNECT_TCP;
>>       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);
>> @@ -193,11 +257,19 @@ int main(const int argc, char *const argv[], 
>> char *const *const envp)
>>               "* %s: list of paths allowed to be used in a read-write 
>> way.\n",
>>               ENV_FS_RW_NAME);
>>           fprintf(stderr,
>> -            "\nexample:\n"
>> +            "* %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);
> 
> This is good and will be better with clang-format. ;)

   Yep. I will fix it. Thanks.
> 
>> +        fprintf(stderr, "\nexample:\n"
>>               "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
>>               "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
>> +            "%s=\"15000:16000\" "
> 
> Bind ports example should reference unprivileged ports such as "9418" 
> (git, not well-known but OK).
> 
  Ok. I will change it
> 
>> +            "%s=\"10000:12000\" "
> 
> Connect ports example should reference well-known ports such as "80:443".
> 
   Ditto.
>>               "%s bash -i\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]);
>>           return 1;
>>       }
>>
>> @@ -234,16 +306,25 @@ int main(const int argc, char *const argv[], 
>> char *const *const envp)
>>
>>       ruleset_fd =
>>           landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 
>> 0);
>> +
> 
> Why?

   Oh. Sorry. My mistake. I will fix it as it was.
> 
> 
>>       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;
>> -    }
> 
> Why? I know that checkpatch.pl prints a warning for that but I 
> delibirately chooe to use curly braces even for "if" statements with one 
> line because it is safer. This code may be copied/pasted and I'd like 
> others to avoid introducing goto-fail-like issues.
> 

  It was done just to reduce the number of checkpatch.pl warnings.
  If you want it to be formated in your way I will fix it.
> 
> 
>> -    if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
>> +
>> +    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))
> 
> So please use curly braces here too.

   Ok. No problems.
> 
>> +        goto err_close_ruleset;
>> +
>> +    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)) {
>>           perror("Failed to restrict privileges");
>>           goto err_close_ruleset;
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index 916b30b31c06..e1ff40f238a6 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -19,7 +19,7 @@
>>   #include "limits.h"
>>   #include "object.h"
>>
>> -typedef u16 access_mask_t;
>> +typedef u32 access_mask_t;
> 
> What‽

   You are right. I will move this changes to another commit, related 
the kernel updates. I might have forgotten to rebase this change and 
left it in sandboxer patch. Thank you..
> 
> 
>>
>>   /* Makes sure all filesystem access rights can be stored. */
>>   static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>> @@ -157,7 +157,7 @@ struct landlock_ruleset {
>>                * layers are set once and never changed for the
>>                * lifetime of the ruleset.
>>                */
>> -            u32 access_masks[];
>> +            access_mask_t access_masks[];
>>           };
>>       };
>>   };
>> -- 
>> 2.25.1
>>
> .

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

* Re: [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks
  2022-05-19 12:10     ` Konstantin Meskhidze
@ 2022-05-19 14:29       ` Mickaël Salaün
  2022-05-24  8:34         ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 14:29 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 19/05/2022 14:10, Konstantin Meskhidze wrote:
> 
> 
> 5/17/2022 12:11 AM, Mickaël Salaün пишет:
>>
>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>> Adds selftests for bind socket action.
>>> The first is with no landlock restrictions:
>>>      - bind_no_restrictions_ip4;
>>>      - bind_no_restrictions_ip6;
>>> The second ones is with mixed landlock rules:
>>>      - bind_with_restrictions_ip4;
>>>      - bind_with_restrictions_ip6;
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v3:
>>> * Split commit.
>>> * Add helper create_socket.
>>> * Add FIXTURE_SETUP.
>>>
>>> Changes since v4:
>>> * Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4
>>> and struct sockaddr_in addr6 in FIXTURE.
>>> * Refactoring FIXTURE_SETUP:
>>>      - initializing self->port, self->addr4 and self->addr6.
>>>      - adding network namespace.
>>> * Refactoring code with self->port, self->addr4 and
>>> self->addr6 variables.
>>> * Adds selftests for IP6 family:
>>>      - bind_no_restrictions_ip6.
>>>      - bind_with_restrictions_ip6.
>>> * Refactoring selftests/landlock/config
>>> * Moves enforce_ruleset() into common.h
>>>
>>> ---
>>>   tools/testing/selftests/landlock/common.h   |   9 +
>>>   tools/testing/selftests/landlock/config     |   5 +-
>>>   tools/testing/selftests/landlock/fs_test.c  |  10 -
>>>   tools/testing/selftests/landlock/net_test.c | 237 ++++++++++++++++++++
>>>   4 files changed, 250 insertions(+), 11 deletions(-)
>>>   create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>
>>> diff --git a/tools/testing/selftests/landlock/common.h 
>>> b/tools/testing/selftests/landlock/common.h
>>> index 7ba18eb23783..c5381e641dfd 100644
>>> --- a/tools/testing/selftests/landlock/common.h
>>> +++ b/tools/testing/selftests/landlock/common.h
>>> @@ -102,6 +102,15 @@ static inline int landlock_restrict_self(const 
>>> int ruleset_fd,
>>>   }
>>>   #endif
>>>
>>> +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));
>>> +    }
>>> +}
>>> +
>>
>> Please create a commit which moves all the needed code for all network 
>> tests. I think there is only this helper though.
> 
>    Ok. I will create one additional commit for moving this helper.
>    But after I have moved the helper to common.h, I got warnings while 
> compiling seltests where I don't use the one (base_test and ptrace_test)

Move it after clear_cap() and use the same attributes.

[...]

 >>> diff --git a/tools/testing/selftests/landlock/config
 >>> b/tools/testing/selftests/landlock/config
 >>> index 0f0a65287bac..b56f3274d3f5 100644
 >>> --- a/tools/testing/selftests/landlock/config
 >>> +++ b/tools/testing/selftests/landlock/config
 >>> @@ -1,7 +1,10 @@
 >>> +CONFIG_INET=y
 >>> +CONFIG_IPV6=y
 >>> +CONFIG_NET=y
 >>>   CONFIG_OVERLAY_FS=y
 >>>   CONFIG_SECURITY_LANDLOCK=y
 >>>   CONFIG_SECURITY_PATH=y
 >>>   CONFIG_SECURITY=y
 >>>   CONFIG_SHMEM=y
 >>>   CONFIG_TMPFS_XATTR=y
 >>> -CONFIG_TMPFS=y
 >>> +CONFIG_TMPFS=y
 >>> \ No newline at end of file

You also need to add CONFIG_NET_NS.

[...]

>>
>>> +        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 = htonl(INADDR_ANY);
>>
>> Could you use the local addr (127.0.0.1) instead?
> 
>    Why cant I use INADDR_ANY here?

You can, but it is cleaner to bind to a specified address (i.e. you 
control where a connection come from), and I guess this variable/address 
could be used to establish connections as well.

>>
>>> +        memset(&(self->addr4[i].sin_zero), '\0', 8);
>>> +    }
>>> +
>>> +    /* Creates IP6 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]);
>>> +        self->addr6[i].sin6_addr = in6addr_any;
>>
>> ditto
> 
>    Why cant I use in6addr_any here?

Same as for IPV4.

> 
>>
>>> +    }
>>> +
>>> +    set_cap(_metadata, CAP_SYS_ADMIN);
>>> +    ASSERT_EQ(0, unshare(CLONE_NEWNET));
>>> +    ASSERT_EQ(0, system("ip link set dev lo up"));
>>
>> If this is really required, could you avoid calling system() but set 
>> up the network in C? You can strace it to see what is going on 
>> underneath.
>>
>   I did check. It's a lot of code to be run under the hood (more than 
> one line) and it will just will complicate the test so I suggest to 
> leave just ONE line of code here.

OK


>>
>>> +    clear_cap(_metadata, CAP_SYS_ADMIN);
>>> +}
>>> +
>>> +FIXTURE_TEARDOWN(socket_test)
>>> +{ }
>>> +
>>> +TEST_F_FORK(socket_test, bind_no_restrictions_ip4) {
>>> +
>>> +    int sockfd;
>>> +
>>> +    sockfd = create_socket(_metadata, false, false);
>>> +    ASSERT_LE(0, sockfd);
>>> +
>>> +    /* Binds a socket to port[0] */
>>
>> This comment is not very useful in this context considering the below 
>> line. It will be even more clear with the bind_variant() call.
>>
>   Ok. I will fix it.
>>
>>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>>> sizeof(self->addr4[0])));
>>> +
>>> +    ASSERT_EQ(0, close(sockfd));
>>> +}
>>> +
>>> +TEST_F_FORK(socket_test, bind_no_restrictions_ip6) {
>>> +
>>> +    int sockfd;
>>> +
>>> +    sockfd = create_socket(_metadata, true, false);
>>> +    ASSERT_LE(0, sockfd);
>>> +
>>> +    /* Binds a socket to port[0] */
>>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], 
>>> sizeof(self->addr6[0])));
>>> +
>>> +    ASSERT_EQ(0, close(sockfd));
>>> +}
>>> +
>>> +TEST_F_FORK(socket_test, bind_with_restrictions_ip4) {
>>> +
>>> +    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. */
>>
>> This comment is useful though because the below call is more complex.
>>
>    So I can leave it as it's, cant I?

Yes, keep it, I'd just like a fair amount of useful comments. ;)

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

* Re: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-19  9:23     ` Konstantin Meskhidze
@ 2022-05-19 14:37       ` Mickaël Salaün
  2022-05-24  8:35         ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 14:37 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



On 19/05/2022 11:23, Konstantin Meskhidze wrote:
> 
> 
> 5/17/2022 11:04 AM, Mickaël Salaün пишет:
>> You can rename the subject to "landlock: Refactor landlock_add_rule()"
>>
>>
>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>> Landlock_add_rule syscall was refactored to support new
>>> rule types in future Landlock versions. Add_rule_path_beneath()
>>
>> nit: add_rule_path_beneath(), not Add_rule_path_beneath()
>>
>    Ok. Thanks. Will be renamed.
> 
>>> helper was added to support current filesystem rules. It is called
>>> by the switch case.
>>
>> You can rephrase (all commit messages) in the present form:
>>
>> Refactor the landlock_add_rule() syscall with add_rule_path_beneath() 
>> to support new…
>>
>> Refactor the landlock_add_rule() syscall to easily support for a new 
>> rule type in a following commit. The new add_rule_path_beneath() 
>> helper supports current filesystem rules.
>>
>    Ok. I will fix it.
>>
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v3:
>>> * Split commit.
>>> * Refactoring landlock_add_rule syscall.
>>>
>>> Changes since v4:
>>> * Refactoring add_rule_path_beneath() and landlock_add_rule() functions
>>> to optimize code usage.
>>> * Refactoring base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
>>> rule type in landlock_add_rule() call.
>>>
>>> ---
>>>   security/landlock/syscalls.c                 | 105 ++++++++++---------
>>>   tools/testing/selftests/landlock/base_test.c |   4 +-
>>>   2 files changed, 59 insertions(+), 50 deletions(-)
>>>
>>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>>> index 1db799d1a50b..412ced6c512f 100644
>>> --- a/security/landlock/syscalls.c
>>> +++ b/security/landlock/syscalls.c
>>> @@ -274,67 +274,23 @@ static int get_path_from_fd(const s32 fd, 
>>> struct path *const path)
>>>       return err;
>>>   }
>>>
>>> -/**
>>> - * 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_attr: Pointer to a rule (only of type &struct
>>> - *             landlock_path_beneath_attr for now).
>>> - * @flags: Must be 0.
>>> - *
>>> - * This system call enables to define a new rule and add it to an 
>>> existing
>>> - * ruleset.
>>> - *
>>> - * Possible returned errors are:
>>> - *
>>> - * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at 
>>> boot time;
>>> - * - 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);
>>> - * - ENOMSG: Empty accesses (e.g. 
>>> &landlock_path_beneath_attr.allowed_access);
>>> - * - EBADF: @ruleset_fd is not a file descriptor for the current 
>>> thread, or a
>>> - *   member of @rule_attr is not a file descriptor as expected;
>>> - * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a 
>>> member of
>>> - *   @rule_attr is not the expected file descriptor type;
>>> - * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
>>> - * - EFAULT: @rule_attr inconsistency.
>>> - */
>>> -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)
>>> +static int add_rule_path_beneath(const int ruleset_fd, const void 
>>> *const rule_attr)
>>>   {
>>>       struct landlock_path_beneath_attr path_beneath_attr;
>>>       struct path path;
>>>       struct landlock_ruleset *ruleset;
>>>       int res, err;
>>>
>>> -    if (!landlock_initialized)
>>> -        return -EOPNOTSUPP;
>>> -
>>> -    /* No flag for now. */
>>> -    if (flags)
>>> -        return -EINVAL;
>>> -
>>>       /* Gets and checks the ruleset. */
>>
>> Like I already said, this needs to stay in landlock_add_rule(). I 
>> think there is some inconsistencies with other patches that rechange 
>> this part. Please review your patches and make clean patches that 
>> don't partially revert the previous ones.
>>
>    Do you mean to leave this code as it its till adding network part
> in commit landlock: TCP network hooks implementation?
>   In this case this patch can be dropped.

The syscall argument check ordering needs to stay in the same order as 
you can see in the add_rule_checks_ordering test. Other than that, this 
commit looks good, it just splits the syscall in two functions, which is 
useful.


>>
>>>       ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>>       if (IS_ERR(ruleset))
>>>           return PTR_ERR(ruleset);
>>>
>>> -    if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
>>> -        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;
>>> -    }
>>> +                sizeof(path_beneath_attr));
>>> +    if (res)
>>> +        return -EFAULT;
>>>
>>>       /*
>>>        * Informs about useless rule: empty allowed_access (i.e. deny 
>>> rules)
>>> @@ -370,6 +326,59 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, 
>>> ruleset_fd,
>>>       return err;
>>>   }
>>>
>>> +/**
>>> + * 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_attr: Pointer to a rule (only of type &struct
>>> + *             landlock_path_beneath_attr for now).
>>> + * @flags: Must be 0.
>>> + *
>>> + * This system call enables to define a new rule and add it to an 
>>> existing
>>> + * ruleset.
>>> + *
>>> + * Possible returned errors are:
>>> + *
>>> + * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at 
>>> boot time;
>>> + * - 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 rule's
>>> + *   accesses);
>>> + * - ENOMSG: Empty accesses (e.g. 
>>> &landlock_path_beneath_attr.allowed_access);
>>> + * - EBADF: @ruleset_fd is not a file descriptor for the current 
>>> thread, or a
>>> + *   member of @rule_attr is not a file descriptor as expected;
>>> + * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a 
>>> member of
>>> + *   @rule_attr is not the expected file descriptor type (e.g. file 
>>> open
>>> + *   without O_PATH);
>>> + * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
>>> + * - EFAULT: @rule_attr inconsistency.
>>> + */
>>> +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)
>>> +{
>>> +    int err;
>>> +
>>> +    if (!landlock_initialized)
>>> +        return -EOPNOTSUPP;
>>> +
>>> +    /* No flag for now. */
>>> +    if (flags)
>>> +        return -EINVAL;
>>> +
>>> +    switch (rule_type) {
>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>> +        err = add_rule_path_beneath(ruleset_fd, rule_attr);
>>> +        break;
>>> +    default:
>>> +        err = -EINVAL;
>>> +        break;
>>> +    }
>>> +    return err;
>>> +}
>>> +
>>>   /* Enforcement */
>>>
>>>   /**
>>> diff --git a/tools/testing/selftests/landlock/base_test.c 
>>> b/tools/testing/selftests/landlock/base_test.c
>>> index da9290817866..0c4c3a538d54 100644
>>> --- a/tools/testing/selftests/landlock/base_test.c
>>> +++ b/tools/testing/selftests/landlock/base_test.c
>>> @@ -156,11 +156,11 @@ TEST(add_rule_checks_ordering)
>>>       ASSERT_LE(0, ruleset_fd);
>>>
>>>       /* Checks invalid flags. */
>>> -    ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
>>> +    ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 
>>> NULL, 1));
>>
>> This must not be changed! I specifically added these tests to make 
>> sure no one change the argument ordering checks…
> 
>    I updated this code cause I got error in base_test.
>    Ok. But in future commints I will order funtions calls in
>    landlock_add_rule() so that base_test runs smoothly (ordering checks).

Right, these tests are correct and they can help you.


> 
>>
>>
>>>       ASSERT_EQ(EINVAL, errno);
>>>
>>>       /* Checks invalid ruleset FD. */
>>> -    ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0));
>>> +    ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 
>>> NULL, 0));
>>>       ASSERT_EQ(EBADF, errno);
>>>
>>>       /* Checks invalid rule type. */
>>> -- 
>>> 2.25.1
>>>
>> .

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

* Re: [PATCH v5 07/15] landlock: add support network rules
  2022-05-19  9:27     ` Konstantin Meskhidze
@ 2022-05-19 14:42       ` Mickaël Salaün
  2022-05-24  8:36         ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 14:42 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 19/05/2022 11:27, Konstantin Meskhidze wrote:
> 
> 
> 5/17/2022 11:27 AM, Mickaël Salaün пишет:

[...]


>>>
>>> @@ -275,21 +281,17 @@ static int get_path_from_fd(const s32 fd, 
>>> struct path *const path)
>>>       return err;
>>>   }
>>>
>>> -static int add_rule_path_beneath(const int ruleset_fd, const void 
>>> *const rule_attr)
>>> +static int add_rule_path_beneath(struct landlock_ruleset *const 
>>> ruleset,
>>> +                 const void *const rule_attr)
>>>   {
>>>       struct landlock_path_beneath_attr path_beneath_attr;
>>>       struct path path;
>>> -    struct landlock_ruleset *ruleset;
>>>       int res, err;
>>> -
>>> -    /* Gets and checks the ruleset. */
>>> -    ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>> -    if (IS_ERR(ruleset))
>>> -        return PTR_ERR(ruleset);
>>> +    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));
>>> +            sizeof(path_beneath_attr));
>>>       if (res)
>>>           return -EFAULT;
>>>
>>> @@ -298,32 +300,26 @@ static int add_rule_path_beneath(const int 
>>> ruleset_fd, const void *const rule_at
>>>        * are ignored in path walks.
>>>        */
>>>       if (!path_beneath_attr.allowed_access) {
>>> -        err = -ENOMSG;
>>> -        goto out_put_ruleset;
>>> +        return -ENOMSG;
>>>       }
>>>       /*
>>>        * 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;
>>> -    }
>>> +    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)
>>> -        goto out_put_ruleset;
>>> +        return err;
>>>
>>>       /* 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;
>>>   }
>>>
>>> @@ -360,6 +356,7 @@ 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_ruleset *ruleset;
>>>       int err;
>>>
>>>       if (!landlock_initialized)
>>> @@ -369,14 +366,20 @@ SYSCALL_DEFINE4(landlock_add_rule,
>>>       if (flags)
>>>           return -EINVAL;
>>>
>>> +    /* Gets and checks the ruleset. */
>>> +    ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>> +    if (IS_ERR(ruleset))
>>> +        return PTR_ERR(ruleset);
>>
>> This shouldn't be part of this patch.
>>
>    I agree. I will move it into another patch.

To be clear, it is kind of a partial revert of patch 5/15.

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

* Re: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-19 12:31     ` Konstantin Meskhidze
@ 2022-05-19 15:00       ` Mickaël Salaün
  2022-05-24  8:40         ` Konstantin Meskhidze
  2022-05-19 15:02       ` Mickaël Salaün
  1 sibling, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 15:00 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 19/05/2022 14:31, Konstantin Meskhidze wrote:
> 
> 
> 5/17/2022 11:55 AM, Mickaël Salaün пишет:
>> I guess these tests would also work with IPv6. You can then use the 
>> "alternative" tests I explained.
>>
>    Do you mean adding new helpers such as bind_variant() and 
> connect_variant()??
>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>> Adds two selftests for connect() action with
>>> AF_UNSPEC family flag.
>>> The one is with no landlock restrictions
>>> allows to disconnect already conneted socket
>>> with connect(..., AF_UNSPEC, ...):
>>>      - connect_afunspec_no_restictions;
>>> The second one refuses landlocked process
>>> to disconnect already connected socket:
>>>      - connect_afunspec_with_restictions;
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v3:
>>> * Add connect_afunspec_no_restictions test.
>>> * Add connect_afunspec_with_restictions test.
>>>
>>> Changes since v4:
>>> * Refactoring code with self->port, self->addr4 variables.
>>> * Adds bind() hook check for with AF_UNSPEC family.
>>>
>>> ---
>>>   tools/testing/selftests/landlock/net_test.c | 121 ++++++++++++++++++++
>>>   1 file changed, 121 insertions(+)
>>>
>>> diff --git a/tools/testing/selftests/landlock/net_test.c 
>>> b/tools/testing/selftests/landlock/net_test.c
>>> index cf914d311eb3..bf8e49466d1d 100644
>>> --- a/tools/testing/selftests/landlock/net_test.c
>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>> @@ -449,6 +449,7 @@ TEST_F_FORK(socket_test, 
>>> connect_with_restrictions_ip6) {
>>>       int new_fd;
>>>       int sockfd_1, sockfd_2;
>>>       pid_t child_1, child_2;
>>> +
>>>       int status;
>>>
>>>       struct landlock_ruleset_attr ruleset_attr = {
>>> @@ -467,10 +468,12 @@ TEST_F_FORK(socket_test, 
>>> connect_with_restrictions_ip6) {
>>>
>>>       const int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>>>               sizeof(ruleset_attr), 0);
>>> +
>>
>> Please no…
>>
>   Sorry for that. I will apply clang-format-14.

clang-format will not complain about these new lines.


>>
>>>       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,
>>> +
>>
>> ditto
> 
>    Ditto. Will be fixed with clang-format.

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

* Re: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-19 12:31     ` Konstantin Meskhidze
  2022-05-19 15:00       ` Mickaël Salaün
@ 2022-05-19 15:02       ` Mickaël Salaün
  2022-05-24  8:42         ` Konstantin Meskhidze
  1 sibling, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 15:02 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov


On 19/05/2022 14:31, Konstantin Meskhidze wrote:
> 
> 
> 5/17/2022 11:55 AM, Mickaël Salaün пишет:
>> I guess these tests would also work with IPv6. You can then use the 
>> "alternative" tests I explained.
>>
>    Do you mean adding new helpers such as bind_variant() and 
> connect_variant()??

Yes, reusing bind_variant() and adding connect_variant().

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

* Re: [PATCH v5 12/15] seltests/landlock: rules overlapping test
  2022-05-19 12:24     ` Konstantin Meskhidze
@ 2022-05-19 15:04       ` Mickaël Salaün
  2022-05-24  8:55         ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 15:04 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



On 19/05/2022 14:24, Konstantin Meskhidze wrote:
> 
> 
> 5/16/2022 8:41 PM, Mickaël Salaün пишет:

[...]

>>> +
>>> +    /* Makes connection to socket with port[0] */
>>> +    ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
>>
>> Can you please get rid of this (struct sockaddr *) type casting please 
>> (without compiler warning)?
>>
>    Do you have a warning here? Cause I don't.

There is no warning but this kind of cast is useless.

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

* Re: [PATCH v5 15/15] samples/landlock: adds network demo
  2022-05-19 13:33     ` Konstantin Meskhidze
@ 2022-05-19 15:09       ` Mickaël Salaün
  2022-05-24  8:41         ` Konstantin Meskhidze
  0 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-19 15:09 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



On 19/05/2022 15:33, Konstantin Meskhidze wrote:
> 
> 
> 5/17/2022 12:19 PM, Mickaël Salaün пишет:
>>
>>
>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>> This commit adds network demo. It's possible to
>>> allow a sandoxer to bind/connect to a list of
>>> particular ports restricting networks actions to
>>> the rest of ports.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> 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.
>>> * Refactoring main() to support network sandboxing.
>>>
>>> ---

[...]

>>>       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;
>>> -    }
>>
>> Why? I know that checkpatch.pl prints a warning for that but I 
>> delibirately chooe to use curly braces even for "if" statements with 
>> one line because it is safer. This code may be copied/pasted and I'd 
>> like others to avoid introducing goto-fail-like issues.
>>
> 
>   It was done just to reduce the number of checkpatch.pl warnings.
>   If you want it to be formated in your way I will fix it.

Yes please, checkpatch.pl helps to mantain kernel code but this is a 
user space code and I prefer to follow safe practices for this kind of 
checks.

[...]

>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>> index 916b30b31c06..e1ff40f238a6 100644
>>> --- a/security/landlock/ruleset.h
>>> +++ b/security/landlock/ruleset.h
>>> @@ -19,7 +19,7 @@
>>>   #include "limits.h"
>>>   #include "object.h"
>>>
>>> -typedef u16 access_mask_t;
>>> +typedef u32 access_mask_t;
>>
>> What‽
> 
>    You are right. I will move this changes to another commit, related 
> the kernel updates. I might have forgotten to rebase this change and 
> left it in sandboxer patch. Thank you..

Indeed. Please check that every commit build (without warning) and that 
the related tests are OK.


>>
>>
>>>
>>>   /* Makes sure all filesystem access rights can be stored. */
>>>   static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
>>> @@ -157,7 +157,7 @@ struct landlock_ruleset {
>>>                * layers are set once and never changed for the
>>>                * lifetime of the ruleset.
>>>                */
>>> -            u32 access_masks[];
>>> +            access_mask_t access_masks[];
>>>           };
>>>       };
>>>   };
>>> -- 
>>> 2.25.1
>>>
>> .

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

* Re: [PATCH v5 00/15] Network support for Landlock - UDP discussion
  2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
                   ` (14 preceding siblings ...)
  2022-05-16 15:20 ` [PATCH v5 15/15] samples/landlock: adds network demo Konstantin Meskhidze
@ 2022-05-20 10:48 ` Mickaël Salaün
  2022-05-25  9:41   ` Konstantin Meskhidze
  15 siblings, 1 reply; 56+ messages in thread
From: Mickaël Salaün @ 2022-05-20 10:48 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov, Paul Moore

Hi,

Regarding future plan to support UDP, it may not be possible to 
efficiently restrict sending on a port or receiving on a port because of 
the non-connnected state of UDP sockets. Indeed, when setting up a 
socket to send a packet on a specified port, we (automatically or 
manually) have a receiving port configured and this socket can be used 
to receive any UDP packet. An UDP socket could be restricted to only 
send/write or to receive/read from a specific port, but this would 
probably not be as useful as the TCP restrictions. That could look like 
RECEIVE_UDP and SEND_UDP access-rights but the LSM implementation would 
be more complex because of the socket/FD tracking. Moreover, the 
performance impact could be more important for every read and write 
syscall (whatever the FD type).

Any opinion?

Regards,
  Mickaël


On 16/05/2022 17:20, Konstantin Meskhidze wrote:
> Hi,
> This is a new V5 patch related to Landlock LSM network confinement.
> It is based on the latest landlock-wip branch on top of v5.18-rc5:
> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=landlock-wip
> 
> It brings refactoring of previous patch version V4.
> Added additional selftests for IP6 network families and network namespace.
> Added TCP sockets confinement support in sandboxer demo.
> 
> All test were run in QEMU evironment and compiled with
>   -static flag.
>   1. network_test: 13/13 tests passed.
>   2. base_test: 7/7 tests passed.
>   3. fs_test: 59/59 tests passed.
>   4. ptrace_test: 8/8 tests passed.
> 
> Still have issue with base_test were compiled without -static flag
> (landlock-wip branch without network support)
> 1. base_test: 6/7 tests passed.
>   Error:
>   #  RUN           global.inconsistent_attr ...
>   # base_test.c:54:inconsistent_attr:Expected ENOMSG (42) == errno (22)
>   # inconsistent_attr: Test terminated by assertion
>   #          FAIL  global.inconsistent_attr
> not ok 1 global.inconsistent_attr
> 
> LCOV - code coverage report:
>              Hit  Total  Coverage
> Lines:      952  1010    94.3 %
> Functions:  79   82      96.3 %
> 
> Previous versions:
> 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 (15):
>    landlock: access mask renaming
>    landlock: landlock_find/insert_rule refactoring
>    landlock: merge and inherit function refactoring
>    landlock: helper functions refactoring
>    landlock: landlock_add_rule syscall refactoring
>    landlock: user space API network support
>    landlock: add support network rules
>    landlock: TCP network hooks implementation
>    seltests/landlock: add tests for bind() hooks
>    seltests/landlock: add tests for connect() hooks
>    seltests/landlock: connect() with AF_UNSPEC tests
>    seltests/landlock: rules overlapping test
>    seltests/landlock: ruleset expanding test
>    seltests/landlock: invalid user input data test
>    samples/landlock: adds network demo
> 
>   include/uapi/linux/landlock.h                |  48 +
>   samples/landlock/sandboxer.c                 | 105 ++-
>   security/landlock/Kconfig                    |   1 +
>   security/landlock/Makefile                   |   2 +
>   security/landlock/fs.c                       | 169 +---
>   security/landlock/limits.h                   |   8 +-
>   security/landlock/net.c                      | 159 ++++
>   security/landlock/net.h                      |  25 +
>   security/landlock/ruleset.c                  | 481 ++++++++--
>   security/landlock/ruleset.h                  | 102 +-
>   security/landlock/setup.c                    |   2 +
>   security/landlock/syscalls.c                 | 173 ++--
>   tools/testing/selftests/landlock/base_test.c |   4 +-
>   tools/testing/selftests/landlock/common.h    |   9 +
>   tools/testing/selftests/landlock/config      |   5 +-
>   tools/testing/selftests/landlock/fs_test.c   |  10 -
>   tools/testing/selftests/landlock/net_test.c  | 935 +++++++++++++++++++
>   17 files changed, 1925 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] 56+ messages in thread

* Re: [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks
  2022-05-19 14:29       ` Mickaël Salaün
@ 2022-05-24  8:34         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:34 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 5:29 PM, Mickaël Salaün пишет:
> 
> On 19/05/2022 14:10, Konstantin Meskhidze wrote:
>>
>>
>> 5/17/2022 12:11 AM, Mickaël Salaün пишет:
>>>
>>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>>> Adds selftests for bind socket action.
>>>> The first is with no landlock restrictions:
>>>>      - bind_no_restrictions_ip4;
>>>>      - bind_no_restrictions_ip6;
>>>> The second ones is with mixed landlock rules:
>>>>      - bind_with_restrictions_ip4;
>>>>      - bind_with_restrictions_ip6;
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v3:
>>>> * Split commit.
>>>> * Add helper create_socket.
>>>> * Add FIXTURE_SETUP.
>>>>
>>>> Changes since v4:
>>>> * Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4
>>>> and struct sockaddr_in addr6 in FIXTURE.
>>>> * Refactoring FIXTURE_SETUP:
>>>>      - initializing self->port, self->addr4 and self->addr6.
>>>>      - adding network namespace.
>>>> * Refactoring code with self->port, self->addr4 and
>>>> self->addr6 variables.
>>>> * Adds selftests for IP6 family:
>>>>      - bind_no_restrictions_ip6.
>>>>      - bind_with_restrictions_ip6.
>>>> * Refactoring selftests/landlock/config
>>>> * Moves enforce_ruleset() into common.h
>>>>
>>>> ---
>>>>   tools/testing/selftests/landlock/common.h   |   9 +
>>>>   tools/testing/selftests/landlock/config     |   5 +-
>>>>   tools/testing/selftests/landlock/fs_test.c  |  10 -
>>>>   tools/testing/selftests/landlock/net_test.c | 237 
>>>> ++++++++++++++++++++
>>>>   4 files changed, 250 insertions(+), 11 deletions(-)
>>>>   create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>>
>>>> diff --git a/tools/testing/selftests/landlock/common.h 
>>>> b/tools/testing/selftests/landlock/common.h
>>>> index 7ba18eb23783..c5381e641dfd 100644
>>>> --- a/tools/testing/selftests/landlock/common.h
>>>> +++ b/tools/testing/selftests/landlock/common.h
>>>> @@ -102,6 +102,15 @@ static inline int landlock_restrict_self(const 
>>>> int ruleset_fd,
>>>>   }
>>>>   #endif
>>>>
>>>> +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));
>>>> +    }
>>>> +}
>>>> +
>>>
>>> Please create a commit which moves all the needed code for all 
>>> network tests. I think there is only this helper though.
>>
>>    Ok. I will create one additional commit for moving this helper.
>>    But after I have moved the helper to common.h, I got warnings while 
>> compiling seltests where I don't use the one (base_test and ptrace_test)
> 
> Move it after clear_cap() and use the same attributes.
> 
   Ok. Thank you.
> [...]
> 
>  >>> diff --git a/tools/testing/selftests/landlock/config
>  >>> b/tools/testing/selftests/landlock/config
>  >>> index 0f0a65287bac..b56f3274d3f5 100644
>  >>> --- a/tools/testing/selftests/landlock/config
>  >>> +++ b/tools/testing/selftests/landlock/config
>  >>> @@ -1,7 +1,10 @@
>  >>> +CONFIG_INET=y
>  >>> +CONFIG_IPV6=y
>  >>> +CONFIG_NET=y
>  >>>   CONFIG_OVERLAY_FS=y
>  >>>   CONFIG_SECURITY_LANDLOCK=y
>  >>>   CONFIG_SECURITY_PATH=y
>  >>>   CONFIG_SECURITY=y
>  >>>   CONFIG_SHMEM=y
>  >>>   CONFIG_TMPFS_XATTR=y
>  >>> -CONFIG_TMPFS=y
>  >>> +CONFIG_TMPFS=y
>  >>> \ No newline at end of file
> 
> You also need to add CONFIG_NET_NS.

   Yep. I have forgotten about it. Thanks.
> 
> [...]
> 
>>>
>>>> +        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 = htonl(INADDR_ANY);
>>>
>>> Could you use the local addr (127.0.0.1) instead?
>>
>>    Why cant I use INADDR_ANY here?
> 
> You can, but it is cleaner to bind to a specified address (i.e. you 
> control where a connection come from), and I guess this variable/address 
> could be used to establish connections as well.
> 
  Ok. I got it.
>>>
>>>> +        memset(&(self->addr4[i].sin_zero), '\0', 8);
>>>> +    }
>>>> +
>>>> +    /* Creates IP6 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]);
>>>> +        self->addr6[i].sin6_addr = in6addr_any;
>>>
>>> ditto
>>
>>    Why cant I use in6addr_any here?
> 
> Same as for IPV4.

   Ok.
> 
>>
>>>
>>>> +    }
>>>> +
>>>> +    set_cap(_metadata, CAP_SYS_ADMIN);
>>>> +    ASSERT_EQ(0, unshare(CLONE_NEWNET));
>>>> +    ASSERT_EQ(0, system("ip link set dev lo up"));
>>>
>>> If this is really required, could you avoid calling system() but set 
>>> up the network in C? You can strace it to see what is going on 
>>> underneath.
>>>
>>   I did check. It's a lot of code to be run under the hood (more than 
>> one line) and it will just will complicate the test so I suggest to 
>> leave just ONE line of code here.
> 
> OK
> 
> 
>>>
>>>> +    clear_cap(_metadata, CAP_SYS_ADMIN);
>>>> +}
>>>> +
>>>> +FIXTURE_TEARDOWN(socket_test)
>>>> +{ }
>>>> +
>>>> +TEST_F_FORK(socket_test, bind_no_restrictions_ip4) {
>>>> +
>>>> +    int sockfd;
>>>> +
>>>> +    sockfd = create_socket(_metadata, false, false);
>>>> +    ASSERT_LE(0, sockfd);
>>>> +
>>>> +    /* Binds a socket to port[0] */
>>>
>>> This comment is not very useful in this context considering the below 
>>> line. It will be even more clear with the bind_variant() call.
>>>
>>   Ok. I will fix it.
>>>
>>>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], 
>>>> sizeof(self->addr4[0])));
>>>> +
>>>> +    ASSERT_EQ(0, close(sockfd));
>>>> +}
>>>> +
>>>> +TEST_F_FORK(socket_test, bind_no_restrictions_ip6) {
>>>> +
>>>> +    int sockfd;
>>>> +
>>>> +    sockfd = create_socket(_metadata, true, false);
>>>> +    ASSERT_LE(0, sockfd);
>>>> +
>>>> +    /* Binds a socket to port[0] */
>>>> +    ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], 
>>>> sizeof(self->addr6[0])));
>>>> +
>>>> +    ASSERT_EQ(0, close(sockfd));
>>>> +}
>>>> +
>>>> +TEST_F_FORK(socket_test, bind_with_restrictions_ip4) {
>>>> +
>>>> +    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. */
>>>
>>> This comment is useful though because the below call is more complex.
>>>
>>    So I can leave it as it's, cant I?
> 
> Yes, keep it, I'd just like a fair amount of useful comments. ;)

   Ok. Thank you!
> .

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

* Re: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring
  2022-05-19 14:37       ` Mickaël Salaün
@ 2022-05-24  8:35         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:35 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 5:37 PM, Mickaël Salaün пишет:
> 
> 
> On 19/05/2022 11:23, Konstantin Meskhidze wrote:
>>
>>
>> 5/17/2022 11:04 AM, Mickaël Salaün пишет:
>>> You can rename the subject to "landlock: Refactor landlock_add_rule()"
>>>
>>>
>>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>>> Landlock_add_rule syscall was refactored to support new
>>>> rule types in future Landlock versions. Add_rule_path_beneath()
>>>
>>> nit: add_rule_path_beneath(), not Add_rule_path_beneath()
>>>
>>    Ok. Thanks. Will be renamed.
>>
>>>> helper was added to support current filesystem rules. It is called
>>>> by the switch case.
>>>
>>> You can rephrase (all commit messages) in the present form:
>>>
>>> Refactor the landlock_add_rule() syscall with add_rule_path_beneath() 
>>> to support new…
>>>
>>> Refactor the landlock_add_rule() syscall to easily support for a new 
>>> rule type in a following commit. The new add_rule_path_beneath() 
>>> helper supports current filesystem rules.
>>>
>>    Ok. I will fix it.
>>>
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v3:
>>>> * Split commit.
>>>> * Refactoring landlock_add_rule syscall.
>>>>
>>>> Changes since v4:
>>>> * Refactoring add_rule_path_beneath() and landlock_add_rule() functions
>>>> to optimize code usage.
>>>> * Refactoring base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
>>>> rule type in landlock_add_rule() call.
>>>>
>>>> ---
>>>>   security/landlock/syscalls.c                 | 105 
>>>> ++++++++++---------
>>>>   tools/testing/selftests/landlock/base_test.c |   4 +-
>>>>   2 files changed, 59 insertions(+), 50 deletions(-)
>>>>
>>>> diff --git a/security/landlock/syscalls.c 
>>>> b/security/landlock/syscalls.c
>>>> index 1db799d1a50b..412ced6c512f 100644
>>>> --- a/security/landlock/syscalls.c
>>>> +++ b/security/landlock/syscalls.c
>>>> @@ -274,67 +274,23 @@ static int get_path_from_fd(const s32 fd, 
>>>> struct path *const path)
>>>>       return err;
>>>>   }
>>>>
>>>> -/**
>>>> - * 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_attr: Pointer to a rule (only of type &struct
>>>> - *             landlock_path_beneath_attr for now).
>>>> - * @flags: Must be 0.
>>>> - *
>>>> - * This system call enables to define a new rule and add it to an 
>>>> existing
>>>> - * ruleset.
>>>> - *
>>>> - * Possible returned errors are:
>>>> - *
>>>> - * - EOPNOTSUPP: Landlock is supported by the kernel but disabled 
>>>> at boot time;
>>>> - * - 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);
>>>> - * - ENOMSG: Empty accesses (e.g. 
>>>> &landlock_path_beneath_attr.allowed_access);
>>>> - * - EBADF: @ruleset_fd is not a file descriptor for the current 
>>>> thread, or a
>>>> - *   member of @rule_attr is not a file descriptor as expected;
>>>> - * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a 
>>>> member of
>>>> - *   @rule_attr is not the expected file descriptor type;
>>>> - * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
>>>> - * - EFAULT: @rule_attr inconsistency.
>>>> - */
>>>> -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)
>>>> +static int add_rule_path_beneath(const int ruleset_fd, const void 
>>>> *const rule_attr)
>>>>   {
>>>>       struct landlock_path_beneath_attr path_beneath_attr;
>>>>       struct path path;
>>>>       struct landlock_ruleset *ruleset;
>>>>       int res, err;
>>>>
>>>> -    if (!landlock_initialized)
>>>> -        return -EOPNOTSUPP;
>>>> -
>>>> -    /* No flag for now. */
>>>> -    if (flags)
>>>> -        return -EINVAL;
>>>> -
>>>>       /* Gets and checks the ruleset. */
>>>
>>> Like I already said, this needs to stay in landlock_add_rule(). I 
>>> think there is some inconsistencies with other patches that rechange 
>>> this part. Please review your patches and make clean patches that 
>>> don't partially revert the previous ones.
>>>
>>    Do you mean to leave this code as it its till adding network part
>> in commit landlock: TCP network hooks implementation?
>>   In this case this patch can be dropped.
> 
> The syscall argument check ordering needs to stay in the same order as 
> you can see in the add_rule_checks_ordering test. Other than that, this 
> commit looks good, it just splits the syscall in two functions, which is 
> useful.
> 
> 
>>>
>>>>       ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>>>       if (IS_ERR(ruleset))
>>>>           return PTR_ERR(ruleset);
>>>>
>>>> -    if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
>>>> -        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;
>>>> -    }
>>>> +                sizeof(path_beneath_attr));
>>>> +    if (res)
>>>> +        return -EFAULT;
>>>>
>>>>       /*
>>>>        * Informs about useless rule: empty allowed_access (i.e. deny 
>>>> rules)
>>>> @@ -370,6 +326,59 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, 
>>>> ruleset_fd,
>>>>       return err;
>>>>   }
>>>>
>>>> +/**
>>>> + * 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_attr: Pointer to a rule (only of type &struct
>>>> + *             landlock_path_beneath_attr for now).
>>>> + * @flags: Must be 0.
>>>> + *
>>>> + * This system call enables to define a new rule and add it to an 
>>>> existing
>>>> + * ruleset.
>>>> + *
>>>> + * Possible returned errors are:
>>>> + *
>>>> + * - EOPNOTSUPP: Landlock is supported by the kernel but disabled 
>>>> at boot time;
>>>> + * - 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 rule's
>>>> + *   accesses);
>>>> + * - ENOMSG: Empty accesses (e.g. 
>>>> &landlock_path_beneath_attr.allowed_access);
>>>> + * - EBADF: @ruleset_fd is not a file descriptor for the current 
>>>> thread, or a
>>>> + *   member of @rule_attr is not a file descriptor as expected;
>>>> + * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a 
>>>> member of
>>>> + *   @rule_attr is not the expected file descriptor type (e.g. file 
>>>> open
>>>> + *   without O_PATH);
>>>> + * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
>>>> + * - EFAULT: @rule_attr inconsistency.
>>>> + */
>>>> +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)
>>>> +{
>>>> +    int err;
>>>> +
>>>> +    if (!landlock_initialized)
>>>> +        return -EOPNOTSUPP;
>>>> +
>>>> +    /* No flag for now. */
>>>> +    if (flags)
>>>> +        return -EINVAL;
>>>> +
>>>> +    switch (rule_type) {
>>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>>> +        err = add_rule_path_beneath(ruleset_fd, rule_attr);
>>>> +        break;
>>>> +    default:
>>>> +        err = -EINVAL;
>>>> +        break;
>>>> +    }
>>>> +    return err;
>>>> +}
>>>> +
>>>>   /* Enforcement */
>>>>
>>>>   /**
>>>> diff --git a/tools/testing/selftests/landlock/base_test.c 
>>>> b/tools/testing/selftests/landlock/base_test.c
>>>> index da9290817866..0c4c3a538d54 100644
>>>> --- a/tools/testing/selftests/landlock/base_test.c
>>>> +++ b/tools/testing/selftests/landlock/base_test.c
>>>> @@ -156,11 +156,11 @@ TEST(add_rule_checks_ordering)
>>>>       ASSERT_LE(0, ruleset_fd);
>>>>
>>>>       /* Checks invalid flags. */
>>>> -    ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
>>>> +    ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 
>>>> NULL, 1));
>>>
>>> This must not be changed! I specifically added these tests to make 
>>> sure no one change the argument ordering checks…
>>
>>    I updated this code cause I got error in base_test.
>>    Ok. But in future commints I will order funtions calls in
>>    landlock_add_rule() so that base_test runs smoothly (ordering checks).
> 
> Right, these tests are correct and they can help you.
> 
  Thank you!!
> 
>>
>>>
>>>
>>>>       ASSERT_EQ(EINVAL, errno);
>>>>
>>>>       /* Checks invalid ruleset FD. */
>>>> -    ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0));
>>>> +    ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 
>>>> NULL, 0));
>>>>       ASSERT_EQ(EBADF, errno);
>>>>
>>>>       /* Checks invalid rule type. */
>>>> -- 
>>>> 2.25.1
>>>>
>>> .
> .

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

* Re: [PATCH v5 07/15] landlock: add support network rules
  2022-05-19 14:42       ` Mickaël Salaün
@ 2022-05-24  8:36         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:36 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 5:42 PM, Mickaël Salaün пишет:
> 
> On 19/05/2022 11:27, Konstantin Meskhidze wrote:
>>
>>
>> 5/17/2022 11:27 AM, Mickaël Salaün пишет:
> 
> [...]
> 
> 
>>>>
>>>> @@ -275,21 +281,17 @@ static int get_path_from_fd(const s32 fd, 
>>>> struct path *const path)
>>>>       return err;
>>>>   }
>>>>
>>>> -static int add_rule_path_beneath(const int ruleset_fd, const void 
>>>> *const rule_attr)
>>>> +static int add_rule_path_beneath(struct landlock_ruleset *const 
>>>> ruleset,
>>>> +                 const void *const rule_attr)
>>>>   {
>>>>       struct landlock_path_beneath_attr path_beneath_attr;
>>>>       struct path path;
>>>> -    struct landlock_ruleset *ruleset;
>>>>       int res, err;
>>>> -
>>>> -    /* Gets and checks the ruleset. */
>>>> -    ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>>> -    if (IS_ERR(ruleset))
>>>> -        return PTR_ERR(ruleset);
>>>> +    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));
>>>> +            sizeof(path_beneath_attr));
>>>>       if (res)
>>>>           return -EFAULT;
>>>>
>>>> @@ -298,32 +300,26 @@ static int add_rule_path_beneath(const int 
>>>> ruleset_fd, const void *const rule_at
>>>>        * are ignored in path walks.
>>>>        */
>>>>       if (!path_beneath_attr.allowed_access) {
>>>> -        err = -ENOMSG;
>>>> -        goto out_put_ruleset;
>>>> +        return -ENOMSG;
>>>>       }
>>>>       /*
>>>>        * 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;
>>>> -    }
>>>> +    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)
>>>> -        goto out_put_ruleset;
>>>> +        return err;
>>>>
>>>>       /* 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;
>>>>   }
>>>>
>>>> @@ -360,6 +356,7 @@ 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_ruleset *ruleset;
>>>>       int err;
>>>>
>>>>       if (!landlock_initialized)
>>>> @@ -369,14 +366,20 @@ SYSCALL_DEFINE4(landlock_add_rule,
>>>>       if (flags)
>>>>           return -EINVAL;
>>>>
>>>> +    /* Gets and checks the ruleset. */
>>>> +    ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
>>>> +    if (IS_ERR(ruleset))
>>>> +        return PTR_ERR(ruleset);
>>>
>>> This shouldn't be part of this patch.
>>>
>>    I agree. I will move it into another patch.
> 
> To be clear, it is kind of a partial revert of patch 5/15.

   Yep. Thank you for noticing that.
> .

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

* Re: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-19 15:00       ` Mickaël Salaün
@ 2022-05-24  8:40         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:40 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 6:00 PM, Mickaël Salaün пишет:
> 
> On 19/05/2022 14:31, Konstantin Meskhidze wrote:
>>
>>
>> 5/17/2022 11:55 AM, Mickaël Salaün пишет:
>>> I guess these tests would also work with IPv6. You can then use the 
>>> "alternative" tests I explained.
>>>
>>    Do you mean adding new helpers such as bind_variant() and 
>> connect_variant()??
>>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>>> Adds two selftests for connect() action with
>>>> AF_UNSPEC family flag.
>>>> The one is with no landlock restrictions
>>>> allows to disconnect already conneted socket
>>>> with connect(..., AF_UNSPEC, ...):
>>>>      - connect_afunspec_no_restictions;
>>>> The second one refuses landlocked process
>>>> to disconnect already connected socket:
>>>>      - connect_afunspec_with_restictions;
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v3:
>>>> * Add connect_afunspec_no_restictions test.
>>>> * Add connect_afunspec_with_restictions test.
>>>>
>>>> Changes since v4:
>>>> * Refactoring code with self->port, self->addr4 variables.
>>>> * Adds bind() hook check for with AF_UNSPEC family.
>>>>
>>>> ---
>>>>   tools/testing/selftests/landlock/net_test.c | 121 
>>>> ++++++++++++++++++++
>>>>   1 file changed, 121 insertions(+)
>>>>
>>>> diff --git a/tools/testing/selftests/landlock/net_test.c 
>>>> b/tools/testing/selftests/landlock/net_test.c
>>>> index cf914d311eb3..bf8e49466d1d 100644
>>>> --- a/tools/testing/selftests/landlock/net_test.c
>>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>>> @@ -449,6 +449,7 @@ TEST_F_FORK(socket_test, 
>>>> connect_with_restrictions_ip6) {
>>>>       int new_fd;
>>>>       int sockfd_1, sockfd_2;
>>>>       pid_t child_1, child_2;
>>>> +
>>>>       int status;
>>>>
>>>>       struct landlock_ruleset_attr ruleset_attr = {
>>>> @@ -467,10 +468,12 @@ TEST_F_FORK(socket_test, 
>>>> connect_with_restrictions_ip6) {
>>>>
>>>>       const int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>>>>               sizeof(ruleset_attr), 0);
>>>> +
>>>
>>> Please no…
>>>
>>   Sorry for that. I will apply clang-format-14.
> 
> clang-format will not complain about these new lines.

   Anyway I will check that no additional new lines appear.
   Thank you.
> 
> 
>>>
>>>>       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,
>>>> +
>>>
>>> ditto
>>
>>    Ditto. Will be fixed with clang-format.
> .

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

* Re: [PATCH v5 15/15] samples/landlock: adds network demo
  2022-05-19 15:09       ` Mickaël Salaün
@ 2022-05-24  8:41         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:41 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 6:09 PM, Mickaël Salaün пишет:
> 
> 
> On 19/05/2022 15:33, Konstantin Meskhidze wrote:
>>
>>
>> 5/17/2022 12:19 PM, Mickaël Salaün пишет:
>>>
>>>
>>> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>>>> This commit adds network demo. It's possible to
>>>> allow a sandoxer to bind/connect to a list of
>>>> particular ports restricting networks actions to
>>>> the rest of ports.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> 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.
>>>> * Refactoring main() to support network sandboxing.
>>>>
>>>> ---
> 
> [...]
> 
>>>>       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;
>>>> -    }
>>>
>>> Why? I know that checkpatch.pl prints a warning for that but I 
>>> delibirately chooe to use curly braces even for "if" statements with 
>>> one line because it is safer. This code may be copied/pasted and I'd 
>>> like others to avoid introducing goto-fail-like issues.
>>>
>>
>>   It was done just to reduce the number of checkpatch.pl warnings.
>>   If you want it to be formated in your way I will fix it.
> 
> Yes please, checkpatch.pl helps to mantain kernel code but this is a 
> user space code and I prefer to follow safe practices for this kind of 
> checks.
> 
  Ok. I will keep you code here. Thanks.
> [...]
> 
>>>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>>>> index 916b30b31c06..e1ff40f238a6 100644
>>>> --- a/security/landlock/ruleset.h
>>>> +++ b/security/landlock/ruleset.h
>>>> @@ -19,7 +19,7 @@
>>>>   #include "limits.h"
>>>>   #include "object.h"
>>>>
>>>> -typedef u16 access_mask_t;
>>>> +typedef u32 access_mask_t;
>>>
>>> What‽
>>
>>    You are right. I will move this changes to another commit, related 
>> the kernel updates. I might have forgotten to rebase this change and 
>> left it in sandboxer patch. Thank you..
> 
> Indeed. Please check that every commit build (without warning) and that 
> the related tests are OK.

   Ok. I will. Thanks.
> 
> 
>>>
>>>
>>>>
>>>>   /* Makes sure all filesystem access rights can be stored. */
>>>>   static_assert(BITS_PER_TYPE(access_mask_t) >= 
>>>> LANDLOCK_NUM_ACCESS_FS);
>>>> @@ -157,7 +157,7 @@ struct landlock_ruleset {
>>>>                * layers are set once and never changed for the
>>>>                * lifetime of the ruleset.
>>>>                */
>>>> -            u32 access_masks[];
>>>> +            access_mask_t access_masks[];
>>>>           };
>>>>       };
>>>>   };
>>>> -- 
>>>> 2.25.1
>>>>
>>> .
> .

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

* Re: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests
  2022-05-19 15:02       ` Mickaël Salaün
@ 2022-05-24  8:42         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:42 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 6:02 PM, Mickaël Salaün пишет:
> 
> On 19/05/2022 14:31, Konstantin Meskhidze wrote:
>>
>>
>> 5/17/2022 11:55 AM, Mickaël Salaün пишет:
>>> I guess these tests would also work with IPv6. You can then use the 
>>> "alternative" tests I explained.
>>>
>>    Do you mean adding new helpers such as bind_variant() and 
>> connect_variant()??
> 
> Yes, reusing bind_variant() and adding connect_variant().

   Ok. I got it. Thanks!
> .

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

* Re: [PATCH v5 12/15] seltests/landlock: rules overlapping test
  2022-05-19 15:04       ` Mickaël Salaün
@ 2022-05-24  8:55         ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-24  8:55 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov



5/19/2022 6:04 PM, Mickaël Salaün пишет:
> 
> 
> On 19/05/2022 14:24, Konstantin Meskhidze wrote:
>>
>>
>> 5/16/2022 8:41 PM, Mickaël Salaün пишет:
> 
> [...]
> 
>>>> +
>>>> +    /* Makes connection to socket with port[0] */
>>>> +    ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0],
>>>
>>> Can you please get rid of this (struct sockaddr *) type casting 
>>> please (without compiler warning)?
>>>
>>    Do you have a warning here? Cause I don't.
> 
> There is no warning but this kind of cast is useless.

   But addr4 is struct sockaddr_in type and connect/bind use struct 
sockaddr type. That's why casting is needed here.

> .

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

* Re: [PATCH v5 00/15] Network support for Landlock - UDP discussion
  2022-05-20 10:48 ` [PATCH v5 00/15] Network support for Landlock - UDP discussion Mickaël Salaün
@ 2022-05-25  9:41   ` Konstantin Meskhidze
  0 siblings, 0 replies; 56+ messages in thread
From: Konstantin Meskhidze @ 2022-05-25  9:41 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, anton.sirazetdinov, Paul Moore



5/20/2022 1:48 PM, Mickaël Salaün пишет:
> Hi,
> 
> Regarding future plan to support UDP, it may not be possible to 
> efficiently restrict sending on a port or receiving on a port because of 
> the non-connnected state of UDP sockets. Indeed, when setting up a 
> socket to send a packet on a specified port, we (automatically or 
> manually) have a receiving port configured and this socket can be used 
> to receive any UDP packet. An UDP socket could be restricted to only 
> send/write or to receive/read from a specific port, but this would 
> probably not be as useful as the TCP restrictions. That could look like 
> RECEIVE_UDP and SEND_UDP access-rights but the LSM implementation would 
> be more complex because of the socket/FD tracking. Moreover, the 
> performance impact could be more important for every read and write 
> syscall (whatever the FD type).
> 
> Any opinion?
> 

   You are right about non-connected nature of UDP sockets and 
landlocking them like TCP ones would have performance impact.
I'm thinking about a "connected" UDP socket.
	It's possible call connect() for a UDP socket. But this does not result 
in anything like a TCP connection: There is no three-way handshake. 
Instead, the kernel just checks for any immediate errors (e.g., an 
obviously unreachable destination), records the IP address and port 
number of the peer (from the socket address structure passed to 
connect), and returns immediately to the calling process. In this case 
UDP socket is pseudo-connected and stores peer IP addrsss and port from 
connect(). The application calls connect(), specifies the IP address and 
port number of its peer. It then uses read() and write() yo exchange 
data with the peer. Datagrams arriving from any other IP address or port 
are not passed to the connected socket because either the source IP 
address or source UDP port does not match the protocol address to which 
the socket is connected. These datagrams could be delivered to some 
other UDP socket on the host. If there is no other matching socket for 
the arriving datagram, UDP will discard it and generate an ICMP ‘‘port 
unreachable’’ error. In summary, we can say that a UDP client or server 
can call connect only if that process uses the UDP socket to communicate 
with exactly one peer. Normally, it is a UDP client that calls connect, 
but there are applications in which the UDP server communicates with a 
single client for a long duration (e.g., TFTP); in this case, both the
client and server can call connect. [1]

In case if a "connected", or lets call it "pseudo-connected", UPD socket 
there is no performance impact on write(), read() system calls, cause we 
could use the same hooks bind() and connect() like for TCP one.

What do you think? Please share your opinion?

[1] "Unix Network Programming, The sockets Networling API." by W.Richard 
Stevens.

> Regards,
>   Mickaël
> 
> 
> On 16/05/2022 17:20, Konstantin Meskhidze wrote:
>> Hi,
>> This is a new V5 patch related to Landlock LSM network confinement.
>> It is based on the latest landlock-wip branch on top of v5.18-rc5:
>> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=landlock-wip 
>>
>>
>> It brings refactoring of previous patch version V4.
>> Added additional selftests for IP6 network families and network 
>> namespace.
>> Added TCP sockets confinement support in sandboxer demo.
>>
>> All test were run in QEMU evironment and compiled with
>>   -static flag.
>>   1. network_test: 13/13 tests passed.
>>   2. base_test: 7/7 tests passed.
>>   3. fs_test: 59/59 tests passed.
>>   4. ptrace_test: 8/8 tests passed.
>>
>> Still have issue with base_test were compiled without -static flag
>> (landlock-wip branch without network support)
>> 1. base_test: 6/7 tests passed.
>>   Error:
>>   #  RUN           global.inconsistent_attr ...
>>   # base_test.c:54:inconsistent_attr:Expected ENOMSG (42) == errno (22)
>>   # inconsistent_attr: Test terminated by assertion
>>   #          FAIL  global.inconsistent_attr
>> not ok 1 global.inconsistent_attr
>>
>> LCOV - code coverage report:
>>              Hit  Total  Coverage
>> Lines:      952  1010    94.3 %
>> Functions:  79   82      96.3 %
>>
>> Previous versions:
>> 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 (15):
>>    landlock: access mask renaming
>>    landlock: landlock_find/insert_rule refactoring
>>    landlock: merge and inherit function refactoring
>>    landlock: helper functions refactoring
>>    landlock: landlock_add_rule syscall refactoring
>>    landlock: user space API network support
>>    landlock: add support network rules
>>    landlock: TCP network hooks implementation
>>    seltests/landlock: add tests for bind() hooks
>>    seltests/landlock: add tests for connect() hooks
>>    seltests/landlock: connect() with AF_UNSPEC tests
>>    seltests/landlock: rules overlapping test
>>    seltests/landlock: ruleset expanding test
>>    seltests/landlock: invalid user input data test
>>    samples/landlock: adds network demo
>>
>>   include/uapi/linux/landlock.h                |  48 +
>>   samples/landlock/sandboxer.c                 | 105 ++-
>>   security/landlock/Kconfig                    |   1 +
>>   security/landlock/Makefile                   |   2 +
>>   security/landlock/fs.c                       | 169 +---
>>   security/landlock/limits.h                   |   8 +-
>>   security/landlock/net.c                      | 159 ++++
>>   security/landlock/net.h                      |  25 +
>>   security/landlock/ruleset.c                  | 481 ++++++++--
>>   security/landlock/ruleset.h                  | 102 +-
>>   security/landlock/setup.c                    |   2 +
>>   security/landlock/syscalls.c                 | 173 ++--
>>   tools/testing/selftests/landlock/base_test.c |   4 +-
>>   tools/testing/selftests/landlock/common.h    |   9 +
>>   tools/testing/selftests/landlock/config      |   5 +-
>>   tools/testing/selftests/landlock/fs_test.c   |  10 -
>>   tools/testing/selftests/landlock/net_test.c  | 935 +++++++++++++++++++
>>   17 files changed, 1925 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] 56+ messages in thread

end of thread, other threads:[~2022-05-25  9:41 UTC | newest]

Thread overview: 56+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-05-16 15:20 [PATCH v5 00/15] Network support for Landlock Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 01/15] landlock: access mask renaming Konstantin Meskhidze
2022-05-17  8:12   ` Mickaël Salaün
2022-05-18  9:16     ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 02/15] landlock: landlock_find/insert_rule refactoring Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 03/15] landlock: merge and inherit function refactoring Konstantin Meskhidze
2022-05-17  8:14   ` Mickaël Salaün
2022-05-18  9:18     ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 04/15] landlock: helper functions refactoring Konstantin Meskhidze
2022-05-16 17:14   ` Mickaël Salaün
2022-05-16 17:43     ` Konstantin Meskhidze
2022-05-16 18:28       ` Mickaël Salaün
2022-05-18  9:14         ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring Konstantin Meskhidze
2022-05-17  8:04   ` Mickaël Salaün
2022-05-17  8:10     ` Mickaël Salaün
2022-05-19  9:24       ` Konstantin Meskhidze
2022-05-19  9:23     ` Konstantin Meskhidze
2022-05-19 14:37       ` Mickaël Salaün
2022-05-24  8:35         ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 06/15] landlock: user space API network support Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 07/15] landlock: add support network rules Konstantin Meskhidze
2022-05-17  8:27   ` Mickaël Salaün
2022-05-19  9:27     ` Konstantin Meskhidze
2022-05-19 14:42       ` Mickaël Salaün
2022-05-24  8:36         ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 08/15] landlock: TCP network hooks implementation Konstantin Meskhidze
2022-05-17  8:51   ` Mickaël Salaün
2022-05-19 11:40     ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks Konstantin Meskhidze
2022-05-16 21:11   ` Mickaël Salaün
2022-05-19 12:10     ` Konstantin Meskhidze
2022-05-19 14:29       ` Mickaël Salaün
2022-05-24  8:34         ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 10/15] seltests/landlock: add tests for connect() hooks Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests Konstantin Meskhidze
2022-05-17  8:55   ` Mickaël Salaün
2022-05-19 12:31     ` Konstantin Meskhidze
2022-05-19 15:00       ` Mickaël Salaün
2022-05-24  8:40         ` Konstantin Meskhidze
2022-05-19 15:02       ` Mickaël Salaün
2022-05-24  8:42         ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 12/15] seltests/landlock: rules overlapping test Konstantin Meskhidze
2022-05-16 17:41   ` Mickaël Salaün
2022-05-19 12:24     ` Konstantin Meskhidze
2022-05-19 15:04       ` Mickaël Salaün
2022-05-24  8:55         ` Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 13/15] seltests/landlock: ruleset expanding test Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 14/15] seltests/landlock: invalid user input data test Konstantin Meskhidze
2022-05-16 15:20 ` [PATCH v5 15/15] samples/landlock: adds network demo Konstantin Meskhidze
2022-05-17  9:19   ` Mickaël Salaün
2022-05-19 13:33     ` Konstantin Meskhidze
2022-05-19 15:09       ` Mickaël Salaün
2022-05-24  8:41         ` Konstantin Meskhidze
2022-05-20 10:48 ` [PATCH v5 00/15] Network support for Landlock - UDP discussion Mickaël Salaün
2022-05-25  9:41   ` Konstantin Meskhidze

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