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

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

It brings refactoring of previous patch version V8.
Mostly there are fixes of logic and typos, adding new tests.

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

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

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

Mickaël Salaün (1):
  landlock: Allow filesystem layout changes for domains without such
    rule type

 Documentation/userspace-api/landlock.rst     |   72 +-
 include/uapi/linux/landlock.h                |   49 +
 samples/landlock/sandboxer.c                 |  131 +-
 security/landlock/Kconfig                    |    1 +
 security/landlock/Makefile                   |    2 +
 security/landlock/fs.c                       |  255 ++--
 security/landlock/limits.h                   |    7 +-
 security/landlock/net.c                      |  200 +++
 security/landlock/net.h                      |   26 +
 security/landlock/ruleset.c                  |  409 +++++--
 security/landlock/ruleset.h                  |  185 ++-
 security/landlock/setup.c                    |    2 +
 security/landlock/syscalls.c                 |  165 ++-
 tools/testing/selftests/landlock/base_test.c |    2 +-
 tools/testing/selftests/landlock/common.h    |   10 +
 tools/testing/selftests/landlock/config      |    4 +
 tools/testing/selftests/landlock/fs_test.c   |   75 +-
 tools/testing/selftests/landlock/net_test.c  | 1157 ++++++++++++++++++
 18 files changed, 2398 insertions(+), 354 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] 74+ messages in thread

* [PATCH v9 01/12] landlock: Make ruleset's access masks more generic
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type Konstantin Meskhidze
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

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

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

Changes since v8:
* Minor fixes.

Changes since v7:
* Refactors commit message.

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

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

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

Changes since v4:
* Deletes struct landlock_access_mask.

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

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

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


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

* [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:34   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

From: Mickaël Salaün <mic@digikod.net>

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

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

Remove unnecessary inlining.

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

Changes since v8:
* Refactors get_handled_fs_accesses().
* Adds landlock_get_raw_fs_access_mask() helper.

---
 security/landlock/fs.c       | 73 ++++++++++++++++++------------------
 security/landlock/ruleset.h  | 25 +++++++++++-
 security/landlock/syscalls.c |  6 +--
 3 files changed, 62 insertions(+), 42 deletions(-)

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


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

* [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:36   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 04/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

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

This patch also adds is_object_pointer() and get_root() helpers.
is_object_pointer() returns true if key type is LANDLOCK_KEY_INODE.
get_root() helper returns a red_black tree root pointer according to
a key type.

Refactor landlock_insert_rule() and landlock_find_rule() to support coming
network modifications. Adding or searching a rule in ruleset can now be
done thanks to a Landlock ID argument passed to these helpers.

Remove unnecessary inlining.

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

Changes since v8:
* Refactors commit message.
* Removes inlining.
* Minor fixes.

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

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

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

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

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

---
 security/landlock/fs.c      |  49 ++++++------
 security/landlock/ruleset.c | 148 +++++++++++++++++++++++++-----------
 security/landlock/ruleset.h |  65 +++++++++++++---
 3 files changed, 185 insertions(+), 77 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 0ae54a639e16..273ed8549da1 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -158,7 +158,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 			    access_mask_t access_rights)
 {
 	int err;
-	struct landlock_object *object;
+	struct landlock_id id = {
+		.type = LANDLOCK_KEY_INODE,
+	};
 
 	/* Files only get access rights that make sense. */
 	if (!d_is_dir(path->dentry) &&
@@ -170,17 +172,17 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
 	/* Transforms relative access rights to absolute ones. */
 	access_rights |= LANDLOCK_MASK_ACCESS_FS &
 			 ~landlock_get_fs_access_mask(ruleset, 0);
-	object = get_inode_object(d_backing_inode(path->dentry));
-	if (IS_ERR(object))
-		return PTR_ERR(object);
+	id.key.object = get_inode_object(d_backing_inode(path->dentry));
+	if (IS_ERR(id.key.object))
+		return PTR_ERR(id.key.object);
 	mutex_lock(&ruleset->lock);
-	err = landlock_insert_rule(ruleset, object, access_rights);
+	err = landlock_insert_rule(ruleset, id, access_rights);
 	mutex_unlock(&ruleset->lock);
 	/*
 	 * No need to check for an error because landlock_insert_rule()
 	 * increments the refcount for the new object if needed.
 	 */
-	landlock_put_object(object);
+	landlock_put_object(id.key.object);
 	return err;
 }
 
@@ -191,12 +193,15 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
  *
  * Returns NULL if no rule is found or if @dentry is negative.
  */
-static inline const struct landlock_rule *
+static const struct landlock_rule *
 find_rule(const struct landlock_ruleset *const domain,
 	  const struct dentry *const dentry)
 {
 	const struct landlock_rule *rule;
 	const struct inode *inode;
+	struct landlock_id id = {
+		.type = LANDLOCK_KEY_INODE,
+	};
 
 	/* Ignores nonexistent leafs. */
 	if (d_is_negative(dentry))
@@ -204,8 +209,8 @@ find_rule(const struct landlock_ruleset *const domain,
 
 	inode = d_backing_inode(dentry);
 	rcu_read_lock();
-	rule = landlock_find_rule(
-		domain, rcu_dereference(landlock_inode(inode)->object));
+	id.key.object = rcu_dereference(landlock_inode(inode)->object);
+	rule = landlock_find_rule(domain, id);
 	rcu_read_unlock();
 	return rule;
 }
@@ -217,7 +222,7 @@ find_rule(const struct landlock_ruleset *const domain,
  * Returns true if the request is allowed (i.e. relevant layer masks for the
  * request are empty).
  */
-static inline bool
+static 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])
@@ -269,7 +274,7 @@ unmask_layers(const struct landlock_rule *const rule,
  * sockfs, pipefs), but can still be reachable through
  * /proc/<pid>/fd/<file-descriptor>
  */
-static inline bool is_nouser_or_private(const struct dentry *dentry)
+static bool is_nouser_or_private(const struct dentry *dentry)
 {
 	return (dentry->d_sb->s_flags & SB_NOUSER) ||
 	       (d_is_positive(dentry) &&
@@ -301,7 +306,7 @@ get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
  * Returns: An access mask where each access right bit is set which is handled
  * in any of the active layers in @domain.
  */
-static inline access_mask_t
+static 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])
@@ -356,7 +361,7 @@ static const struct landlock_ruleset *get_current_fs_domain(void)
  *
  * @layer_masks_child2: Optional child masks.
  */
-static inline bool no_more_access(
+static bool no_more_access(
 	const layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
 	const layer_mask_t (*const layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS],
 	const bool child1_is_directory,
@@ -408,7 +413,7 @@ static inline bool no_more_access(
  *
  * Returns true if the request is allowed, false otherwise.
  */
-static inline bool
+static bool
 scope_to_request(const access_mask_t access_request,
 		 layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
 {
@@ -427,7 +432,7 @@ scope_to_request(const access_mask_t access_request,
  * Returns true if there is at least one access right different than
  * LANDLOCK_ACCESS_FS_REFER.
  */
-static inline bool
+static bool
 is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS],
 	  const access_mask_t access_request)
 {
@@ -638,9 +643,9 @@ static bool is_access_to_paths_allowed(
 	return allowed_parent1 && allowed_parent2;
 }
 
-static inline int check_access_path(const struct landlock_ruleset *const domain,
-				    const struct path *const path,
-				    access_mask_t access_request)
+static int check_access_path(const struct landlock_ruleset *const domain,
+			     const struct path *const path,
+			     access_mask_t access_request)
 {
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
 
@@ -652,7 +657,7 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
 }
 
 static int current_check_access_path(const struct path *const path,
-					    const access_mask_t access_request)
+				     const access_mask_t access_request)
 {
 	const struct landlock_ruleset *const dom = get_current_fs_domain();
 
@@ -661,7 +666,7 @@ static int current_check_access_path(const struct path *const path,
 	return check_access_path(dom, path, access_request);
 }
 
-static inline access_mask_t get_mode_access(const umode_t mode)
+static access_mask_t get_mode_access(const umode_t mode)
 {
 	switch (mode & S_IFMT) {
 	case S_IFLNK:
@@ -686,7 +691,7 @@ static inline access_mask_t get_mode_access(const umode_t mode)
 	}
 }
 
-static inline access_mask_t maybe_remove(const struct dentry *const dentry)
+static access_mask_t maybe_remove(const struct dentry *const dentry)
 {
 	if (d_is_negative(dentry))
 		return 0;
@@ -1170,7 +1175,7 @@ static int hook_path_truncate(const struct path *const path)
  * Returns the access rights that are required for opening the given file,
  * depending on the file type and open mode.
  */
-static inline access_mask_t
+static access_mask_t
 get_required_file_open_access(const struct file *const file)
 {
 	access_mask_t access = 0;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 1f3188b4e313..c5c88a100f74 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -35,7 +35,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 		return ERR_PTR(-ENOMEM);
 	refcount_set(&new_ruleset->usage, 1);
 	mutex_init(&new_ruleset->lock);
-	new_ruleset->root = RB_ROOT;
+	new_ruleset->root_inode = RB_ROOT;
 	new_ruleset->num_layers = num_layers;
 	/*
 	 * hierarchy = NULL
@@ -68,8 +68,18 @@ static void build_check_rule(void)
 	BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
 }
 
+static bool is_object_pointer(const enum landlock_key_type key_type)
+{
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		return true;
+	}
+	WARN_ON_ONCE(1);
+	return false;
+}
+
 static struct landlock_rule *
-create_rule(struct landlock_object *const object,
+create_rule(const struct landlock_id id,
 	    const struct landlock_layer (*const layers)[], const u32 num_layers,
 	    const struct landlock_layer *const new_layer)
 {
@@ -90,8 +100,13 @@ create_rule(struct landlock_object *const object,
 	if (!new_rule)
 		return ERR_PTR(-ENOMEM);
 	RB_CLEAR_NODE(&new_rule->node);
-	landlock_get_object(object);
-	new_rule->object = object;
+	if (is_object_pointer(id.type)) {
+		/* This should be catched by insert_rule(). */
+		WARN_ON_ONCE(!id.key.object);
+		landlock_get_object(id.key.object);
+	}
+
+	new_rule->key = id.key;
 	new_rule->num_layers = new_num_layers;
 	/* Copies the original layer stack. */
 	memcpy(new_rule->layers, layers,
@@ -102,12 +117,29 @@ create_rule(struct landlock_object *const object,
 	return new_rule;
 }
 
-static void free_rule(struct landlock_rule *const rule)
+static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
+				const enum landlock_key_type key_type)
+{
+	struct rb_root *root = NULL;
+
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		root = &ruleset->root_inode;
+		break;
+	}
+	if (WARN_ON_ONCE(!root))
+		return ERR_PTR(-EINVAL);
+	return root;
+}
+
+static void free_rule(struct landlock_rule *const rule,
+		      const enum landlock_key_type key_type)
 {
 	might_sleep();
 	if (!rule)
 		return;
-	landlock_put_object(rule->object);
+	if (is_object_pointer(key_type))
+		landlock_put_object(rule->key.object);
 	kfree(rule);
 }
 
@@ -129,8 +161,8 @@ static void build_check_ruleset(void)
  * insert_rule - Create and insert a rule in a ruleset
  *
  * @ruleset: The ruleset to be updated.
- * @object: The object to build the new rule with.  The underlying kernel
- *          object must be held by the caller.
+ * @id: The ID to build the new rule with.  The underlying kernel object, if
+ *      any, must be held by the caller.
  * @layers: One or multiple layers to be copied into the new rule.
  * @num_layers: The number of @layers entries.
  *
@@ -144,26 +176,37 @@ static void build_check_ruleset(void)
  * access rights.
  */
 static int insert_rule(struct landlock_ruleset *const ruleset,
-		       struct landlock_object *const object,
+		       const struct landlock_id id,
 		       const struct landlock_layer (*const layers)[],
-		       size_t num_layers)
+		       const size_t num_layers)
 {
 	struct rb_node **walker_node;
 	struct rb_node *parent_node = NULL;
 	struct landlock_rule *new_rule;
+	struct rb_root *root;
 
 	might_sleep();
 	lockdep_assert_held(&ruleset->lock);
-	if (WARN_ON_ONCE(!object || !layers))
+	if (WARN_ON_ONCE(!layers))
 		return -ENOENT;
-	walker_node = &(ruleset->root.rb_node);
+
+	if (is_object_pointer(id.type)) {
+		if (WARN_ON_ONCE(!id.key.object))
+			return -ENOENT;
+	}
+
+	root = get_root(ruleset, id.type);
+	if (IS_ERR(root))
+		return PTR_ERR(root);
+
+	walker_node = &root->rb_node;
 	while (*walker_node) {
 		struct landlock_rule *const this =
 			rb_entry(*walker_node, struct landlock_rule, node);
 
-		if (this->object != object) {
+		if (this->key.data != id.key.data) {
 			parent_node = *walker_node;
-			if (this->object < object)
+			if (this->key.data < id.key.data)
 				walker_node = &((*walker_node)->rb_right);
 			else
 				walker_node = &((*walker_node)->rb_left);
@@ -195,24 +238,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
 		 * Intersects access rights when it is a merge between a
 		 * ruleset and a domain.
 		 */
-		new_rule = create_rule(object, &this->layers, this->num_layers,
+		new_rule = create_rule(id, &this->layers, this->num_layers,
 				       &(*layers)[0]);
 		if (IS_ERR(new_rule))
 			return PTR_ERR(new_rule);
-		rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
-		free_rule(this);
+		rb_replace_node(&this->node, &new_rule->node, root);
+		free_rule(this, id.type);
 		return 0;
 	}
 
-	/* There is no match for @object. */
+	/* There is no match for @id. */
 	build_check_ruleset();
 	if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
 		return -E2BIG;
-	new_rule = create_rule(object, layers, num_layers, NULL);
+	new_rule = create_rule(id, layers, num_layers, NULL);
 	if (IS_ERR(new_rule))
 		return PTR_ERR(new_rule);
 	rb_link_node(&new_rule->node, parent_node, walker_node);
-	rb_insert_color(&new_rule->node, &ruleset->root);
+	rb_insert_color(&new_rule->node, root);
 	ruleset->num_rules++;
 	return 0;
 }
@@ -230,7 +273,7 @@ static void build_check_layer(void)
 
 /* @ruleset must be locked by the caller. */
 int landlock_insert_rule(struct landlock_ruleset *const ruleset,
-			 struct landlock_object *const object,
+			 const struct landlock_id id,
 			 const access_mask_t access)
 {
 	struct landlock_layer layers[] = { {
@@ -240,10 +283,10 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
 	} };
 
 	build_check_layer();
-	return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers));
+	return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
 }
 
-static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
+static void get_hierarchy(struct landlock_hierarchy *const hierarchy)
 {
 	if (hierarchy)
 		refcount_inc(&hierarchy->usage);
@@ -263,6 +306,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 			 struct landlock_ruleset *const src)
 {
 	struct landlock_rule *walker_rule, *next_rule;
+	struct rb_root *src_root;
 	int err = 0;
 
 	might_sleep();
@@ -273,6 +317,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
 		return -EINVAL;
 
+	src_root = get_root(src, LANDLOCK_KEY_INODE);
+	if (IS_ERR(src_root))
+		return PTR_ERR(src_root);
+
 	/* Locks @dst first because we are its only owner. */
 	mutex_lock(&dst->lock);
 	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
@@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
 
 	/* Merges the @src tree. */
-	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
+	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
 					     node) {
 		struct landlock_layer layers[] = { {
 			.level = dst->num_layers,
 		} };
+		const struct landlock_id id = {
+			.key = walker_rule->key,
+			.type = LANDLOCK_KEY_INODE,
+		};
 
-		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
-			err = -EINVAL;
-			goto out_unlock;
-		}
-		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
-			err = -EINVAL;
-			goto out_unlock;
-		}
+		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
+			return -EINVAL;
+		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
+			return -EINVAL;
 		layers[0].access = walker_rule->layers[0].access;
-		err = insert_rule(dst, walker_rule->object, &layers,
-				  ARRAY_SIZE(layers));
+
+		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
 		if (err)
 			goto out_unlock;
 	}
@@ -316,21 +364,29 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 			   struct landlock_ruleset *const child)
 {
 	struct landlock_rule *walker_rule, *next_rule;
+	struct rb_root *parent_root;
 	int err = 0;
 
 	might_sleep();
 	if (!parent)
 		return 0;
 
+	parent_root = get_root(parent, LANDLOCK_KEY_INODE);
+	if (IS_ERR(parent_root))
+		return PTR_ERR(parent_root);
+
 	/* Locks @child first because we are its only owner. */
 	mutex_lock(&child->lock);
 	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
 
 	/* Copies the @parent tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
-					     &parent->root, node) {
-		err = insert_rule(child, walker_rule->object,
-				  &walker_rule->layers,
+					     parent_root, node) {
+		const struct landlock_id id = {
+			.key = walker_rule->key,
+			.type = LANDLOCK_KEY_INODE,
+		};
+		err = insert_rule(child, id, &walker_rule->layers,
 				  walker_rule->num_layers);
 		if (err)
 			goto out_unlock;
@@ -362,8 +418,9 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
 	struct landlock_rule *freeme, *next;
 
 	might_sleep();
-	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node)
-		free_rule(freeme);
+	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+					     node)
+		free_rule(freeme, LANDLOCK_KEY_INODE);
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
@@ -454,20 +511,23 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
  */
 const struct landlock_rule *
 landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_object *const object)
+		   const struct landlock_id id)
 {
+	const struct rb_root *root;
 	const struct rb_node *node;
 
-	if (!object)
+	root = get_root((struct landlock_ruleset *)ruleset, id.type);
+	if (IS_ERR(root))
 		return NULL;
-	node = ruleset->root.rb_node;
+	node = root->rb_node;
+
 	while (node) {
 		struct landlock_rule *this =
 			rb_entry(node, struct landlock_rule, node);
 
-		if (this->object == object)
+		if (this->key.data == id.key.data)
 			return this;
-		if (this->object < object)
+		if (this->key.data < id.key.data)
 			node = node->rb_right;
 		else
 			node = node->rb_left;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index c6db528817c5..502270c7d612 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -60,6 +60,47 @@ struct landlock_layer {
 	access_mask_t access;
 };
 
+/**
+ * union landlock_key - Key of a ruleset's red-black tree
+ */
+union landlock_key {
+	/**
+	 * @object: Pointer to identify a kernel object (e.g. an inode).
+	 */
+	struct landlock_object *object;
+	/**
+	 * @data: Raw data to identify an arbitrary 32-bit value
+	 * (e.g. a TCP port).
+	 */
+	uintptr_t data;
+};
+
+/**
+ * enum landlock_key_type - Type of &union landlock_key
+ */
+enum landlock_key_type {
+	/**
+	 * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
+	 * keys.
+	 */
+	LANDLOCK_KEY_INODE = 1,
+};
+
+/**
+ * struct landlock_id - Unique rule identifier for a ruleset
+ */
+struct landlock_id {
+	/**
+	 * @key: Identifies either a kernel object (e.g. an inode) or
+	 * a raw value (e.g. a TCP port).
+	 */
+	union landlock_key key;
+	/**
+	 * @type: Type of a landlock_ruleset's root tree.
+	 */
+	const enum landlock_key_type type;
+};
+
 /**
  * struct landlock_rule - Access rights tied to an object
  */
@@ -69,12 +110,13 @@ struct landlock_rule {
 	 */
 	struct rb_node node;
 	/**
-	 * @object: Pointer to identify a kernel object (e.g. an inode).  This
-	 * is used as a key for this ruleset element.  This pointer is set once
-	 * and never modified.  It always points to an allocated object because
-	 * each rule increments the refcount of its object.
+	 * @key: A union to identify either a kernel object (e.g. an inode) or
+	 * a raw data value (e.g. a network socket port). This is used as a key
+	 * for this ruleset element.  The pointer is set once and never
+	 * modified.  It always points to an allocated object because each rule
+	 * increments the refcount of its object.
 	 */
-	struct landlock_object *object;
+	union landlock_key key;
 	/**
 	 * @num_layers: Number of entries in @layers.
 	 */
@@ -110,11 +152,12 @@ struct landlock_hierarchy {
  */
 struct landlock_ruleset {
 	/**
-	 * @root: Root of a red-black tree containing &struct landlock_rule
-	 * nodes.  Once a ruleset is tied to a process (i.e. as a domain), this
-	 * tree is immutable until @usage reaches zero.
+	 * @root_inode: Root of a red-black tree containing &struct
+	 * landlock_rule nodes with inode object.  Once a ruleset is tied to a
+	 * process (i.e. as a domain), this tree is immutable until @usage
+	 * reaches zero.
 	 */
-	struct rb_root root;
+	struct rb_root root_inode;
 	/**
 	 * @hierarchy: Enables hierarchy identification even when a parent
 	 * domain vanishes.  This is needed for the ptrace protection.
@@ -176,7 +219,7 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
 
 int landlock_insert_rule(struct landlock_ruleset *const ruleset,
-			 struct landlock_object *const object,
+			 const struct landlock_id id,
 			 const access_mask_t access);
 
 struct landlock_ruleset *
@@ -185,7 +228,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
 
 const struct landlock_rule *
 landlock_find_rule(const struct landlock_ruleset *const ruleset,
-		   const struct landlock_object *const object);
+		   const struct landlock_id id);
 
 static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
 {
-- 
2.25.1


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

* [PATCH v9 04/12] landlock: Refactor merge/inherit_ruleset functions
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (2 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks() Konstantin Meskhidze
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Refactor merge_ruleset() and inherit_ruleset() functions to support
new rule types. This patch adds merge_tree() and inherit_tree()
helpers. They use a specific ruleset's red-black tree according to
a key type argument.

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

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

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

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

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

Changes since v4:
* None

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

diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index c5c88a100f74..3e1cffda128e 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -302,36 +302,22 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy)
 	}
 }
 
-static int merge_ruleset(struct landlock_ruleset *const dst,
-			 struct landlock_ruleset *const src)
+static int merge_tree(struct landlock_ruleset *const dst,
+		      struct landlock_ruleset *const src,
+		      const enum landlock_key_type key_type)
 {
 	struct landlock_rule *walker_rule, *next_rule;
 	struct rb_root *src_root;
 	int err = 0;
 
 	might_sleep();
-	/* Should already be checked by landlock_merge_ruleset() */
-	if (WARN_ON_ONCE(!src))
-		return 0;
-	/* Only merge into a domain. */
-	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
-		return -EINVAL;
+	lockdep_assert_held(&dst->lock);
+	lockdep_assert_held(&src->lock);
 
-	src_root = get_root(src, LANDLOCK_KEY_INODE);
+	src_root = get_root(src, key_type);
 	if (IS_ERR(src_root))
 		return PTR_ERR(src_root);
 
-	/* Locks @dst first because we are its only owner. */
-	mutex_lock(&dst->lock);
-	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
-
-	/* Stacks the new layer. */
-	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
-		err = -EINVAL;
-		goto out_unlock;
-	}
-	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
-
 	/* Merges the @src tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
 					     node) {
@@ -340,7 +326,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 		} };
 		const struct landlock_id id = {
 			.key = walker_rule->key,
-			.type = LANDLOCK_KEY_INODE,
+			.type = key_type,
 		};
 
 		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
@@ -351,8 +337,39 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 
 		err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
 		if (err)
-			goto out_unlock;
+			return err;
+	}
+	return err;
+}
+
+static int merge_ruleset(struct landlock_ruleset *const dst,
+			 struct landlock_ruleset *const src)
+{
+	int err = 0;
+
+	might_sleep();
+	/* Should already be checked by landlock_merge_ruleset() */
+	if (WARN_ON_ONCE(!src))
+		return 0;
+	/* Only merge into a domain. */
+	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
+		return -EINVAL;
+
+	/* Locks @dst first because we are its only owner. */
+	mutex_lock(&dst->lock);
+	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
+
+	/* Stacks the new layer. */
+	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
+		err = -EINVAL;
+		goto out_unlock;
 	}
+	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
+
+	/* Merges the @src inode tree. */
+	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
+	if (err)
+		goto out_unlock;
 
 out_unlock:
 	mutex_unlock(&src->lock);
@@ -360,43 +377,64 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	return err;
 }
 
-static int inherit_ruleset(struct landlock_ruleset *const parent,
-			   struct landlock_ruleset *const child)
+static int inherit_tree(struct landlock_ruleset *const parent,
+			struct landlock_ruleset *const child,
+			const enum landlock_key_type key_type)
 {
 	struct landlock_rule *walker_rule, *next_rule;
 	struct rb_root *parent_root;
 	int err = 0;
 
 	might_sleep();
-	if (!parent)
-		return 0;
+	lockdep_assert_held(&parent->lock);
+	lockdep_assert_held(&child->lock);
 
-	parent_root = get_root(parent, LANDLOCK_KEY_INODE);
+	parent_root = get_root(parent, key_type);
 	if (IS_ERR(parent_root))
 		return PTR_ERR(parent_root);
 
-	/* Locks @child first because we are its only owner. */
-	mutex_lock(&child->lock);
-	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
-
-	/* Copies the @parent tree. */
+	/* Copies the @parent inode or network tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
 					     parent_root, node) {
 		const struct landlock_id id = {
 			.key = walker_rule->key,
-			.type = LANDLOCK_KEY_INODE,
+			.type = key_type,
 		};
+
 		err = insert_rule(child, id, &walker_rule->layers,
 				  walker_rule->num_layers);
 		if (err)
-			goto out_unlock;
+			return err;
 	}
+	return err;
+}
+
+static int inherit_ruleset(struct landlock_ruleset *const parent,
+			   struct landlock_ruleset *const child)
+{
+	int err = 0;
+
+	might_sleep();
+	if (!parent)
+		return 0;
+
+	/* Locks @child first because we are its only owner. */
+	mutex_lock(&child->lock);
+	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
+
+	/* Copies the @parent inode tree. */
+	err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
+	if (err)
+		goto out_unlock;
 
 	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
 		err = -EINVAL;
 		goto out_unlock;
 	}
-	/* Copies the parent layer stack and leaves a space for the new layer. */
+	/*
+	 * Copies the parent layer stack and leaves a space
+	 * for the new layer.
+	 */
 	memcpy(child->access_masks, parent->access_masks,
 	       flex_array_size(parent, access_masks, parent->num_layers));
 
-- 
2.25.1


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

* [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks()
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (3 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 04/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:37   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks() Konstantin Meskhidze
                   ` (7 subsequent siblings)
  12 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

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

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

Changes since v8:
* Refactors commit message.
* Adds "landlock_" prefix for moved helpers.

Changes since v7:
* Refactors commit message.

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

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

---
 security/landlock/fs.c      | 136 ++++++------------------------------
 security/landlock/ruleset.c |  98 ++++++++++++++++++++++++++
 security/landlock/ruleset.h |   9 +++
 3 files changed, 128 insertions(+), 115 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 273ed8549da1..73a7399f93ba 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -215,60 +215,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 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
@@ -293,50 +239,6 @@ get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
 	return access_dom & LANDLOCK_MASK_ACCESS_FS;
 }
 
-/**
- * init_layer_masks - Initialize layer masks from an access request
- *
- * Populates @layer_masks such that for each access right in @access_request,
- * the bits for all the layers are set where this access right is handled.
- *
- * @domain: The domain that defines the current restrictions.
- * @access_request: The requested access rights to check.
- * @layer_masks: The layer masks to populate.
- *
- * Returns: An access mask where each access right bit is set which is handled
- * in any of the active layers in @domain.
- */
-static 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 (BIT_ULL(access_bit) &
-			    landlock_get_fs_access_mask(domain, layer_level)) {
-				(*layer_masks)[access_bit] |=
-					BIT_ULL(layer_level);
-				handled_accesses |= BIT_ULL(access_bit);
-			}
-		}
-	}
-	return handled_accesses;
-}
-
 static access_mask_t
 get_handled_fs_accesses(const struct landlock_ruleset *const domain)
 {
@@ -539,18 +441,20 @@ static bool is_access_to_paths_allowed(
 	}
 
 	if (unlikely(dentry_child1)) {
-		unmask_layers(find_rule(domain, dentry_child1),
-			      init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
+		landlock_unmask_layers(find_rule(domain, dentry_child1),
+				       landlock_init_layer_masks(
+					       domain, LANDLOCK_MASK_ACCESS_FS,
 					       &_layer_masks_child1),
-			      &_layer_masks_child1);
+				       &_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,
+		landlock_unmask_layers(find_rule(domain, dentry_child2),
+				       landlock_init_layer_masks(
+					       domain, LANDLOCK_MASK_ACCESS_FS,
 					       &_layer_masks_child2),
-			      &_layer_masks_child2);
+				       &_layer_masks_child2);
 		layer_masks_child2 = &_layer_masks_child2;
 		child2_is_directory = d_is_dir(dentry_child2);
 	}
@@ -602,10 +506,10 @@ static bool is_access_to_paths_allowed(
 		}
 
 		rule = find_rule(domain, walker_path.dentry);
-		allowed_parent1 = unmask_layers(rule, access_masked_parent1,
-						layer_masks_parent1);
-		allowed_parent2 = unmask_layers(rule, access_masked_parent2,
-						layer_masks_parent2);
+		allowed_parent1 = landlock_unmask_layers(
+			rule, access_masked_parent1, layer_masks_parent1);
+		allowed_parent2 = landlock_unmask_layers(
+			rule, access_masked_parent2, layer_masks_parent2);
 
 		/* Stops when a rule from each layer grants access. */
 		if (allowed_parent1 && allowed_parent2)
@@ -649,7 +553,8 @@ static 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 =
+		landlock_init_layer_masks(domain, access_request, &layer_masks);
 	if (is_access_to_paths_allowed(domain, path, access_request,
 				       &layer_masks, NULL, 0, NULL, NULL))
 		return 0;
@@ -734,16 +639,16 @@ static bool collect_domain_accesses(
 	if (is_nouser_or_private(dir))
 		return true;
 
-	access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
-				      layer_masks_dom);
+	access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
+					       layer_masks_dom);
 
 	dget(dir);
 	while (true) {
 		struct dentry *parent_dentry;
 
 		/* Gets all layers allowing all domain accesses. */
-		if (unmask_layers(find_rule(domain, dir), access_dom,
-				  layer_masks_dom)) {
+		if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
+					   layer_masks_dom)) {
 			/*
 			 * Stops when all handled accesses are allowed by at
 			 * least one rule in each layer.
@@ -856,7 +761,7 @@ 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(
+		access_request_parent1 = landlock_init_layer_masks(
 			dom, access_request_parent1 | access_request_parent2,
 			&layer_masks_parent1);
 		if (is_access_to_paths_allowed(
@@ -1233,7 +1138,8 @@ static int hook_file_open(struct file *const file)
 
 	if (is_access_to_paths_allowed(
 		    dom, &file->f_path,
-		    init_layer_masks(dom, full_access_request, &layer_masks),
+		    landlock_init_layer_masks(dom, full_access_request,
+					      &layer_masks),
 		    &layer_masks, NULL, 0, NULL, NULL)) {
 		allowed_access = full_access_request;
 	} else {
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 3e1cffda128e..22590cac3d56 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -572,3 +572,101 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
 	}
 	return NULL;
 }
+
+/*
+ * @layer_masks is read and may be updated according to the access request and
+ * the matching rule.
+ *
+ * Returns true if the request is allowed (i.e. relevant layer masks for the
+ * request are empty).
+ */
+bool landlock_unmask_layers(
+	const struct landlock_rule *const rule,
+	const access_mask_t access_request,
+	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+{
+	size_t layer_level;
+
+	if (!access_request || !layer_masks)
+		return true;
+	if (!rule)
+		return false;
+
+	/*
+	 * An access is granted if, for each policy layer, at least one rule
+	 * encountered on the pathwalk grants the requested access,
+	 * regardless of its position in the layer stack.  We must then check
+	 * the remaining layers for each inode, from the first added layer to
+	 * the last one.  When there is multiple requested accesses, for each
+	 * policy layer, the full set of requested accesses may not be granted
+	 * by only one rule, but by the union (binary OR) of multiple rules.
+	 * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
+	 */
+	for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
+		const struct landlock_layer *const layer =
+			&rule->layers[layer_level];
+		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
+		const unsigned long access_req = access_request;
+		unsigned long access_bit;
+		bool is_empty;
+
+		/*
+		 * Records in @layer_masks which layer grants access to each
+		 * requested access.
+		 */
+		is_empty = true;
+		for_each_set_bit(access_bit, &access_req,
+				 ARRAY_SIZE(*layer_masks)) {
+			if (layer->access & BIT_ULL(access_bit))
+				(*layer_masks)[access_bit] &= ~layer_bit;
+			is_empty = is_empty && !(*layer_masks)[access_bit];
+		}
+		if (is_empty)
+			return true;
+	}
+	return false;
+}
+
+/*
+ * init_layer_masks - Initialize layer masks from an access request
+ *
+ * Populates @layer_masks such that for each access right in @access_request,
+ * the bits for all the layers are set where this access right is handled.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @layer_masks: The layer masks to populate.
+ *
+ * Returns: An access mask where each access right bit is set which is handled
+ * in any of the active layers in @domain.
+ */
+access_mask_t landlock_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 (BIT_ULL(access_bit) &
+			    landlock_get_fs_access_mask(domain, layer_level)) {
+				(*layer_masks)[access_bit] |=
+					BIT_ULL(layer_level);
+				handled_accesses |= BIT_ULL(access_bit);
+			}
+		}
+	}
+	return handled_accesses;
+}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 502270c7d612..60a3c4d4d961 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -266,5 +266,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
 	       ACCESS_FS_INITIALLY_DENIED;
 }
+bool landlock_unmask_layers(
+	const struct landlock_rule *const rule,
+	const access_mask_t access_request,
+	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
+
+access_mask_t landlock_init_layer_masks(
+	const struct landlock_ruleset *const domain,
+	const access_mask_t access_request,
+	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
 
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
-- 
2.25.1


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

* [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks()
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (4 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks() Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:38   ` Mickaël Salaün
  2023-02-21 18:07   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
                   ` (6 subsequent siblings)
  12 siblings, 2 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Add new key_type argument to the landlock_init_layer_masks() helper.
Add a masks_array_size argument to the landlock_unmask_layers() helper.
These modifications support implementing new rule types in the next
Landlock versions.

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

Changes since v8:
* None.

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

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

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

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

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

---
 security/landlock/fs.c      | 43 ++++++++++++++++--------------
 security/landlock/ruleset.c | 52 ++++++++++++++++++++++++++-----------
 security/landlock/ruleset.h | 17 ++++++------
 3 files changed, 70 insertions(+), 42 deletions(-)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 73a7399f93ba..a73dbd3f9ddb 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -441,20 +441,22 @@ static bool is_access_to_paths_allowed(
 	}
 
 	if (unlikely(dentry_child1)) {
-		landlock_unmask_layers(find_rule(domain, dentry_child1),
-				       landlock_init_layer_masks(
-					       domain, LANDLOCK_MASK_ACCESS_FS,
-					       &_layer_masks_child1),
-				       &_layer_masks_child1);
+		landlock_unmask_layers(
+			find_rule(domain, dentry_child1),
+			landlock_init_layer_masks(
+				domain, LANDLOCK_MASK_ACCESS_FS,
+				&_layer_masks_child1, LANDLOCK_KEY_INODE),
+			&_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
 		layer_masks_child1 = &_layer_masks_child1;
 		child1_is_directory = d_is_dir(dentry_child1);
 	}
 	if (unlikely(dentry_child2)) {
-		landlock_unmask_layers(find_rule(domain, dentry_child2),
-				       landlock_init_layer_masks(
-					       domain, LANDLOCK_MASK_ACCESS_FS,
-					       &_layer_masks_child2),
-				       &_layer_masks_child2);
+		landlock_unmask_layers(
+			find_rule(domain, dentry_child2),
+			landlock_init_layer_masks(
+				domain, LANDLOCK_MASK_ACCESS_FS,
+				&_layer_masks_child2, LANDLOCK_KEY_INODE),
+			&_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
 		layer_masks_child2 = &_layer_masks_child2;
 		child2_is_directory = d_is_dir(dentry_child2);
 	}
@@ -507,14 +509,15 @@ static bool is_access_to_paths_allowed(
 
 		rule = find_rule(domain, walker_path.dentry);
 		allowed_parent1 = landlock_unmask_layers(
-			rule, access_masked_parent1, layer_masks_parent1);
+			rule, access_masked_parent1, layer_masks_parent1,
+			ARRAY_SIZE(*layer_masks_parent1));
 		allowed_parent2 = landlock_unmask_layers(
-			rule, access_masked_parent2, layer_masks_parent2);
+			rule, access_masked_parent2, layer_masks_parent2,
+			ARRAY_SIZE(*layer_masks_parent2));
 
 		/* Stops when a rule from each layer grants access. */
 		if (allowed_parent1 && allowed_parent2)
 			break;
-
 jump_up:
 		if (walker_path.dentry == walker_path.mnt->mnt_root) {
 			if (follow_up(&walker_path)) {
@@ -553,8 +556,8 @@ static int check_access_path(const struct landlock_ruleset *const domain,
 {
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
 
-	access_request =
-		landlock_init_layer_masks(domain, access_request, &layer_masks);
+	access_request = landlock_init_layer_masks(
+		domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
 	if (is_access_to_paths_allowed(domain, path, access_request,
 				       &layer_masks, NULL, 0, NULL, NULL))
 		return 0;
@@ -640,7 +643,8 @@ static bool collect_domain_accesses(
 		return true;
 
 	access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
-					       layer_masks_dom);
+					       layer_masks_dom,
+					       LANDLOCK_KEY_INODE);
 
 	dget(dir);
 	while (true) {
@@ -648,7 +652,8 @@ static bool collect_domain_accesses(
 
 		/* Gets all layers allowing all domain accesses. */
 		if (landlock_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.
@@ -763,7 +768,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 		 */
 		access_request_parent1 = landlock_init_layer_masks(
 			dom, access_request_parent1 | access_request_parent2,
-			&layer_masks_parent1);
+			&layer_masks_parent1, LANDLOCK_KEY_INODE);
 		if (is_access_to_paths_allowed(
 			    dom, new_dir, access_request_parent1,
 			    &layer_masks_parent1, NULL, 0, NULL, NULL))
@@ -1139,7 +1144,7 @@ static int hook_file_open(struct file *const file)
 	if (is_access_to_paths_allowed(
 		    dom, &file->f_path,
 		    landlock_init_layer_masks(dom, full_access_request,
-					      &layer_masks),
+					      &layer_masks, LANDLOCK_KEY_INODE),
 		    &layer_masks, NULL, 0, NULL, NULL)) {
 		allowed_access = full_access_request;
 	} else {
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 22590cac3d56..9748b54b42fe 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -576,14 +576,15 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
 /*
  * @layer_masks is read and may be updated according to the access request and
  * the matching rule.
+ * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
  *
  * Returns true if the request is allowed (i.e. relevant layer masks for the
  * request are empty).
  */
-bool landlock_unmask_layers(
-	const struct landlock_rule *const rule,
-	const access_mask_t access_request,
-	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+			    const access_mask_t access_request,
+			    layer_mask_t (*const layer_masks)[],
+			    const size_t masks_array_size)
 {
 	size_t layer_level;
 
@@ -615,8 +616,7 @@ bool landlock_unmask_layers(
 		 * requested access.
 		 */
 		is_empty = true;
-		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
+		for_each_set_bit(access_bit, &access_req, masks_array_size) {
 			if (layer->access & BIT_ULL(access_bit))
 				(*layer_masks)[access_bit] &= ~layer_bit;
 			is_empty = is_empty && !(*layer_masks)[access_bit];
@@ -627,6 +627,10 @@ bool landlock_unmask_layers(
 	return false;
 }
 
+typedef access_mask_t
+get_access_mask_t(const struct landlock_ruleset *const ruleset,
+		  const u16 layer_level);
+
 /*
  * init_layer_masks - Initialize layer masks from an access request
  *
@@ -636,19 +640,34 @@ bool landlock_unmask_layers(
  * @domain: The domain that defines the current restrictions.
  * @access_request: The requested access rights to check.
  * @layer_masks: The layer masks to populate.
+ * @key_type: The key type to switch between access masks of different types.
  *
  * Returns: An access mask where each access right bit is set which is handled
  * in any of the active layers in @domain.
  */
-access_mask_t landlock_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
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+			  const access_mask_t access_request,
+			  layer_mask_t (*const layer_masks)[],
+			  const enum landlock_key_type key_type)
 {
 	access_mask_t handled_accesses = 0;
-	size_t layer_level;
+	size_t layer_level, num_access;
+	get_access_mask_t *get_access_mask;
+
+	switch (key_type) {
+	case LANDLOCK_KEY_INODE:
+		get_access_mask = landlock_get_fs_access_mask;
+		num_access = LANDLOCK_NUM_ACCESS_FS;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	memset(layer_masks, 0,
+	       array_size(sizeof((*layer_masks)[0]), num_access));
 
-	memset(layer_masks, 0, sizeof(*layer_masks));
 	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
 	if (!access_request)
 		return 0;
@@ -658,10 +677,13 @@ access_mask_t landlock_init_layer_masks(
 		const unsigned long access_req = access_request;
 		unsigned long access_bit;
 
-		for_each_set_bit(access_bit, &access_req,
-				 ARRAY_SIZE(*layer_masks)) {
+		for_each_set_bit(access_bit, &access_req, num_access) {
+			/*
+			 * Artificially handles all initially denied by default
+			 * access rights.
+			 */
 			if (BIT_ULL(access_bit) &
-			    landlock_get_fs_access_mask(domain, layer_level)) {
+			    get_access_mask(domain, layer_level)) {
 				(*layer_masks)[access_bit] |=
 					BIT_ULL(layer_level);
 				handled_accesses |= BIT_ULL(access_bit);
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 60a3c4d4d961..77349764e111 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -266,14 +266,15 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
 	       ACCESS_FS_INITIALLY_DENIED;
 }
-bool landlock_unmask_layers(
-	const struct landlock_rule *const rule,
-	const access_mask_t access_request,
-	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+			    const access_mask_t access_request,
+			    layer_mask_t (*const layer_masks)[],
+			    const size_t masks_array_size);
 
-access_mask_t landlock_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
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+			  const access_mask_t access_request,
+			  layer_mask_t (*const layer_masks)[],
+			  const enum landlock_key_type key_type);
 
 #endif /* _SECURITY_LANDLOCK_RULESET_H */
-- 
2.25.1


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

* [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (5 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks() Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:38   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 08/12] landlock: Add network rules and TCP hooks support Konstantin Meskhidze
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Change the landlock_add_rule() syscall to support new rule types
in future Landlock versions. Add the add_rule_path_beneath() helper
to support current filesystem rules.

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

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

Changes since v7:
* None

Changes since v6:
* None

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

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

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

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

diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index d35cd5d304db..73c80cd2cdbe 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -274,6 +274,49 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
 	return err;
 }
 
+static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
+				 const void __user *const rule_attr)
+{
+	struct landlock_path_beneath_attr path_beneath_attr;
+	struct path path;
+	int res, err;
+	access_mask_t mask;
+
+	/* Copies raw user space buffer, only one type for now. */
+	res = copy_from_user(&path_beneath_attr, rule_attr,
+			     sizeof(path_beneath_attr));
+	if (res)
+		return -EFAULT;
+
+	/*
+	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
+	 * are ignored in path walks.
+	 */
+	if (!path_beneath_attr.allowed_access) {
+		return -ENOMSG;
+	}
+	/*
+	 * Checks that allowed_access matches the @ruleset constraints
+	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
+	 */
+	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
+	if ((path_beneath_attr.allowed_access | mask) != mask) {
+		return -EINVAL;
+	}
+
+	/* Gets and checks the new rule. */
+	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
+	if (err)
+		return err;
+
+	/* Imports the new rule. */
+	err = landlock_append_fs_rule(ruleset, &path,
+				      path_beneath_attr.allowed_access);
+	path_put(&path);
+
+	return err;
+}
+
 /**
  * sys_landlock_add_rule - Add a new rule to a ruleset
  *
@@ -306,11 +349,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 		const enum landlock_rule_type, rule_type,
 		const void __user *const, rule_attr, const __u32, flags)
 {
-	struct landlock_path_beneath_attr path_beneath_attr;
-	struct path path;
 	struct landlock_ruleset *ruleset;
-	int res, err;
-	access_mask_t mask;
+	int err;
 
 	if (!landlock_initialized)
 		return -EOPNOTSUPP;
@@ -324,48 +364,14 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	if (IS_ERR(ruleset))
 		return PTR_ERR(ruleset);
 
-	if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
-		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;
-	}
-
-	/*
-	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
-	 * are ignored in path walks.
-	 */
-	if (!path_beneath_attr.allowed_access) {
-		err = -ENOMSG;
-		goto out_put_ruleset;
-	}
-	/*
-	 * Checks that allowed_access matches the @ruleset constraints
-	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
-	 */
-	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
-	if ((path_beneath_attr.allowed_access | mask) != mask) {
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		err = add_rule_path_beneath(ruleset, rule_attr);
+		break;
+	default:
 		err = -EINVAL;
-		goto out_put_ruleset;
+		break;
 	}
-
-	/* Gets and checks the new rule. */
-	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
-	if (err)
-		goto out_put_ruleset;
-
-	/* Imports the new rule. */
-	err = landlock_append_fs_rule(ruleset, &path,
-				      path_beneath_attr.allowed_access);
-	path_put(&path);
-
-out_put_ruleset:
 	landlock_put_ruleset(ruleset);
 	return err;
 }
-- 
2.25.1


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

* [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (6 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:39   ` Mickaël Salaün
  2023-02-21 18:04   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
                   ` (4 subsequent siblings)
  12 siblings, 2 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

This commit adds network rules support in the ruleset management
helpers and the landlock_create_ruleset syscall.
Refactor user space API to support network actions. Add new network
access flags, network rule and network attributes. Increment Landlock
ABI version. Expand access_masks_t to u32 to be sure network access
rights can be stored. Implement socket_bind() and socket_connect()
LSM hooks, which enable to restrict TCP socket binding and connection
to specific ports.

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

Changes since v8:
* Squashes commits.
* Refactors commit message.
* Changes UAPI port field to __be16.
* Changes logic of bind/connect hooks with AF_UNSPEC families.
* Adds address length checking.
* Minor fixes.

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

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

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

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

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

---
 include/uapi/linux/landlock.h                |  49 +++++
 security/landlock/Kconfig                    |   1 +
 security/landlock/Makefile                   |   2 +
 security/landlock/limits.h                   |   6 +-
 security/landlock/net.c                      | 200 +++++++++++++++++++
 security/landlock/net.h                      |  26 +++
 security/landlock/ruleset.c                  |  52 ++++-
 security/landlock/ruleset.h                  |  63 +++++-
 security/landlock/setup.c                    |   2 +
 security/landlock/syscalls.c                 |  72 ++++++-
 tools/testing/selftests/landlock/base_test.c |   2 +-
 11 files changed, 452 insertions(+), 23 deletions(-)
 create mode 100644 security/landlock/net.c
 create mode 100644 security/landlock/net.h

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f3223f964691..ae11c663c975 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.
+	 */
+	__be16 port;
+
+} __attribute__((packed));
+
 /**
  * DOC: fs_access
  *
@@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
 #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
 /* clang-format on */
 
+/**
+ * DOC: net_access
+ *
+ * Network flags
+ * ~~~~~~~~~~~~~~~~
+ *
+ * These flags enable to restrict a sandboxed process to a set of network
+ * actions.
+ *
+ * TCP sockets with allowed actions:
+ *
+ * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
+ * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
+ *   a remote port.
+ */
+/* clang-format off */
+#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
+#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
+/* clang-format on */
 #endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/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/limits.h b/security/landlock/limits.h
index bafb3b8dc677..8a1a6463c64e 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -23,6 +23,10 @@
 #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
 #define LANDLOCK_SHIFT_ACCESS_FS	0
 
-/* clang-format on */
+#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
+#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
+#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
 
+/* clang-format on */
 #endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
new file mode 100644
index 000000000000..338bd6dd8e3f
--- /dev/null
+++ b/security/landlock/net.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022 Huawei Tech. Co., Ltd.
+ * Copyright © 2022 Microsoft Corporation
+ */
+
+#include <linux/in.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <net/ipv6.h>
+
+#include "common.h"
+#include "cred.h"
+#include "limits.h"
+#include "net.h"
+#include "ruleset.h"
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+			     const __be16 port, access_mask_t access_rights)
+{
+	int err;
+	const struct landlock_id id = {
+		.key.data = port,
+		.type = LANDLOCK_KEY_NET_PORT,
+	};
+	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+	/* Transforms relative access rights to absolute ones. */
+	access_rights |= LANDLOCK_MASK_ACCESS_NET &
+			 ~landlock_get_net_access_mask(ruleset, 0);
+
+	mutex_lock(&ruleset->lock);
+	err = landlock_insert_rule(ruleset, id, access_rights);
+	mutex_unlock(&ruleset->lock);
+
+	return err;
+}
+
+static int check_addrlen(const struct sockaddr *const address, int addrlen)
+{
+	if (addrlen < offsetofend(struct sockaddr, sa_family))
+		return -EINVAL;
+	switch (address->sa_family) {
+	case AF_UNSPEC:
+	case AF_INET:
+		if (addrlen < sizeof(struct sockaddr_in))
+			return -EINVAL;
+		return 0;
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6:
+		if (addrlen < SIN6_LEN_RFC2133)
+			return -EINVAL;
+		return 0;
+#endif
+	}
+	WARN_ON_ONCE(1);
+	return 0;
+}
+
+static int check_socket_access(const struct landlock_ruleset *const domain,
+			       struct sockaddr *address, __be16 port,
+			       access_mask_t access_request)
+{
+	bool allowed = false;
+	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+	const struct landlock_rule *rule;
+	access_mask_t handled_access;
+	const struct landlock_id id = {
+		.key.data = port,
+		.type = LANDLOCK_KEY_NET_PORT,
+	};
+
+	if (WARN_ON_ONCE(!domain))
+		return 0;
+	if (WARN_ON_ONCE(domain->num_layers < 1))
+		return -EACCES;
+
+	switch (address->sa_family) {
+	case AF_UNSPEC:
+		/*
+		 * Connecting to an address with AF_UNSPEC dissolves the TCP
+		 * association, which have the same effect as closing the
+		 * connection while retaining the socket object (i.e., the file
+		 * descriptor).  As for dropping privileges, closing
+		 * connections is always allowed.
+		 */
+		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
+			return 0;
+
+		/*
+		 * For compatibility reason, accept AF_UNSPEC for bind
+		 * accesses (mapped to AF_INET) only if the address is
+		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
+		 * required to not wrongfully return -EACCES instead of
+		 * -EAFNOSUPPORT.
+		 */
+		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+			const struct sockaddr_in *const sockaddr =
+				(struct sockaddr_in *)address;
+
+			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
+				return -EAFNOSUPPORT;
+		}
+
+		fallthrough;
+	case AF_INET:
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6:
+#endif
+		rule = landlock_find_rule(domain, id);
+		handled_access = landlock_init_layer_masks(
+			domain, access_request, &layer_masks,
+			LANDLOCK_KEY_NET_PORT);
+		allowed = landlock_unmask_layers(rule, handled_access,
+						 &layer_masks,
+						 ARRAY_SIZE(layer_masks));
+
+		fallthrough;
+	}
+	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 sockaddr->sin_port;
+	}
+#if IS_ENABLED(CONFIG_IPV6)
+	case AF_INET6: {
+		const struct sockaddr_in6 *const sockaddr_ip6 =
+			(struct sockaddr_in6 *)address;
+		return sockaddr_ip6->sin6_port;
+	}
+#endif
+	}
+	WARN_ON_ONCE(1);
+	return 0;
+}
+
+static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
+			    int addrlen)
+{
+	int ret;
+	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;
+
+	ret = check_addrlen(address, addrlen);
+	if (ret)
+		return ret;
+
+	return check_socket_access(dom, address, get_port(address),
+				   LANDLOCK_ACCESS_NET_BIND_TCP);
+}
+
+static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
+			       int addrlen)
+{
+	int ret;
+	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;
+
+	ret = check_addrlen(address, addrlen);
+	if (ret)
+		return ret;
+
+	return check_socket_access(dom, address, get_port(address),
+				   LANDLOCK_ACCESS_NET_CONNECT_TCP);
+}
+
+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..561784df7ba2
--- /dev/null
+++ b/security/landlock/net.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022 Huawei Tech. Co., Ltd.
+ */
+
+#ifndef _SECURITY_LANDLOCK_NET_H
+#define _SECURITY_LANDLOCK_NET_H
+
+#include "common.h"
+#include "ruleset.h"
+#include "setup.h"
+
+#if IS_ENABLED(CONFIG_INET)
+__init void landlock_add_net_hooks(void);
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+			     const __be16 port, access_mask_t access_rights);
+#else /* IS_ENABLED(CONFIG_INET) */
+static inline void landlock_add_net_hooks(void)
+{
+}
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+#endif /* _SECURITY_LANDLOCK_NET_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 9748b54b42fe..41f31aef6995 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -36,6 +36,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 	refcount_set(&new_ruleset->usage, 1);
 	mutex_init(&new_ruleset->lock);
 	new_ruleset->root_inode = RB_ROOT;
+#if IS_ENABLED(CONFIG_INET)
+	new_ruleset->root_net_port = RB_ROOT;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	new_ruleset->num_layers = num_layers;
 	/*
 	 * hierarchy = NULL
@@ -46,16 +49,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
 }
 
 struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask)
+landlock_create_ruleset(const access_mask_t fs_access_mask,
+			const access_mask_t net_access_mask)
 {
 	struct landlock_ruleset *new_ruleset;
 
 	/* Informs about useless ruleset. */
-	if (!fs_access_mask)
+	if (!fs_access_mask && !net_access_mask)
 		return ERR_PTR(-ENOMSG);
 	new_ruleset = create_ruleset(1);
-	if (!IS_ERR(new_ruleset))
+	if (IS_ERR(new_ruleset))
+		return new_ruleset;
+	if (fs_access_mask)
 		landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
+	if (net_access_mask)
+		landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
 	return new_ruleset;
 }
 
@@ -73,6 +81,10 @@ static bool is_object_pointer(const enum landlock_key_type key_type)
 	switch (key_type) {
 	case LANDLOCK_KEY_INODE:
 		return true;
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		return false;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	}
 	WARN_ON_ONCE(1);
 	return false;
@@ -126,6 +138,11 @@ static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
 	case LANDLOCK_KEY_INODE:
 		root = &ruleset->root_inode;
 		break;
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		root = &ruleset->root_net_port;
+		break;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	}
 	if (WARN_ON_ONCE(!root))
 		return ERR_PTR(-EINVAL);
@@ -154,7 +171,8 @@ static void build_check_ruleset(void)
 	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
 	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
 	BUILD_BUG_ON(access_masks <
-		     (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS));
+		     ((LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) |
+		      (LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET)));
 }
 
 /**
@@ -371,6 +389,12 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
 	if (err)
 		goto out_unlock;
 
+#if IS_ENABLED(CONFIG_INET)
+	/* Merges the @src network port tree. */
+	err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
+	if (err)
+		goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
 out_unlock:
 	mutex_unlock(&src->lock);
 	mutex_unlock(&dst->lock);
@@ -427,6 +451,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
 	if (err)
 		goto out_unlock;
 
+#if IS_ENABLED(CONFIG_INET)
+	/* Copies the @parent network port tree. */
+	err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
+	if (err)
+		goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
 		err = -EINVAL;
 		goto out_unlock;
@@ -459,6 +489,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
 	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
 					     node)
 		free_rule(freeme, LANDLOCK_KEY_INODE);
+#if IS_ENABLED(CONFIG_INET)
+	rbtree_postorder_for_each_entry_safe(freeme, next,
+					     &ruleset->root_net_port, node)
+		free_rule(freeme, LANDLOCK_KEY_NET_PORT);
+#endif /* IS_ENABLED(CONFIG_INET) */
 	put_hierarchy(ruleset->hierarchy);
 	kfree(ruleset);
 }
@@ -639,7 +674,8 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
  *
  * @domain: The domain that defines the current restrictions.
  * @access_request: The requested access rights to check.
- * @layer_masks: The layer masks to populate.
+ * @layer_masks: It must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET
+ * elements according to @key_type.
  * @key_type: The key type to switch between access masks of different types.
  *
  * Returns: An access mask where each access right bit is set which is handled
@@ -660,6 +696,12 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
 		get_access_mask = landlock_get_fs_access_mask;
 		num_access = LANDLOCK_NUM_ACCESS_FS;
 		break;
+#if IS_ENABLED(CONFIG_INET)
+	case LANDLOCK_KEY_NET_PORT:
+		get_access_mask = landlock_get_net_access_mask;
+		num_access = LANDLOCK_NUM_ACCESS_NET;
+		break;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	default:
 		WARN_ON_ONCE(1);
 		return 0;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 77349764e111..84a622af1ac1 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -33,13 +33,16 @@
 typedef u16 access_mask_t;
 /* Makes sure all filesystem access rights can be stored. */
 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
+/* Makes sure all network access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
 /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
 static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
 
 /* Ruleset access masks. */
-typedef u16 access_masks_t;
+typedef u32 access_masks_t;
 /* Makes sure all ruleset access rights can be stored. */
-static_assert(BITS_PER_TYPE(access_masks_t) >= LANDLOCK_NUM_ACCESS_FS);
+static_assert(BITS_PER_TYPE(access_masks_t) >=
+	      LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
 
 typedef u16 layer_mask_t;
 /* Makes sure all layers can be checked. */
@@ -84,6 +87,13 @@ enum landlock_key_type {
 	 * keys.
 	 */
 	LANDLOCK_KEY_INODE = 1,
+#if IS_ENABLED(CONFIG_INET)
+	/**
+	 * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
+	 * node keys.
+	 */
+	LANDLOCK_KEY_NET_PORT = 2,
+#endif /* IS_ENABLED(CONFIG_INET) */
 };
 
 /**
@@ -158,6 +168,15 @@ struct landlock_ruleset {
 	 * reaches zero.
 	 */
 	struct rb_root root_inode;
+#if IS_ENABLED(CONFIG_INET)
+	/**
+	 * @root_net_port: Root of a red-black tree containing &struct
+	 * landlock_rule nodes with network port. Once a ruleset is tied to a
+	 * process (i.e. as a domain), this tree is immutable until @usage
+	 * reaches zero.
+	 */
+	struct rb_root root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
 	/**
 	 * @hierarchy: Enables hierarchy identification even when a parent
 	 * domain vanishes.  This is needed for the ptrace protection.
@@ -196,13 +215,13 @@ struct landlock_ruleset {
 			 */
 			u32 num_layers;
 			/**
-			 * @access_masks: Contains the subset of filesystem
-			 * actions that are restricted by a ruleset.  A domain
-			 * saves all layers of merged rulesets in a stack
-			 * (FAM), starting from the first layer to the last
-			 * one.  These layers are used when merging rulesets,
-			 * for user space backward compatibility (i.e.
-			 * future-proof), and to properly handle merged
+			 * @access_masks: Contains the subset of filesystem and
+			 * network actions that are restricted by a ruleset.
+			 * A domain saves all layers of merged rulesets in a
+			 * stack (FAM), starting from the first layer to the
+			 * last one.  These layers are used when merging
+			 * rulesets, for user space backward compatibility
+			 * (i.e. future-proof), and to properly handle merged
 			 * rulesets without overlapping access rights.  These
 			 * layers are set once and never changed for the
 			 * lifetime of the ruleset.
@@ -213,7 +232,8 @@ struct landlock_ruleset {
 };
 
 struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t access_mask);
+landlock_create_ruleset(const access_mask_t access_mask_fs,
+			const access_mask_t access_mask_net);
 
 void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -249,6 +269,19 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
 		(fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
 }
 
+static inline void
+landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
+			     const access_mask_t net_access_mask,
+			     const u16 layer_level)
+{
+	access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET;
+
+	/* Should already be checked in sys_landlock_create_ruleset(). */
+	WARN_ON_ONCE(net_access_mask != net_mask);
+	ruleset->access_masks[layer_level] |=
+		(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
+}
+
 static inline access_mask_t
 landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
 				const u16 layer_level)
@@ -266,6 +299,16 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
 	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
 	       ACCESS_FS_INITIALLY_DENIED;
 }
+
+static inline access_mask_t
+landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
+			     const u16 layer_level)
+{
+	return (ruleset->access_masks[layer_level] >>
+		LANDLOCK_SHIFT_ACCESS_NET) &
+	       LANDLOCK_MASK_ACCESS_NET;
+}
+
 bool landlock_unmask_layers(const struct landlock_rule *const rule,
 			    const access_mask_t access_request,
 			    layer_mask_t (*const layer_masks)[],
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 3f196d2ce4f9..7e4a598177b8 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -14,6 +14,7 @@
 #include "fs.h"
 #include "ptrace.h"
 #include "setup.h"
+#include "net.h"
 
 bool landlock_initialized __lsm_ro_after_init = false;
 
@@ -29,6 +30,7 @@ static int __init landlock_init(void)
 	landlock_add_cred_hooks();
 	landlock_add_ptrace_hooks();
 	landlock_add_fs_hooks();
+	landlock_add_net_hooks();
 	landlock_initialized = true;
 	pr_info("Up and running.\n");
 	return 0;
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 73c80cd2cdbe..83d35ec40416 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
@@ -82,13 +84,19 @@ 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);
 	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 */
@@ -129,7 +137,7 @@ static const struct file_operations ruleset_fops = {
 	.write = fop_dummy_write,
 };
 
-#define LANDLOCK_ABI_VERSION 3
+#define LANDLOCK_ABI_VERSION 4
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
@@ -188,8 +196,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);
 
@@ -317,13 +331,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
 	return err;
 }
 
+static int add_rule_net_service(struct landlock_ruleset *ruleset,
+				const void __user *const rule_attr)
+{
+#if IS_ENABLED(CONFIG_INET)
+	struct landlock_net_service_attr net_service_attr;
+	int res;
+	access_mask_t mask;
+
+	/* Copies raw user space buffer, only one type for now. */
+	res = copy_from_user(&net_service_attr, rule_attr,
+			     sizeof(net_service_attr));
+	if (res)
+		return -EFAULT;
+
+	/*
+	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
+	 * are ignored by network actions.
+	 */
+	if (!net_service_attr.allowed_access)
+		return -ENOMSG;
+
+	/*
+	 * Checks that allowed_access matches the @ruleset constraints
+	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
+	 */
+	mask = landlock_get_net_access_mask(ruleset, 0);
+	if ((net_service_attr.allowed_access | mask) != mask)
+		return -EINVAL;
+
+	/* Denies inserting a rule with port 0. */
+	if (net_service_attr.port == 0)
+		return -EINVAL;
+
+	/* Imports the new rule. */
+	return landlock_append_net_rule(ruleset, net_service_attr.port,
+					net_service_attr.allowed_access);
+#else /* IS_ENABLED(CONFIG_INET) */
+	return -EAFNOSUPPORT;
+#endif /* IS_ENABLED(CONFIG_INET) */
+}
+
 /**
  * sys_landlock_add_rule - Add a new rule to a ruleset
  *
  * @ruleset_fd: File descriptor tied to the ruleset that should be extended
  *		with the new rule.
- * @rule_type: Identify the structure type pointed to by @rule_attr (only
- *             %LANDLOCK_RULE_PATH_BENEATH for now).
+ * @rule_type: Identify the structure type pointed to by @rule_attr:
+ *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_SERVICE.
  * @rule_attr: Pointer to a rule (only of type &struct
  *             landlock_path_beneath_attr for now).
  * @flags: Must be 0.
@@ -334,6 +389,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
  * Possible returned errors are:
  *
  * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - %EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not
+ *   supported by the running kernel;
  * - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
  *   &landlock_path_beneath_attr.allowed_access is not a subset of the
  *   ruleset handled accesses);
@@ -368,6 +425,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
 	case LANDLOCK_RULE_PATH_BENEATH:
 		err = add_rule_path_beneath(ruleset, rule_attr);
 		break;
+	case LANDLOCK_RULE_NET_SERVICE:
+		err = add_rule_net_service(ruleset, rule_attr);
+		break;
 	default:
 		err = -EINVAL;
 		break;
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 792c3f0a59b4..646f778dfb1e 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -75,7 +75,7 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(3, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(4, landlock_create_ruleset(NULL, 0,
 					     LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
-- 
2.25.1


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

* [PATCH v9 09/12] selftests/landlock: Share enforce_ruleset()
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (7 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 08/12] landlock: Add network rules and TCP hooks support Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

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

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

Changes since v8:
* Adds __maybe_unused attribute for enforce_ruleset() helper.

Changes since v7:
* Refactors commit message.

Changes since v6:
* None.

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

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

diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index d7987ae8d7fc..0fd6c4cf5e6f 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -256,3 +256,13 @@ static int __maybe_unused send_fd(int usock, int fd_tx)
 		return -errno;
 	return 0;
 }
+
+static void __maybe_unused
+enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
+{
+	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
+	{
+		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
+	}
+}
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index b6c4be3faf7a..b762b5419a89 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -598,16 +598,6 @@ static int create_ruleset(struct __test_metadata *const _metadata,
 	return ruleset_fd;
 }
 
-static void enforce_ruleset(struct __test_metadata *const _metadata,
-			    const int ruleset_fd)
-{
-	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
-	ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0))
-	{
-		TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
-	}
-}
-
 TEST_F_FORK(layout1, proc_nsfs)
 {
 	const struct rule rules[] = {
-- 
2.25.1


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

* [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (8 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-02-10 17:40   ` Mickaël Salaün
  2023-02-21 18:05   ` Mickaël Salaün
  2023-01-16  8:58 ` [PATCH v9 11/12] samples/landlock: Add network demo Konstantin Meskhidze
                   ` (2 subsequent siblings)
  12 siblings, 2 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

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

socket:
* bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
* connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
* bind_afunspec: Tests with non-landlocked/landlocked restrictions
for bind action with AF_UNSPEC socket family.
* connect_afunspec: Tests with non-landlocked/landlocked restrictions
for connect action with AF_UNSPEC socket family.
* ruleset_overlap: Tests with overlapping rules for one port.
* ruleset_expanding: Tests with expanding rulesets in which rules are
gradually added one by one, restricting sockets' connections.
* inval: Tests with invalid user space supplied data:
    - out of range ruleset attribute;
    - unhandled allowed access;
    - zero port value;
    - zero access value;
    - legitimate access values;
* bind_connect_inval_addrlen: Tests with invalid address length
for ipv4/ipv6 sockets.
* inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.

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

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

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

Changes since v8:
* Adds is_sandboxed const for FIXTURE_VARIANT(socket).
* Refactors AF_UNSPEC tests.
* Adds address length checking tests.
* Convert ports in all tests to __be16.
* Adds invalid port values tests.
* Minor fixes.

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

---
 tools/testing/selftests/landlock/config     |    4 +
 tools/testing/selftests/landlock/fs_test.c  |   65 ++
 tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
 3 files changed, 1226 insertions(+)
 create mode 100644 tools/testing/selftests/landlock/net_test.c

diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 0f0a65287bac..71f7e9a8a64c 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -1,3 +1,7 @@
+CONFIG_INET=y
+CONFIG_IPV6=y
+CONFIG_NET=y
+CONFIG_NET_NS=y
 CONFIG_OVERLAY_FS=y
 CONFIG_SECURITY_LANDLOCK=y
 CONFIG_SECURITY_PATH=y
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index b762b5419a89..5de4559c7fbb 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -8,8 +8,10 @@
  */
 
 #define _GNU_SOURCE
+#include <arpa/inet.h>
 #include <fcntl.h>
 #include <linux/landlock.h>
+#include <netinet/in.h>
 #include <sched.h>
 #include <stdio.h>
 #include <string.h>
@@ -17,6 +19,7 @@
 #include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/sendfile.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
@@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
 	}
 }
 
+#define IP_ADDRESS "127.0.0.1"
+
+TEST_F_FORK(layout1, with_net)
+{
+	int sockfd;
+	int sock_port = 15000;
+	struct sockaddr_in addr4;
+
+	addr4.sin_family = AF_INET;
+	addr4.sin_port = htons(sock_port);
+	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
+	memset(&addr4.sin_zero, '\0', 8);
+
+	const struct rule rules[] = {
+		{
+			.path = dir_s1d2,
+			.access = ACCESS_RO,
+		},
+		{},
+	};
+
+	struct landlock_ruleset_attr ruleset_attr_net = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = htons(sock_port),
+	};
+
+	/* Creates ruleset for network access. */
+	const int ruleset_fd_net = landlock_create_ruleset(
+		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
+	ASSERT_LE(0, ruleset_fd_net);
+
+	/* Adds a network rule. */
+	ASSERT_EQ(0,
+		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
+				    &net_service, 0));
+
+	enforce_ruleset(_metadata, ruleset_fd_net);
+	ASSERT_EQ(0, close(ruleset_fd_net));
+
+	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/* Tests on a directory with the network rule loaded. */
+	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
+	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
+
+	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port 15000. */
+	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
+
+	/* Closes bounded socket. */
+	ASSERT_EQ(0, close(sockfd));
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
new file mode 100644
index 000000000000..b9543089a4d3
--- /dev/null
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -0,0 +1,1157 @@
+// 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 <linux/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_IPv4 "127.0.0.1"
+#define IP_ADDRESS_IPv6 "::1"
+#define SOCK_PORT 15000
+
+/* Number pending connections queue to be hold. */
+#define BACKLOG 10
+
+const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
+
+/* Invalid attribute, out of landlock network access range. */
+#define LANDLOCK_INVAL_ATTR 7
+
+FIXTURE(socket)
+{
+	uint port[MAX_SOCKET_NUM];
+	struct sockaddr_in addr4[MAX_SOCKET_NUM];
+	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
+};
+
+/* struct _fixture_variant_socket */
+FIXTURE_VARIANT(socket)
+{
+	const bool is_ipv4;
+	const bool is_sandboxed;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket, ipv4) {
+	/* clang-format on */
+	.is_ipv4 = true,
+	.is_sandboxed = false,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket, ipv4_sandboxed) {
+	/* clang-format on */
+	.is_ipv4 = true,
+	.is_sandboxed = true,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket, ipv6) {
+	/* clang-format on */
+	.is_ipv4 = false,
+	.is_sandboxed = false,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket, ipv6_sandboxed) {
+	/* clang-format on */
+	.is_ipv4 = false,
+	.is_sandboxed = true,
+};
+
+static int
+create_socket_variant(const struct _fixture_variant_socket *const variant,
+		      const int type)
+{
+	if (variant->is_ipv4)
+		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
+	else
+		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
+}
+
+static int bind_variant(const struct _fixture_variant_socket *const variant,
+			const int sockfd,
+			const struct _test_data_socket *const self,
+			const size_t index, const bool zero_size)
+
+{
+	if (variant->is_ipv4)
+		return bind(sockfd, &self->addr4[index],
+			    (zero_size ? 0 : sizeof(self->addr4[index])));
+	else
+		return bind(sockfd, &self->addr6[index],
+			    (zero_size ? 0 : sizeof(self->addr6[index])));
+}
+
+static int connect_variant(const struct _fixture_variant_socket *const variant,
+			   const int sockfd,
+			   const struct _test_data_socket *const self,
+			   const size_t index, const bool zero_size)
+{
+	if (variant->is_ipv4)
+		return connect(sockfd, &self->addr4[index],
+			       (zero_size ? 0 : sizeof(self->addr4[index])));
+	else
+		return connect(sockfd, &self->addr6[index],
+			       (zero_size ? 0 : sizeof(self->addr6[index])));
+}
+
+FIXTURE_SETUP(socket)
+{
+	int i;
+
+	/* Creates IPv4 socket addresses. */
+	for (i = 0; i < MAX_SOCKET_NUM; i++) {
+		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
+		self->addr4[i].sin_family = AF_INET;
+		self->addr4[i].sin_port = htons(self->port[i]);
+		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS_IPv4);
+		memset(&(self->addr4[i].sin_zero), '\0', 8);
+	}
+
+	/* Creates IPv6 socket addresses. */
+	for (i = 0; i < MAX_SOCKET_NUM; i++) {
+		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
+		self->addr6[i].sin6_family = AF_INET6;
+		self->addr6[i].sin6_port = htons(self->port[i]);
+		inet_pton(AF_INET6, IP_ADDRESS_IPv6,
+			  &(self->addr6[i].sin6_addr));
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNET));
+	ASSERT_EQ(0, system("ip link set dev lo up"));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+};
+
+FIXTURE_TEARDOWN(socket){};
+
+FIXTURE(socket_standalone)
+{
+	uint port[MAX_SOCKET_NUM];
+	struct sockaddr_in addr4[MAX_SOCKET_NUM];
+	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
+};
+
+/* struct _fixture_variant_socket */
+FIXTURE_VARIANT(socket_standalone)
+{
+	const bool is_sandboxed;
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket_standalone, none_sandboxed) {
+	/* clang-format on */
+	.is_sandboxed = false,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(socket_standalone, sandboxed) {
+	/* clang-format on */
+	.is_sandboxed = true,
+};
+
+FIXTURE_SETUP(socket_standalone)
+{
+	int i;
+
+	/* Creates IPv4 socket addresses. */
+	for (i = 0; i < MAX_SOCKET_NUM; i++) {
+		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
+		self->addr4[i].sin_family = AF_INET;
+		self->addr4[i].sin_port = htons(self->port[i]);
+		self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS_IPv4);
+		memset(&(self->addr4[i].sin_zero), '\0', 8);
+	}
+
+	/* Creates IPv6 socket addresses. */
+	for (i = 0; i < MAX_SOCKET_NUM; i++) {
+		self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i;
+		self->addr6[i].sin6_family = AF_INET6;
+		self->addr6[i].sin6_port = htons(self->port[i]);
+		inet_pton(AF_INET6, IP_ADDRESS_IPv6,
+			  &(self->addr6[i].sin6_addr));
+	}
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	ASSERT_EQ(0, unshare(CLONE_NEWNET));
+	ASSERT_EQ(0, system("ip link set dev lo up"));
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+};
+
+FIXTURE_TEARDOWN(socket_standalone){};
+
+TEST_F_FORK(socket, bind)
+{
+	int sockfd;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = htons(self->port[0]),
+	};
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = htons(self->port[1]),
+	};
+	struct landlock_net_service_attr net_service_3 = {
+		.allowed_access = 0,
+		.port = htons(self->port[2]),
+	};
+	int ruleset_fd, ret;
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+						     sizeof(ruleset_attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+
+		/*
+		 * Allows connect and bind operations to the port[0]
+		 * socket.
+		 */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_1, 0));
+		/*
+		 * Allows connect and deny bind operations to the port[1]
+		 * socket.
+		 */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_2, 0));
+		/*
+		 * Empty allowed_access (i.e. deny rules) are ignored in
+		 * network actions for port[2] socket.
+		 */
+		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
+						LANDLOCK_RULE_NET_SERVICE,
+						&net_service_3, 0));
+		ASSERT_EQ(ENOMSG, errno);
+
+		/* Enforces the ruleset. */
+		enforce_ruleset(_metadata, ruleset_fd);
+	}
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[0]. */
+	ret = bind_variant(variant, sockfd, self, 0, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Closes bounded socket. */
+	ASSERT_EQ(0, close(sockfd));
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[1]. */
+	ret = bind_variant(variant, sockfd, self, 1, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EACCES, errno);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Binds a socket to port[2]. */
+	ret = bind_variant(variant, sockfd, self, 2, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EACCES, errno);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+}
+
+TEST_F_FORK(socket, connect)
+{
+	int new_fd;
+	int sockfd_1, sockfd_2;
+	pid_t child_1, child_2;
+	int status;
+	int ruleset_fd, ret;
+
+	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 = htons(self->port[0]),
+	};
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = htons(self->port[1]),
+	};
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+						     sizeof(ruleset_attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+
+		/*
+		 * Allows connect and bind operations to the port[0]
+		 * socket.
+		 */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_1, 0));
+		/*
+		 * Allows connect and deny bind operations to the port[1]
+		 * socket.
+		 */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_2, 0));
+
+		/* Enforces the ruleset. */
+		enforce_ruleset(_metadata, ruleset_fd);
+	}
+
+	/* Creates a server socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+
+	/* Binds the socket 1 to address with port[0]. */
+	ret = bind_variant(variant, sockfd_1, self, 0, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Makes listening socket 1. */
+	ret = listen(sockfd_1, BACKLOG);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	child_1 = fork();
+	ASSERT_LE(0, child_1);
+	if (child_1 == 0) {
+		int child_sockfd, ret;
+
+		/* Closes listening socket for the child. */
+		ASSERT_EQ(0, close(sockfd_1));
+		/* Creates a stream client socket. */
+		child_sockfd = create_socket_variant(variant, SOCK_STREAM);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket with port[0]. */
+		ret = connect_variant(variant, child_sockfd, self, 0, false);
+		if (variant->is_sandboxed) {
+			ASSERT_EQ(0, ret);
+		} else {
+			ASSERT_EQ(0, ret);
+		}
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+	/* Accepts connection from the child 1. */
+	new_fd = accept(sockfd_1, NULL, 0);
+	ASSERT_LE(0, new_fd);
+
+	/* Closes connection. */
+	ASSERT_EQ(0, close(new_fd));
+
+	/* Closes listening socket 1 for the parent. */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	ASSERT_EQ(child_1, waitpid(child_1, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+	/* Creates a server socket 2. */
+	sockfd_2 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_2);
+
+	/* Binds the socket 2 to address with port[1]. */
+	ret = bind_variant(variant, sockfd_2, self, 1, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Makes listening socket 2. */
+	ret = listen(sockfd_2, BACKLOG);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	child_2 = fork();
+	ASSERT_LE(0, child_2);
+	if (child_2 == 0) {
+		int child_sockfd, ret;
+
+		/* Closes listening socket for the child. */
+		ASSERT_EQ(0, close(sockfd_2));
+		/* Creates a stream client socket. */
+		child_sockfd = create_socket_variant(variant, SOCK_STREAM);
+		ASSERT_LE(0, child_sockfd);
+
+		/* Makes connection to the listening socket with port[1]. */
+		ret = connect_variant(variant, child_sockfd, self, 1, false);
+		if (variant->is_sandboxed) {
+			ASSERT_EQ(-1, ret);
+			ASSERT_EQ(EACCES, errno);
+		} else {
+			ASSERT_EQ(0, ret);
+		}
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+
+	if (!variant->is_sandboxed) {
+		/* Accepts connection from the child 2. */
+		new_fd = accept(sockfd_1, NULL, 0);
+		ASSERT_LE(0, new_fd);
+
+		/* Closes connection. */
+		ASSERT_EQ(0, close(new_fd));
+	}
+
+	/* 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_standalone, bind_afunspec)
+{
+	int sockfd_unspec;
+	struct sockaddr_in addr4_unspec;
+	int ruleset_fd_net, ret;
+
+	addr4_unspec.sin_family = AF_UNSPEC;
+	addr4_unspec.sin_port = htons(SOCK_PORT);
+	addr4_unspec.sin_addr.s_addr = htonl(INADDR_ANY);
+	memset(&addr4_unspec.sin_zero, '\0', 8);
+
+	struct landlock_ruleset_attr ruleset_attr_net = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = htons(SOCK_PORT),
+	};
+
+	if (variant->is_sandboxed) {
+		/* Creates ruleset for network access. */
+		ruleset_fd_net = landlock_create_ruleset(
+			&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
+		ASSERT_LE(0, ruleset_fd_net);
+
+		/* Adds a network rule. */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd_net,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service, 0));
+
+		enforce_ruleset(_metadata, ruleset_fd_net);
+		ASSERT_EQ(0, close(ruleset_fd_net));
+	}
+
+	sockfd_unspec = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	ASSERT_LE(0, sockfd_unspec);
+
+	/* Binds a socket to port SOCK_PORT with INADDR_ANY address. */
+	ret = bind(sockfd_unspec, &addr4_unspec, sizeof(addr4_unspec));
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Closes bounded socket. */
+	ASSERT_EQ(0, close(sockfd_unspec));
+
+	/* Changes to a specific address. */
+	addr4_unspec.sin_addr.s_addr = inet_addr(IP_ADDRESS_IPv4);
+
+	sockfd_unspec = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	ASSERT_LE(0, sockfd_unspec);
+
+	/* Binds a socket to port SOCK_PORT with the specific address. */
+	ret = bind(sockfd_unspec, &addr4_unspec, sizeof(addr4_unspec));
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EAFNOSUPPORT, errno);
+	} else {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EAFNOSUPPORT, errno);
+	}
+
+	/* Closes bounded socket. */
+	ASSERT_EQ(0, close(sockfd_unspec));
+}
+
+TEST_F_FORK(socket, connect_afunspec)
+{
+	int sockfd;
+	pid_t child;
+	int status;
+	int ruleset_fd_1, ruleset_fd_2;
+	int ret;
+
+	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 = htons(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 = htons(self->port[0]),
+	};
+
+	if (variant->is_sandboxed) {
+		ruleset_fd_1 = landlock_create_ruleset(
+			&ruleset_attr_1, sizeof(ruleset_attr_1), 0);
+		ASSERT_LE(0, ruleset_fd_1);
+
+		/* Allows bind operations to the port[0] socket. */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_1, 0));
+
+		/* Enforces the ruleset. */
+		enforce_ruleset(_metadata, ruleset_fd_1);
+	}
+
+	/* Creates a server socket 1. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+
+	/* Binds the socket 1 to address with port[0]. */
+	ret = bind_variant(variant, sockfd, self, 0, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Makes connection to socket with port[0]. */
+	ret = connect_variant(variant, sockfd, self, 0, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	if (variant->is_sandboxed) {
+		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) {
+		int ret;
+
+		/* Child tries to disconnect already connected socket. */
+		ret = connect(sockfd, (struct sockaddr *)&addr_unspec,
+			      sizeof(addr_unspec));
+		if (variant->is_sandboxed) {
+			ASSERT_EQ(0, ret);
+		} else {
+			ASSERT_EQ(0, ret);
+		}
+		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+		return;
+	}
+	/* Closes listening socket 1 for the parent. */
+	ASSERT_EQ(0, close(sockfd));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+TEST_F_FORK(socket, ruleset_overlap)
+{
+	int sockfd;
+	int one = 1;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = htons(self->port[0]),
+	};
+
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+
+		.port = htons(self->port[0]),
+	};
+
+	int ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/* Allows bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+	/* Allows connect and bind operations to the port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+
+	/* Creates a server socket. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0, false));
+
+	/* Makes connection to socket with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0, false));
+
+	/* Closes socket. */
+	ASSERT_EQ(0, close(sockfd));
+
+	/* Creates another ruleset layer. */
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	/*
+	 * Allows bind operations to the port[0] socket in
+	 * the new ruleset layer.
+	 */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+
+	/* Enforces the new ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd);
+
+	/* Creates a server socket. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0, false));
+
+	/*
+	 * Forbids to connect the socket to address with port[0],
+	 * as just one ruleset layer has connect() access rule.
+	 */
+	ASSERT_EQ(-1, connect_variant(variant, sockfd, self, 0, false));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Closes socket. */
+	ASSERT_EQ(0, close(sockfd));
+}
+
+TEST_F_FORK(socket, ruleset_expanding)
+{
+	int sockfd_1, sockfd_2;
+	int one = 1;
+
+	struct landlock_ruleset_attr ruleset_attr_1 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+	};
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+
+		.port = htons(self->port[0]),
+	};
+
+	const int ruleset_fd_1 = landlock_create_ruleset(
+		&ruleset_attr_1, sizeof(ruleset_attr_1), 0);
+	ASSERT_LE(0, ruleset_fd_1);
+
+	/* Adds rule to port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_1, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_1);
+	ASSERT_EQ(0, close(ruleset_fd_1));
+
+	/* Creates a socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0, false));
+
+	/* Makes connection to socket 1 with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd_1, self, 0, false));
+
+	/* Closes socket 1. */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	/* Creates a socket 2. */
+	sockfd_2 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_2);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_2, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/*
+	 * Forbids to bind the socket 2 to address with port[1],
+	 * since there is no rule with bind() access for port[1].
+	 */
+	ASSERT_EQ(-1, bind_variant(variant, sockfd_2, self, 1, false));
+	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 = htons(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 = htons(self->port[1]),
+	};
+
+	const int ruleset_fd_2 = landlock_create_ruleset(
+		&ruleset_attr_2, sizeof(ruleset_attr_2), 0);
+	ASSERT_LE(0, ruleset_fd_2);
+
+	/* Adds rule to port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_2, 0));
+	/* Adds rule to port[1] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_3, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_2);
+	ASSERT_EQ(0, close(ruleset_fd_2));
+
+	/* Creates a socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0, false));
+
+	/* Makes connection to socket 1 with port[0]. */
+	ASSERT_EQ(0, connect_variant(variant, sockfd_1, self, 0, false));
+
+	/* Closes socket 1. */
+	ASSERT_EQ(0, close(sockfd_1));
+
+	/* Creates a socket 2. */
+	sockfd_2 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_2);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_2, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/*
+	 * Forbids to bind the socket 2 to address with port[1],
+	 * because just one layer has bind() access rule.
+	 */
+	ASSERT_EQ(-1, bind_variant(variant, sockfd_1, self, 1, false));
+	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 = htons(self->port[0]),
+	};
+
+	const int ruleset_fd_3 = landlock_create_ruleset(
+		&ruleset_attr_3, sizeof(ruleset_attr_3), 0);
+	ASSERT_LE(0, ruleset_fd_3);
+
+	/* Adds rule to port[0] socket. */
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd_3, LANDLOCK_RULE_NET_SERVICE,
+				       &net_service_4, 0));
+
+	/* Enforces the ruleset. */
+	enforce_ruleset(_metadata, ruleset_fd_3);
+	ASSERT_EQ(0, close(ruleset_fd_3));
+
+	/* Creates a socket 1. */
+	sockfd_1 = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd_1);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket 1 to address with port[0]. */
+	ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0, false));
+
+	/*
+	 * Forbids to connect the socket 1 to address with port[0],
+	 * as just one layer has connect() access rule.
+	 */
+	ASSERT_EQ(-1, connect_variant(variant, sockfd_1, self, 0, false));
+	ASSERT_EQ(EACCES, errno);
+
+	/* Closes socket 1. */
+	ASSERT_EQ(0, close(sockfd_1));
+}
+
+/* clang-format off */
+
+#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
+
+#define ACCESS_ALL ( \
+	LANDLOCK_ACCESS_NET_BIND_TCP | \
+	LANDLOCK_ACCESS_NET_CONNECT_TCP)
+
+/* clang-format on */
+
+TEST_F_FORK(socket_standalone, inval)
+{
+	__u64 access;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP
+	};
+	struct landlock_ruleset_attr ruleset_attr_inval = {
+		.handled_access_net = LANDLOCK_INVAL_ATTR
+	};
+	struct landlock_ruleset_attr ruleset_attr_all = { .handled_access_net =
+								  ACCESS_ALL };
+
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.port = htons(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 = htons(self->port[1]),
+	};
+	struct landlock_net_service_attr net_service_4 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		.port = htons(self->port[2]),
+	};
+
+	struct landlock_net_service_attr net_service_5 = {};
+
+	if (variant->is_sandboxed) {
+		/* Checks invalid ruleset attribute. */
+		const int ruleset_fd_inv = landlock_create_ruleset(
+			&ruleset_attr_inval, sizeof(ruleset_attr_inval), 0);
+		ASSERT_EQ(-1, ruleset_fd_inv);
+		ASSERT_EQ(EINVAL, errno);
+
+		/* Gets ruleset. */
+		const int ruleset_fd = landlock_create_ruleset(
+			&ruleset_attr, sizeof(ruleset_attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+
+		/* Checks unhandled allowed_access. */
+		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
+						LANDLOCK_RULE_NET_SERVICE,
+						&net_service_1, 0));
+		ASSERT_EQ(EINVAL, errno);
+
+		/* Checks zero port value. */
+		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
+						LANDLOCK_RULE_NET_SERVICE,
+						&net_service_2, 0));
+		ASSERT_EQ(EINVAL, errno);
+
+		/* Checks zero access value. */
+		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
+						LANDLOCK_RULE_NET_SERVICE,
+						&net_service_3, 0));
+		ASSERT_EQ(ENOMSG, errno);
+
+		/* Adds with legitimate values. */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_4, 0));
+
+		const int ruleset_fd_all = landlock_create_ruleset(
+			&ruleset_attr_all, sizeof(ruleset_attr_all), 0);
+
+		ASSERT_LE(0, ruleset_fd_all);
+
+		/* Tests access rights for all network rules */
+		for (access = 1; access <= ACCESS_LAST; access <<= 1) {
+			net_service_5.allowed_access = access;
+			net_service_5.port = htons(self->port[3]);
+
+			ASSERT_EQ(0,
+				  landlock_add_rule(ruleset_fd_all,
+						    LANDLOCK_RULE_NET_SERVICE,
+						    &net_service_5, 0));
+		}
+
+		/* Enforces the ruleset. */
+		enforce_ruleset(_metadata, ruleset_fd);
+		ASSERT_EQ(0, close(ruleset_fd));
+
+		enforce_ruleset(_metadata, ruleset_fd_all);
+		ASSERT_EQ(0, close(ruleset_fd_all));
+	}
+}
+
+TEST_F_FORK(socket, bind_connect_inval_addrlen)
+{
+	int sockfd;
+	int ruleset_fd, ret;
+	int one = 1;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+
+	struct landlock_net_service_attr net_service = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
+				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
+
+		.port = htons(self->port[0]),
+	};
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+						     sizeof(ruleset_attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+
+		/* Allows bind/connect actions for socket with SOCK_PORT. */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service, 0));
+
+		/* Enforces the ruleset. */
+		enforce_ruleset(_metadata, ruleset_fd);
+	}
+
+	/* Creates a socket 1. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to port[0] with zero addrlen. */
+	ret = bind_variant(variant, sockfd, self, 0, true);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EINVAL, errno);
+	} else {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EINVAL, errno);
+	}
+
+	/* Connects the socket to the listening port with zero addrlen. */
+	ret = connect_variant(variant, sockfd, self, 0, true);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EINVAL, errno);
+	} else {
+		ASSERT_EQ(-1, ret);
+		ASSERT_EQ(EINVAL, errno);
+	}
+
+	/* Binds the socket to port[0] with correct addrlen. */
+	ret = bind_variant(variant, sockfd, self, 0, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Connects the socket to the listening port with correct addrlen. */
+	ret = connect_variant(variant, sockfd, self, 0, false);
+	if (variant->is_sandboxed) {
+		ASSERT_EQ(0, ret);
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Closes the connection*/
+	ASSERT_EQ(0, close(sockfd));
+}
+
+TEST_F_FORK(socket, inval_port_format)
+{
+	int sockfd;
+	int ruleset_fd, ret;
+	int one = 1;
+	bool little_endian = false;
+	unsigned int i = 1;
+
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+
+	struct landlock_net_service_attr net_service_1 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		/* Wrong port format. */
+		.port = self->port[0],
+	};
+
+	struct landlock_net_service_attr net_service_2 = {
+		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+		/* Correct port format. */
+		.port = htons(self->port[1]),
+	};
+
+	if (variant->is_sandboxed) {
+		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+						     sizeof(ruleset_attr), 0);
+		ASSERT_LE(0, ruleset_fd);
+
+		/* Allows bind/connect actions for socket with wrong port. */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_1, 0));
+
+		/* Allows bind/connect actions for socket with correct port. */
+		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					       LANDLOCK_RULE_NET_SERVICE,
+					       &net_service_2, 0));
+
+		/* Enforces the ruleset. */
+		enforce_ruleset(_metadata, ruleset_fd);
+	}
+
+	/* Checks endianness. */
+	char *c = (char *)&i;
+	if (*c)
+		little_endian = true;
+
+	/* Creates a socket 1. */
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to port[0] with wrong format . */
+	ret = bind_variant(variant, sockfd, self, 0, false);
+	if (variant->is_sandboxed) {
+		if (little_endian) {
+			ASSERT_EQ(-1, ret);
+			ASSERT_EQ(EACCES, errno);
+		} else {
+			/* No error for big-endinan cpu by default. */
+			ASSERT_EQ(0, ret);
+		}
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Closes the connection*/
+	ASSERT_EQ(0, close(sockfd));
+
+	sockfd = create_socket_variant(variant, SOCK_STREAM);
+	ASSERT_LE(0, sockfd);
+	/* Allows to reuse of local address. */
+	ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one,
+				sizeof(one)));
+
+	/* Binds the socket to port[1] with correct format. */
+	ret = bind_variant(variant, sockfd, self, 1, false);
+	if (variant->is_sandboxed) {
+		if (little_endian) {
+			ASSERT_EQ(0, ret);
+		} else {
+			/* No error for big-endinan cpu by default. */
+			ASSERT_EQ(0, ret);
+		}
+	} else {
+		ASSERT_EQ(0, ret);
+	}
+
+	/* Closes the connection*/
+	ASSERT_EQ(0, close(sockfd));
+}
+TEST_HARNESS_MAIN
-- 
2.25.1


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

* [PATCH v9 11/12] samples/landlock: Add network demo
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (9 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-01-16  8:58 ` [PATCH v9 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
  2023-02-23 22:17 ` [PATCH v9 00/12] Network support for Landlock Günther Noack
  12 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

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

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

Changes since v8:
* Converts ports to __be16.
* Minor fixes.

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

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

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

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

---
 samples/landlock/sandboxer.c | 131 +++++++++++++++++++++++++++++++----
 1 file changed, 119 insertions(+), 12 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e2056c8b902c..e52aeb844f9e 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -8,6 +8,7 @@
  */
 
 #define _GNU_SOURCE
+#include <arpa/inet.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/landlock.h>
@@ -51,6 +52,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 +74,20 @@ static int parse_path(char *env_path, const char ***const path_list)
 	return num_paths;
 }
 
+static int parse_port_num(char *env_port)
+{
+	int i, num_ports = 0;
+
+	if (env_port) {
+		num_ports++;
+		for (i = 0; env_port[i]; i++) {
+			if (env_port[i] == ENV_PATH_TOKEN[0])
+				num_ports++;
+		}
+	}
+	return num_ports;
+}
+
 /* clang-format off */
 
 #define ACCESS_FILE ( \
@@ -81,8 +98,8 @@ static int parse_path(char *env_path, const char ***const path_list)
 
 /* clang-format on */
 
-static int populate_ruleset(const char *const env_var, const int ruleset_fd,
-			    const __u64 allowed_access)
+static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
+			       const __u64 allowed_access)
 {
 	int num_paths, i, ret = 1;
 	char *env_path_name;
@@ -143,6 +160,48 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
 	return ret;
 }
 
+static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
+				const __u64 allowed_access)
+{
+	int num_ports, i, ret = 1;
+	char *env_port_name;
+	struct landlock_net_service_attr net_service = {
+		.allowed_access = 0,
+		.port = 0,
+	};
+
+	env_port_name = getenv(env_var);
+	if (!env_port_name) {
+		return 0;
+	}
+	env_port_name = strdup(env_port_name);
+	unsetenv(env_var);
+	num_ports = parse_port_num(env_port_name);
+
+	if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) {
+		ret = 0;
+		goto out_free_name;
+	}
+
+	for (i = 0; i < num_ports; i++) {
+		net_service.allowed_access = allowed_access;
+		net_service.port =
+			htons(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 ( \
@@ -166,39 +225,58 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd,
 
 /* clang-format on */
 
-#define LANDLOCK_ABI_LAST 3
+#define LANDLOCK_ABI_LAST 4
 
 int main(const int argc, char *const argv[], char *const *const envp)
 {
 	const char *cmd_path;
 	char *const *cmd_argv;
 	int ruleset_fd, abi;
+	char *env_port_name;
 	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
 	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
+
 	struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = access_fs_rw,
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
 	};
 
 	if (argc < 2) {
 		fprintf(stderr,
-			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
-			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
+			"usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
+			"<cmd> [args]...\n\n",
+			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
+			ENV_TCP_CONNECT_NAME, argv[0]);
 		fprintf(stderr,
 			"Launch a command in a restricted environment.\n\n");
-		fprintf(stderr, "Environment variables containing paths, "
-				"each separated by a colon:\n");
+		fprintf(stderr,
+			"Environment variables containing paths and ports "
+			"each separated by a colon:\n");
 		fprintf(stderr,
 			"* %s: list of paths allowed to be used in a read-only way.\n",
 			ENV_FS_RO_NAME);
 		fprintf(stderr,
-			"* %s: list of paths allowed to be used in a read-write way.\n",
+			"* %s: list of paths allowed to be used in a read-write way.\n\n",
 			ENV_FS_RW_NAME);
+		fprintf(stderr,
+			"Environment variables containing ports are optional "
+			"and could be skipped.\n");
+		fprintf(stderr,
+			"* %s: list of ports allowed to bind (server).\n",
+			ENV_TCP_BIND_NAME);
+		fprintf(stderr,
+			"* %s: list of ports allowed to connect (client).\n",
+			ENV_TCP_CONNECT_NAME);
 		fprintf(stderr,
 			"\nexample:\n"
 			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
 			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
+			"%s=\"9418\" "
+			"%s=\"80:443\" "
 			"%s bash -i\n\n",
-			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
+			ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
+			ENV_TCP_CONNECT_NAME, argv[0]);
 		fprintf(stderr,
 			"This sandboxer can use Landlock features "
 			"up to ABI version %d.\n",
@@ -255,7 +333,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	case 2:
 		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
 		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
-
+		__attribute__((fallthrough));
+	case 3:
+		/* Removes network support for ABI < 4 */
+		ruleset_attr.handled_access_net &=
+			~(LANDLOCK_ACCESS_NET_BIND_TCP |
+			  LANDLOCK_ACCESS_NET_CONNECT_TCP);
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
 			"to leverage Landlock features "
@@ -274,18 +357,42 @@ int main(const int argc, char *const argv[], char *const *const envp)
 	access_fs_ro &= ruleset_attr.handled_access_fs;
 	access_fs_rw &= ruleset_attr.handled_access_fs;
 
+	/* Removes bind access attribute if not supported by a user. */
+	env_port_name = getenv(ENV_TCP_BIND_NAME);
+	if (!env_port_name) {
+		ruleset_attr.handled_access_net &=
+			~LANDLOCK_ACCESS_NET_BIND_TCP;
+	}
+	/* Removes connect access attribute if not supported by a user. */
+	env_port_name = getenv(ENV_TCP_CONNECT_NAME);
+	if (!env_port_name) {
+		ruleset_attr.handled_access_net &=
+			~LANDLOCK_ACCESS_NET_CONNECT_TCP;
+	}
+
 	ruleset_fd =
 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
 	if (ruleset_fd < 0) {
 		perror("Failed to create a ruleset");
 		return 1;
 	}
-	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+
+	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+		goto err_close_ruleset;
+	}
+	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
 		goto err_close_ruleset;
 	}
-	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+
+	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;
-- 
2.25.1


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

* [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (10 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 11/12] samples/landlock: Add network demo Konstantin Meskhidze
@ 2023-01-16  8:58 ` Konstantin Meskhidze
  2023-01-21 23:07   ` Günther Noack
  2023-02-23 22:17 ` [PATCH v9 00/12] Network support for Landlock Günther Noack
  12 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze @ 2023-01-16  8:58 UTC (permalink / raw)
  To: mic
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Describe network access rules for TCP sockets. Add network access
example in the tutorial. Add kernel configuration support for network.

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

Changes since v8:
* Minor refactoring.

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

Changes since v6:
* Adds network support documentaion.

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

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index d8cd8cd9ce25..980558b879d6 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -11,10 +11,10 @@ Landlock: unprivileged access control
 :Date: October 2022
 
 The goal of Landlock is to enable to restrict ambient rights (e.g. global
-filesystem access) for a set of processes.  Because Landlock is a stackable
-LSM, it makes possible to create safe security sandboxes as new security layers
-in addition to the existing system-wide access-controls. This kind of sandbox
-is expected to help mitigate the security impact of bugs or
+filesystem or network access) for a set of processes.  Because Landlock
+is a stackable LSM, it makes possible to create safe security sandboxes as new
+security layers in addition to the existing system-wide access-controls. This
+kind of sandbox is expected to help mitigate the security impact of bugs or
 unexpected/malicious behaviors in user space applications.  Landlock empowers
 any process, including unprivileged ones, to securely restrict themselves.
 
@@ -30,18 +30,20 @@ Landlock rules
 
 A Landlock rule describes an action on an object.  An object is currently a
 file hierarchy, and the related filesystem actions are defined with `access
-rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
-the thread enforcing it, and its future children.
+rights`_.  Since ABI version 4 a port data appears with related network actions
+for TCP socket families.  A set of rules is aggregated in a ruleset, which
+can then restrict the thread enforcing it, and its future children.
 
 Defining and enforcing a security policy
 ----------------------------------------
 
 We first need to define the ruleset that will contain our rules.  For this
 example, the ruleset will contain rules that only allow read actions, but write
-actions will be denied.  The ruleset then needs to handle both of these kind of
+actions will be denied. The ruleset then needs to handle both of these kind of
 actions.  This is required for backward and forward compatibility (i.e. the
 kernel and user space may not know each other's supported restrictions), hence
-the need to be explicit about the denied-by-default access rights.
+the need to be explicit about the denied-by-default access rights.  Also ruleset
+will have network rules for specific ports, so it should handle network actions.
 
 .. code-block:: c
 
@@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
             LANDLOCK_ACCESS_FS_MAKE_SYM |
             LANDLOCK_ACCESS_FS_REFER |
             LANDLOCK_ACCESS_FS_TRUNCATE,
+        .handled_access_net =
+            LANDLOCK_ACCESS_NET_BIND_TCP |
+            LANDLOCK_ACCESS_NET_CONNECT_TCP,
     };
 
 Because we may not know on which kernel version an application will be
@@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
 using.  To avoid binary enforcement (i.e. either all security features or
 none), we can leverage a dedicated Landlock command to get the current version
 of the Landlock ABI and adapt the handled accesses.  Let's check if we should
-remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
-access rights, which are only supported starting with the second and third
-version of the ABI.
+remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE`` or
+network access rights, which are only supported starting with the second,
+third and fourth version of the ABI.
 
 .. code-block:: c
 
     int abi;
 
+    #define ACCESS_NET_BIND_CONNECT ( \
+        LANDLOCK_ACCESS_NET_BIND_TCP | \
+        LANDLOCK_ACCESS_NET_CONNECT_TCP)
+
     abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
     if (abi < 0) {
         /* Degrades gracefully if Landlock is not handled. */
@@ -92,6 +101,11 @@ version of the ABI.
     case 2:
         /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
         ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
+    case 3:
+        /* Removes network support for ABI < 4 */
+		ruleset_attr.handled_access_net &=
+			~(LANDLOCK_ACCESS_NET_BIND_TCP |
+			  LANDLOCK_ACCESS_NET_CONNECT_TCP);
     }
 
 This enables to create an inclusive ruleset that will contain our rules.
@@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
 ABI version.  In this example, this is not required because all of the requested
 ``allowed_access`` rights are already available in ABI 1.
 
-We now have a ruleset with one rule allowing read access to ``/usr`` while
-denying all other handled accesses for the filesystem.  The next step is to
-restrict the current thread from gaining more privileges (e.g. thanks to a SUID
-binary).
+For network access-control, we can add a set of rules that allow to use a port
+number for a specific action. All ports values must be defined in network byte
+order.
+
+.. code-block:: c
+
+    struct landlock_net_service_attr net_service = {
+        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+        .port = htons(8080),
+    };
+
+    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
+                            &net_service, 0);
+
+The next step is to restrict the current thread from gaining more privileges
+(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
+read access to ``/usr`` while denying all other handled accesses for the filesystem,
+and a second rule allowing TCP binding on port 8080.
 
 .. code-block:: c
 
@@ -355,7 +383,7 @@ Access rights
 -------------
 
 .. kernel-doc:: include/uapi/linux/landlock.h
-    :identifiers: fs_access
+    :identifiers: fs_access net_access
 
 Creating a new ruleset
 ----------------------
@@ -374,6 +402,7 @@ Extending a ruleset
 
 .. kernel-doc:: include/uapi/linux/landlock.h
     :identifiers: landlock_rule_type landlock_path_beneath_attr
+                  landlock_net_service_attr
 
 Enforcing a ruleset
 -------------------
@@ -451,6 +480,12 @@ always allowed when using a kernel that only supports the first or second ABI.
 Starting with the Landlock ABI version 3, it is now possible to securely control
 truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
 
+Network support (ABI < 4)
+-------------------------
+
+Starting with the Landlock ABI version 4, it is now possible to restrict TCP
+bind and connect actions to only a set of allowed ports.
+
 .. _kernel_support:
 
 Kernel support
@@ -469,6 +504,11 @@ still enable it by adding ``lsm=landlock,[...]`` to
 Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
 configuration.
 
+To be able to explicitly allow TCP operations (e.g., adding a network rule with
+``LANDLOCK_ACCESS_NET_TCP_BIND``), the kernel must support TCP (``CONFIG_INET=y``).
+Otherwise, sys_landlock_add_rule() returns an ``EAFNOSUPPORT`` error, which can
+safely be ignored because this kind of TCP operation is already not possible.
+
 Questions and answers
 =====================
 
-- 
2.25.1


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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-16  8:58 ` [PATCH v9 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
@ 2023-01-21 23:07   ` Günther Noack
  2023-01-23  9:38     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Günther Noack @ 2023-01-21 23:07 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Hello!

Thank you for sending these patches! I'll start poking a bit at the
Go-Landlock library to see how we can support it there when this lands.

On Mon, Jan 16, 2023 at 04:58:18PM +0800, Konstantin Meskhidze wrote:
> Describe network access rules for TCP sockets. Add network access
> example in the tutorial. Add kernel configuration support for network.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Minor refactoring.
> 
> Changes since v7:
> * Fixes documentaion logic errors and typos as Mickaёl suggested:
> https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/
> 
> Changes since v6:
> * Adds network support documentaion.
> 
> ---
>  Documentation/userspace-api/landlock.rst | 72 ++++++++++++++++++------
>  1 file changed, 56 insertions(+), 16 deletions(-)
> 
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index d8cd8cd9ce25..980558b879d6 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -11,10 +11,10 @@ Landlock: unprivileged access control
>  :Date: October 2022
>  
>  The goal of Landlock is to enable to restrict ambient rights (e.g. global
> -filesystem access) for a set of processes.  Because Landlock is a stackable
> -LSM, it makes possible to create safe security sandboxes as new security layers
> -in addition to the existing system-wide access-controls. This kind of sandbox
> -is expected to help mitigate the security impact of bugs or
> +filesystem or network access) for a set of processes.  Because Landlock
> +is a stackable LSM, it makes possible to create safe security sandboxes as new
> +security layers in addition to the existing system-wide access-controls. This
> +kind of sandbox is expected to help mitigate the security impact of bugs or
>  unexpected/malicious behaviors in user space applications.  Landlock empowers
>  any process, including unprivileged ones, to securely restrict themselves.
>  
> @@ -30,18 +30,20 @@ Landlock rules
>  
>  A Landlock rule describes an action on an object.  An object is currently a
>  file hierarchy, and the related filesystem actions are defined with `access
> -rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
> -the thread enforcing it, and its future children.
> +rights`_.  Since ABI version 4 a port data appears with related network actions
> +for TCP socket families.  A set of rules is aggregated in a ruleset, which
> +can then restrict the thread enforcing it, and its future children.
>  
>  Defining and enforcing a security policy
>  ----------------------------------------
>  
>  We first need to define the ruleset that will contain our rules.  For this
>  example, the ruleset will contain rules that only allow read actions, but write
> -actions will be denied.  The ruleset then needs to handle both of these kind of
> +actions will be denied. The ruleset then needs to handle both of these kind of

(This one looks like an unintentional whitespace change; the
double-space ending is used in this file, so we should probably stay
consistent.)

>  actions.  This is required for backward and forward compatibility (i.e. the
>  kernel and user space may not know each other's supported restrictions), hence
> -the need to be explicit about the denied-by-default access rights.
> +the need to be explicit about the denied-by-default access rights.  Also ruleset

Wording nit: "Also, the ruleset"...?

> +will have network rules for specific ports, so it should handle network actions.
>  
>  .. code-block:: c
>  
> @@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
>              LANDLOCK_ACCESS_FS_MAKE_SYM |
>              LANDLOCK_ACCESS_FS_REFER |
>              LANDLOCK_ACCESS_FS_TRUNCATE,
> +        .handled_access_net =
> +            LANDLOCK_ACCESS_NET_BIND_TCP |
> +            LANDLOCK_ACCESS_NET_CONNECT_TCP,
>      };
>  
>  Because we may not know on which kernel version an application will be
> @@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
>  using.  To avoid binary enforcement (i.e. either all security features or
>  none), we can leverage a dedicated Landlock command to get the current version
>  of the Landlock ABI and adapt the handled accesses.  Let's check if we should
> -remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
> -access rights, which are only supported starting with the second and third
> -version of the ABI.
> +remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE`` or
> +network access rights, which are only supported starting with the second,
> +third and fourth version of the ABI.
>  
>  .. code-block:: c
>  
>      int abi;
>  
> +    #define ACCESS_NET_BIND_CONNECT ( \
> +        LANDLOCK_ACCESS_NET_BIND_TCP | \
> +        LANDLOCK_ACCESS_NET_CONNECT_TCP)
> +
>      abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
>      if (abi < 0) {
>          /* Degrades gracefully if Landlock is not handled. */
> @@ -92,6 +101,11 @@ version of the ABI.
>      case 2:
>          /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>          ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
> +    case 3:
> +        /* Removes network support for ABI < 4 */
> +		ruleset_attr.handled_access_net &=
           ^^^^^
           Nit: Indentation differs from "case 2"
           
> +			~(LANDLOCK_ACCESS_NET_BIND_TCP |
> +			  LANDLOCK_ACCESS_NET_CONNECT_TCP);
>      }
>  
>  This enables to create an inclusive ruleset that will contain our rules.
> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>  ABI version.  In this example, this is not required because all of the requested
>  ``allowed_access`` rights are already available in ABI 1.
>  
> -We now have a ruleset with one rule allowing read access to ``/usr`` while
> -denying all other handled accesses for the filesystem.  The next step is to
> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
> -binary).
> +For network access-control, we can add a set of rules that allow to use a port
> +number for a specific action. All ports values must be defined in network byte
> +order.

What is the point of asking user space to convert this to network byte
order? It seems to me that the kernel would be able to convert it to
network byte order very easily internally and in a single place -- why
ask all of the users to deal with that complexity? Am I overlooking
something?

> +
> +.. code-block:: c
> +
> +    struct landlock_net_service_attr net_service = {
> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> +        .port = htons(8080),
> +    };

This is a more high-level comment:

The notion of a 16-bit "port" seems to be specific to TCP and UDP --
how do you envision this struct to evolve if other protocols need to
be supported in the future?

Should this struct and the associated constants have "TCP" in its
name, and other protocols use a separate struct in the future?

> +
> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
> +                            &net_service, 0);
> +
> +The next step is to restrict the current thread from gaining more privileges
> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
         ^^^^^^
         "through" a SUID binary? "thanks to" sounds like it's desired
         to do that, while we're actually trying to prevent it here?

> +read access to ``/usr`` while denying all other handled accesses for the filesystem,
> +and a second rule allowing TCP binding on port 8080.
>  
>  .. code-block:: c
>  
> @@ -355,7 +383,7 @@ Access rights
>  -------------
>  
>  .. kernel-doc:: include/uapi/linux/landlock.h
> -    :identifiers: fs_access
> +    :identifiers: fs_access net_access
>  
>  Creating a new ruleset
>  ----------------------
> @@ -374,6 +402,7 @@ Extending a ruleset
>  
>  .. kernel-doc:: include/uapi/linux/landlock.h
>      :identifiers: landlock_rule_type landlock_path_beneath_attr
> +                  landlock_net_service_attr
>  
>  Enforcing a ruleset
>  -------------------
> @@ -451,6 +480,12 @@ always allowed when using a kernel that only supports the first or second ABI.
>  Starting with the Landlock ABI version 3, it is now possible to securely control
>  truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
>  
> +Network support (ABI < 4)
> +-------------------------
> +
> +Starting with the Landlock ABI version 4, it is now possible to restrict TCP
> +bind and connect actions to only a set of allowed ports.
> +
>  .. _kernel_support:
>  
>  Kernel support
> @@ -469,6 +504,11 @@ still enable it by adding ``lsm=landlock,[...]`` to
>  Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
>  configuration.
>  
> +To be able to explicitly allow TCP operations (e.g., adding a network rule with
> +``LANDLOCK_ACCESS_NET_TCP_BIND``), the kernel must support TCP (``CONFIG_INET=y``).
> +Otherwise, sys_landlock_add_rule() returns an ``EAFNOSUPPORT`` error, which can
> +safely be ignored because this kind of TCP operation is already not possible.
> +
>  Questions and answers
>  =====================
>  
> -- 
> 2.25.1
> 

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-21 23:07   ` Günther Noack
@ 2023-01-23  9:38     ` Konstantin Meskhidze (A)
  2023-01-27 18:22       ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-23  9:38 UTC (permalink / raw)
  To: Günther Noack
  Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



1/22/2023 2:07 AM, Günther Noack пишет:
> Hello!
> 
> Thank you for sending these patches! I'll start poking a bit at the
> Go-Landlock library to see how we can support it there when this lands.
> 
> On Mon, Jan 16, 2023 at 04:58:18PM +0800, Konstantin Meskhidze wrote:
>> Describe network access rules for TCP sockets. Add network access
>> example in the tutorial. Add kernel configuration support for network.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Minor refactoring.
>> 
>> Changes since v7:
>> * Fixes documentaion logic errors and typos as Mickaёl suggested:
>> https://lore.kernel.org/netdev/9f354862-2bc3-39ea-92fd-53803d9bbc21@digikod.net/
>> 
>> Changes since v6:
>> * Adds network support documentaion.
>> 
>> ---
>>  Documentation/userspace-api/landlock.rst | 72 ++++++++++++++++++------
>>  1 file changed, 56 insertions(+), 16 deletions(-)
>> 
>> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
>> index d8cd8cd9ce25..980558b879d6 100644
>> --- a/Documentation/userspace-api/landlock.rst
>> +++ b/Documentation/userspace-api/landlock.rst
>> @@ -11,10 +11,10 @@ Landlock: unprivileged access control
>>  :Date: October 2022
>>  
>>  The goal of Landlock is to enable to restrict ambient rights (e.g. global
>> -filesystem access) for a set of processes.  Because Landlock is a stackable
>> -LSM, it makes possible to create safe security sandboxes as new security layers
>> -in addition to the existing system-wide access-controls. This kind of sandbox
>> -is expected to help mitigate the security impact of bugs or
>> +filesystem or network access) for a set of processes.  Because Landlock
>> +is a stackable LSM, it makes possible to create safe security sandboxes as new
>> +security layers in addition to the existing system-wide access-controls. This
>> +kind of sandbox is expected to help mitigate the security impact of bugs or
>>  unexpected/malicious behaviors in user space applications.  Landlock empowers
>>  any process, including unprivileged ones, to securely restrict themselves.
>>  
>> @@ -30,18 +30,20 @@ Landlock rules
>>  
>>  A Landlock rule describes an action on an object.  An object is currently a
>>  file hierarchy, and the related filesystem actions are defined with `access
>> -rights`_.  A set of rules is aggregated in a ruleset, which can then restrict
>> -the thread enforcing it, and its future children.
>> +rights`_.  Since ABI version 4 a port data appears with related network actions
>> +for TCP socket families.  A set of rules is aggregated in a ruleset, which
>> +can then restrict the thread enforcing it, and its future children.
>>  
>>  Defining and enforcing a security policy
>>  ----------------------------------------
>>  
>>  We first need to define the ruleset that will contain our rules.  For this
>>  example, the ruleset will contain rules that only allow read actions, but write
>> -actions will be denied.  The ruleset then needs to handle both of these kind of
>> +actions will be denied. The ruleset then needs to handle both of these kind of
> 
> (This one looks like an unintentional whitespace change; the
> double-space ending is used in this file, so we should probably stay
> consistent.)

   Got it. Thanks.
> 
>>  actions.  This is required for backward and forward compatibility (i.e. the
>>  kernel and user space may not know each other's supported restrictions), hence
>> -the need to be explicit about the denied-by-default access rights.
>> +the need to be explicit about the denied-by-default access rights.  Also ruleset
> 
> Wording nit: "Also, the ruleset"...?

   Right. Thanks for the nit.
> 
>> +will have network rules for specific ports, so it should handle network actions.
>>  
>>  .. code-block:: c
>>  
>> @@ -62,6 +64,9 @@ the need to be explicit about the denied-by-default access rights.
>>              LANDLOCK_ACCESS_FS_MAKE_SYM |
>>              LANDLOCK_ACCESS_FS_REFER |
>>              LANDLOCK_ACCESS_FS_TRUNCATE,
>> +        .handled_access_net =
>> +            LANDLOCK_ACCESS_NET_BIND_TCP |
>> +            LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>      };
>>  
>>  Because we may not know on which kernel version an application will be
>> @@ -70,14 +75,18 @@ should try to protect users as much as possible whatever the kernel they are
>>  using.  To avoid binary enforcement (i.e. either all security features or
>>  none), we can leverage a dedicated Landlock command to get the current version
>>  of the Landlock ABI and adapt the handled accesses.  Let's check if we should
>> -remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE``
>> -access rights, which are only supported starting with the second and third
>> -version of the ABI.
>> +remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE`` or
>> +network access rights, which are only supported starting with the second,
>> +third and fourth version of the ABI.
>>  
>>  .. code-block:: c
>>  
>>      int abi;
>>  
>> +    #define ACCESS_NET_BIND_CONNECT ( \
>> +        LANDLOCK_ACCESS_NET_BIND_TCP | \
>> +        LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> +
>>      abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
>>      if (abi < 0) {
>>          /* Degrades gracefully if Landlock is not handled. */
>> @@ -92,6 +101,11 @@ version of the ABI.
>>      case 2:
>>          /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
>>          ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
>> +    case 3:
>> +        /* Removes network support for ABI < 4 */
>> +		ruleset_attr.handled_access_net &=
>             ^^^^^
>             Nit: Indentation differs from "case 2"

   Got it. Will be fixed. Thanks.
>             
>> +			~(LANDLOCK_ACCESS_NET_BIND_TCP |
>> +			  LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>      }
>>  
>>  This enables to create an inclusive ruleset that will contain our rules.
>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>  ABI version.  In this example, this is not required because all of the requested
>>  ``allowed_access`` rights are already available in ABI 1.
>>  
>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>> -denying all other handled accesses for the filesystem.  The next step is to
>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>> -binary).
>> +For network access-control, we can add a set of rules that allow to use a port
>> +number for a specific action. All ports values must be defined in network byte
>> +order.
> 
> What is the point of asking user space to convert this to network byte
> order? It seems to me that the kernel would be able to convert it to
> network byte order very easily internally and in a single place -- why
> ask all of the users to deal with that complexity? Am I overlooking
> something?

  I had a discussion about this issue with Mickaёl.
  Please check these threads:
  1. 
https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
  2. 
https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
> 
>> +
>> +.. code-block:: c
>> +
>> +    struct landlock_net_service_attr net_service = {
>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> +        .port = htons(8080),
>> +    };
> 
> This is a more high-level comment:
> 
> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
> how do you envision this struct to evolve if other protocols need to
> be supported in the future?

   When TCP restrictions land into Linux, we need to think about UDP 
support. Then other protocols will be on the road. Anyway you are right 
this struct will be evolving in long term, but I don't have a particular 
envision now. Thanks for the question - we need to think about it.
> 
> Should this struct and the associated constants have "TCP" in its
> name, and other protocols use a separate struct in the future?
> 
>> +
>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>> +                            &net_service, 0);
>> +
>> +The next step is to restrict the current thread from gaining more privileges
>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>           ^^^^^^
>           "through" a SUID binary? "thanks to" sounds like it's desired
>           to do that, while we're actually trying to prevent it here?

   This is Mickaёl's part. Let's ask his opinion here.

   Mickaёl, any thoughts?

> 
>> +read access to ``/usr`` while denying all other handled accesses for the filesystem,
>> +and a second rule allowing TCP binding on port 8080.
>>  
>>  .. code-block:: c
>>  
>> @@ -355,7 +383,7 @@ Access rights
>>  -------------
>>  
>>  .. kernel-doc:: include/uapi/linux/landlock.h
>> -    :identifiers: fs_access
>> +    :identifiers: fs_access net_access
>>  
>>  Creating a new ruleset
>>  ----------------------
>> @@ -374,6 +402,7 @@ Extending a ruleset
>>  
>>  .. kernel-doc:: include/uapi/linux/landlock.h
>>      :identifiers: landlock_rule_type landlock_path_beneath_attr
>> +                  landlock_net_service_attr
>>  
>>  Enforcing a ruleset
>>  -------------------
>> @@ -451,6 +480,12 @@ always allowed when using a kernel that only supports the first or second ABI.
>>  Starting with the Landlock ABI version 3, it is now possible to securely control
>>  truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right.
>>  
>> +Network support (ABI < 4)
>> +-------------------------
>> +
>> +Starting with the Landlock ABI version 4, it is now possible to restrict TCP
>> +bind and connect actions to only a set of allowed ports.
>> +
>>  .. _kernel_support:
>>  
>>  Kernel support
>> @@ -469,6 +504,11 @@ still enable it by adding ``lsm=landlock,[...]`` to
>>  Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader
>>  configuration.
>>  
>> +To be able to explicitly allow TCP operations (e.g., adding a network rule with
>> +``LANDLOCK_ACCESS_NET_TCP_BIND``), the kernel must support TCP (``CONFIG_INET=y``).
>> +Otherwise, sys_landlock_add_rule() returns an ``EAFNOSUPPORT`` error, which can
>> +safely be ignored because this kind of TCP operation is already not possible.
>> +
>>  Questions and answers
>>  =====================
>>  
>> -- 
>> 2.25.1
>> 
> .

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-23  9:38     ` Konstantin Meskhidze (A)
@ 2023-01-27 18:22       ` Mickaël Salaün
  2023-01-30 10:03         ` Konstantin Meskhidze (A)
  2023-01-30 12:26         ` Konstantin Meskhidze (A)
  0 siblings, 2 replies; 74+ messages in thread
From: Mickaël Salaün @ 2023-01-27 18:22 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/22/2023 2:07 AM, Günther Noack пишет:

[...]

>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>   ABI version.  In this example, this is not required because all of the requested
>>>   ``allowed_access`` rights are already available in ABI 1.
>>>   
>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>> -denying all other handled accesses for the filesystem.  The next step is to
>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>> -binary).
>>> +For network access-control, we can add a set of rules that allow to use a port
>>> +number for a specific action. All ports values must be defined in network byte
>>> +order.
>>
>> What is the point of asking user space to convert this to network byte
>> order? It seems to me that the kernel would be able to convert it to
>> network byte order very easily internally and in a single place -- why
>> ask all of the users to deal with that complexity? Am I overlooking
>> something?
> 
>    I had a discussion about this issue with Mickaёl.
>    Please check these threads:
>    1.
> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>    2.
> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/

I'm definitely not sure if this is the right solution, or if there is 
one. The rationale is to make it close to the current (POSIX) API. We 
didn't get many opinion about that but I'd really like to have a 
discussion about port endianness for this Landlock API.

I looked at some code (e.g. see [1]) and it seems that using htons() 
might make application patching more complex after all. What do you 
think? Is there some network (syscall) API that don't use this convention?

[1] https://github.com/landlock-lsm/tuto-lighttpd

>>
>>> +
>>> +.. code-block:: c
>>> +
>>> +    struct landlock_net_service_attr net_service = {
>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>> +        .port = htons(8080),
>>> +    };
>>
>> This is a more high-level comment:
>>
>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>> how do you envision this struct to evolve if other protocols need to
>> be supported in the future?
> 
>     When TCP restrictions land into Linux, we need to think about UDP
> support. Then other protocols will be on the road. Anyway you are right
> this struct will be evolving in long term, but I don't have a particular
> envision now. Thanks for the question - we need to think about it.
>>
>> Should this struct and the associated constants have "TCP" in its
>> name, and other protocols use a separate struct in the future?

Other protocols such as AF_VSOCK uses a 32-bit port. We could use a 
32-bits port field or ever a 64-bit one. The later could make more sense 
because each field would eventually be aligned on 64-bit. Picking a 
16-bit value was to help developers (and compilers/linters) with the 
"correct" type (for TCP).

If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it 
could make sense to have a dedicated attr struct specifying other 
properties (e.g. CID). Anyway, the API is flexible but it would be nice 
to not mess with it too much. What do you think?


>>
>>> +
>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>> +                            &net_service, 0);
>>> +
>>> +The next step is to restrict the current thread from gaining more privileges
>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>            ^^^^^^
>>            "through" a SUID binary? "thanks to" sounds like it's desired
>>            to do that, while we're actually trying to prevent it here?
> 
>     This is Mickaёl's part. Let's ask his opinion here.
> 
>     Mickaёl, any thoughts?

Yep, "through" looks better.

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-27 18:22       ` Mickaël Salaün
@ 2023-01-30 10:03         ` Konstantin Meskhidze (A)
  2023-02-21 16:16           ` Mickaël Salaün
  2023-01-30 12:26         ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-30 10:03 UTC (permalink / raw)
  To: Mickaël Salaün, Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



1/27/2023 9:22 PM, Mickaël Salaün пишет:
> 
> On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/22/2023 2:07 AM, Günther Noack пишет:
> 
> [...]
> 
>>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>>   ABI version.  In this example, this is not required because all of the requested
>>>>   ``allowed_access`` rights are already available in ABI 1.
>>>>   
>>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>>> -denying all other handled accesses for the filesystem.  The next step is to
>>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>>> -binary).
>>>> +For network access-control, we can add a set of rules that allow to use a port
>>>> +number for a specific action. All ports values must be defined in network byte
>>>> +order.
>>>
>>> What is the point of asking user space to convert this to network byte
>>> order? It seems to me that the kernel would be able to convert it to
>>> network byte order very easily internally and in a single place -- why
>>> ask all of the users to deal with that complexity? Am I overlooking
>>> something?
>> 
>>    I had a discussion about this issue with Mickaёl.
>>    Please check these threads:
>>    1.
>> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>>    2.
>> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
> 
> I'm definitely not sure if this is the right solution, or if there is
> one. The rationale is to make it close to the current (POSIX) API. We
> didn't get many opinion about that but I'd really like to have a
> discussion about port endianness for this Landlock API.

   As for me, the kernel should take care about port converting. This 
work should be done under the hood.

   Any thoughts?

> 
> I looked at some code (e.g. see [1]) and it seems that using htons()
> might make application patching more complex after all. What do you
> think? Is there some network (syscall) API that don't use this convention?
> 
> [1] https://github.com/landlock-lsm/tuto-lighttpd
> 
>>>
>>>> +
>>>> +.. code-block:: c
>>>> +
>>>> +    struct landlock_net_service_attr net_service = {
>>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>> +        .port = htons(8080),
>>>> +    };
>>>
>>> This is a more high-level comment:
>>>
>>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>>> how do you envision this struct to evolve if other protocols need to
>>> be supported in the future?
>> 
>>     When TCP restrictions land into Linux, we need to think about UDP
>> support. Then other protocols will be on the road. Anyway you are right
>> this struct will be evolving in long term, but I don't have a particular
>> envision now. Thanks for the question - we need to think about it.
>>>
>>> Should this struct and the associated constants have "TCP" in its
>>> name, and other protocols use a separate struct in the future?
> 
> Other protocols such as AF_VSOCK uses a 32-bit port. We could use a
> 32-bits port field or ever a 64-bit one. The later could make more sense
> because each field would eventually be aligned on 64-bit. Picking a
> 16-bit value was to help developers (and compilers/linters) with the
> "correct" type (for TCP).
> 
> If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it
> could make sense to have a dedicated attr struct specifying other
> properties (e.g. CID). Anyway, the API is flexible but it would be nice
> to not mess with it too much. What do you think?
> 
> 
>>>
>>>> +
>>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>> +                            &net_service, 0);
>>>> +
>>>> +The next step is to restrict the current thread from gaining more privileges
>>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>>            ^^^^^^
>>>            "through" a SUID binary? "thanks to" sounds like it's desired
>>>            to do that, while we're actually trying to prevent it here?
>> 
>>     This is Mickaёl's part. Let's ask his opinion here.
>> 
>>     Mickaёl, any thoughts?
> 
> Yep, "through" looks better.
> .

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-27 18:22       ` Mickaël Salaün
  2023-01-30 10:03         ` Konstantin Meskhidze (A)
@ 2023-01-30 12:26         ` Konstantin Meskhidze (A)
  1 sibling, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-01-30 12:26 UTC (permalink / raw)
  To: Mickaël Salaün, Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



1/27/2023 9:22 PM, Mickaël Salaün пишет:
> 
> On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/22/2023 2:07 AM, Günther Noack пишет:
> 
> [...]
> 
>>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>>   ABI version.  In this example, this is not required because all of the requested
>>>>   ``allowed_access`` rights are already available in ABI 1.
>>>>   
>>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>>> -denying all other handled accesses for the filesystem.  The next step is to
>>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>>> -binary).
>>>> +For network access-control, we can add a set of rules that allow to use a port
>>>> +number for a specific action. All ports values must be defined in network byte
>>>> +order.
>>>
>>> What is the point of asking user space to convert this to network byte
>>> order? It seems to me that the kernel would be able to convert it to
>>> network byte order very easily internally and in a single place -- why
>>> ask all of the users to deal with that complexity? Am I overlooking
>>> something?
>> 
>>    I had a discussion about this issue with Mickaёl.
>>    Please check these threads:
>>    1.
>> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>>    2.
>> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
> 
> I'm definitely not sure if this is the right solution, or if there is
> one. The rationale is to make it close to the current (POSIX) API. We
> didn't get many opinion about that but I'd really like to have a
> discussion about port endianness for this Landlock API.
> 
> I looked at some code (e.g. see [1]) and it seems that using htons()
> might make application patching more complex after all. What do you
> think? Is there some network (syscall) API that don't use this convention?
> 
> [1] https://github.com/landlock-lsm/tuto-lighttpd
> 
>>>
>>>> +
>>>> +.. code-block:: c
>>>> +
>>>> +    struct landlock_net_service_attr net_service = {
>>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>> +        .port = htons(8080),
>>>> +    };
>>>
>>> This is a more high-level comment:
>>>
>>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>>> how do you envision this struct to evolve if other protocols need to
>>> be supported in the future?
>> 
>>     When TCP restrictions land into Linux, we need to think about UDP
>> support. Then other protocols will be on the road. Anyway you are right
>> this struct will be evolving in long term, but I don't have a particular
>> envision now. Thanks for the question - we need to think about it.
>>>
>>> Should this struct and the associated constants have "TCP" in its
>>> name, and other protocols use a separate struct in the future?
> 
> Other protocols such as AF_VSOCK uses a 32-bit port. We could use a
> 32-bits port field or ever a 64-bit one. The later could make more sense
> because each field would eventually be aligned on 64-bit. Picking a
> 16-bit value was to help developers (and compilers/linters) with the
> "correct" type (for TCP).
> 
> If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it
> could make sense to have a dedicated attr struct specifying other
> properties (e.g. CID). Anyway, the API is flexible but it would be nice
> to not mess with it too much. What do you think?
> 
  I think it would not be hard to add new protocols in future. You are 
right - the current API is more or less flexible.
The main question is what other protocols are worth for landlocking.


> 
>>>
>>>> +
>>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>> +                            &net_service, 0);
>>>> +
>>>> +The next step is to restrict the current thread from gaining more privileges
>>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>>            ^^^^^^
>>>            "through" a SUID binary? "thanks to" sounds like it's desired
>>>            to do that, while we're actually trying to prevent it here?
>> 
>>     This is Mickaёl's part. Let's ask his opinion here.
>> 
>>     Mickaёl, any thoughts?
> 
> Yep, "through" looks better.
> .

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

* Re: [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type
  2023-01-16  8:58 ` [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type Konstantin Meskhidze
@ 2023-02-10 17:34   ` Mickaël Salaün
  2023-02-14  8:51     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:34 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Hi Konstantin,

I think this patch series is almost ready. Here is a first batch of 
review, I'll send more next week.


I forgot to update the documentation. Can you please squash the 
following patch into this one?


diff --git a/Documentation/userspace-api/landlock.rst 
b/Documentation/userspace-api/landlock.rst
index 980558b879d6..fc2be89b423f 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -416,9 +416,9 @@ Current limitations
  Filesystem topology modification
  --------------------------------

-As for file renaming and linking, a sandboxed thread cannot modify its
-filesystem topology, whether via :manpage:`mount(2)` or
-:manpage:`pivot_root(2)`.  However, :manpage:`chroot(2)` calls are not 
denied.
+Threads sandboxed with filesystem restrictions cannot modify filesystem
+topology, whether via :manpage:`mount(2)` or :manpage:`pivot_root(2)`.
+However, :manpage:`chroot(2)` calls are not denied.

  Special filesystems
  -------------------


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> From: Mickaël Salaün <mic@digikod.net>
> 
> Allow mount point and root directory changes when there is no filesystem
> rule tied to the current Landlock domain.  This doesn't change anything
> for now because a domain must have at least a (filesystem) rule, but
> this will change when other rule types will come.  For instance, a
> domain only restricting the network should have no impact on filesystem
> restrictions.
> 
> Add a new get_current_fs_domain() helper to quickly check filesystem
> rule existence for all filesystem LSM hooks.
> 
> Remove unnecessary inlining.
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
> 
> Changes since v8:
> * Refactors get_handled_fs_accesses().
> * Adds landlock_get_raw_fs_access_mask() helper.
> 

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

* Re: [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule
  2023-01-16  8:58 ` [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
@ 2023-02-10 17:36   ` Mickaël Salaün
  2023-02-14 10:15     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:36 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> Add a new landlock_key union and landlock_id structure to support
> a socket port rule type. A struct landlock_id identifies a unique entry
> in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
> port). There is one red-black tree per key type.
> 
> This patch also adds is_object_pointer() and get_root() helpers.
> is_object_pointer() returns true if key type is LANDLOCK_KEY_INODE.
> get_root() helper returns a red_black tree root pointer according to
> a key type.
> 
> Refactor landlock_insert_rule() and landlock_find_rule() to support coming
> network modifications. Adding or searching a rule in ruleset can now be
> done thanks to a Landlock ID argument passed to these helpers.
> 
> Remove unnecessary inlining.
> 

You need to keep the Co-developed-by before the Signed-off-by for my entry.

> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Refactors commit message.
> * Removes inlining.
> * Minor fixes.
> 
> Changes since v7:
> * Completes all the new field descriptions landlock_key,
>    landlock_key_type, landlock_id.
> * Refactors commit message, adds a co-developer.
> 
> Changes since v6:
> * Adds union landlock_key, enum landlock_key_type, and struct
>    landlock_id.
> * Refactors ruleset functions and improves switch/cases: create_rule(),
>    insert_rule(), get_root(), is_object_pointer(), free_rule(),
>    landlock_find_rule().
> * Refactors landlock_append_fs_rule() functions to support new
>    landlock_id type.
> 
> Changes since v5:
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors insert_rule() and create_rule() functions by deleting
> rule_type from their arguments list, it helps to reduce useless code.
> 
> Changes since v3:
> * Splits commit.
> * Refactors landlock_insert_rule and landlock_find_rule functions.
> * Rename new_ruleset->root_inode.
> 
> ---
>   security/landlock/fs.c      |  49 ++++++------
>   security/landlock/ruleset.c | 148 +++++++++++++++++++++++++-----------
>   security/landlock/ruleset.h |  65 +++++++++++++---
>   3 files changed, 185 insertions(+), 77 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 0ae54a639e16..273ed8549da1 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c

[...]

> @@ -191,12 +193,15 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>    *
>    * Returns NULL if no rule is found or if @dentry is negative.
>    */
> -static inline const struct landlock_rule *
> +static const struct landlock_rule *

Can you please create a (previous) dedicated patch for all the inlining 
changes?


>   find_rule(const struct landlock_ruleset *const domain,
>   	  const struct dentry *const dentry)
>   {
>   	const struct landlock_rule *rule;
>   	const struct inode *inode;
> +	struct landlock_id id = {
> +		.type = LANDLOCK_KEY_INODE,
> +	};
>   
>   	/* Ignores nonexistent leafs. */
>   	if (d_is_negative(dentry))

[...]

> @@ -652,7 +657,7 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>   }
>   
>   static int current_check_access_path(const struct path *const path,
> -					    const access_mask_t access_request)
> +				     const access_mask_t access_request)

This syntax fix should be moved to patch 2/12.


[...]

> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 1f3188b4e313..c5c88a100f74 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c

[...]

> @@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst,


> -		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
> -			err = -EINVAL;
> -			goto out_unlock;
> -		}
> -		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
> -			err = -EINVAL;
> -			goto out_unlock;
> -		}
> +		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
> +			return -EINVAL;
> +		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
> +			return -EINVAL;

This introduces two potential bugs. Why change this code?

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

* Re: [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks()
  2023-01-16  8:58 ` [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks() Konstantin Meskhidze
@ 2023-02-10 17:37   ` Mickaël Salaün
  2023-02-14 10:15     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:37 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> This patch renames and moves unmask_layers() and init_layer_masks()
> helpers to ruleset.c to share them with Landlock network implementation
> in following commits.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Refactors commit message.
> * Adds "landlock_" prefix for moved helpers.
> 
> Changes since v7:
> * Refactors commit message.
> 
> Changes since v6:
> * Moves get_handled_accesses() helper from ruleset.c back to fs.c,
>    cause it's not used in coming network commits.
> 
> Changes since v5:
> * Splits commit.
> * Moves init_layer_masks() and get_handled_accesses() helpers
> to ruleset.c and makes then non-static.
> * Formats code with clang-format-14.
> 
> ---

[...]

> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 3e1cffda128e..22590cac3d56 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -572,3 +572,101 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>   	}
>   	return NULL;
>   }
> +
> +/*
> + * @layer_masks is read and may be updated according to the access request and
> + * the matching rule.
> + *
> + * Returns true if the request is allowed (i.e. relevant layer masks for the
> + * request are empty).
> + */
> +bool landlock_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;
> +}
> +
> +/*

Please keep the original "/**"


> + * init_layer_masks - Initialize layer masks from an access request
> + *
> + * Populates @layer_masks such that for each access right in @access_request,
> + * the bits for all the layers are set where this access right is handled.
> + *
> + * @domain: The domain that defines the current restrictions.
> + * @access_request: The requested access rights to check.
> + * @layer_masks: The layer masks to populate.
> + *
> + * Returns: An access mask where each access right bit is set which is handled
> + * in any of the active layers in @domain.
> + */

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

* Re: [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks()
  2023-01-16  8:58 ` [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks() Konstantin Meskhidze
@ 2023-02-10 17:38   ` Mickaël Salaün
  2023-02-14 10:16     ` Konstantin Meskhidze (A)
  2023-02-21 18:07   ` Mickaël Salaün
  1 sibling, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:38 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> Add new key_type argument to the landlock_init_layer_masks() helper.
> Add a masks_array_size argument to the landlock_unmask_layers() helper.
> These modifications support implementing new rule types in the next
> Landlock versions.
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * None.
> 
> Changes since v7:
> * Refactors commit message, adds a co-developer.
> * Minor fixes.
> 
> Changes since v6:
> * Removes masks_size attribute from init_layer_masks().
> * Refactors init_layer_masks() with new landlock_key_type.
> 
> Changes since v5:
> * Splits commit.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors init_layer_masks(), get_handled_accesses()
> and unmask_layers() functions to support multiple rule types.
> * Refactors landlock_get_fs_access_mask() function with
> LANDLOCK_MASK_ACCESS_FS mask.
> 
> Changes since v3:
> * Splits commit.
> * Refactors landlock_unmask_layers functions.
> 
> ---
>   security/landlock/fs.c      | 43 ++++++++++++++++--------------
>   security/landlock/ruleset.c | 52 ++++++++++++++++++++++++++-----------
>   security/landlock/ruleset.h | 17 ++++++------
>   3 files changed, 70 insertions(+), 42 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 73a7399f93ba..a73dbd3f9ddb 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
>

[...]

> @@ -658,10 +677,13 @@ access_mask_t landlock_init_layer_masks(
>   		const unsigned long access_req = access_request;
>   		unsigned long access_bit;
>   
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> +		for_each_set_bit(access_bit, &access_req, num_access) {
> +			/*
> +			 * Artificially handles all initially denied by default
> +			 * access rights.
> +			 */

No need to re-add this old comment which was removed with patch 2/12.


>   			if (BIT_ULL(access_bit) &
> -			    landlock_get_fs_access_mask(domain, layer_level)) {
> +			    get_access_mask(domain, layer_level)) {
>   				(*layer_masks)[access_bit] |=
>   					BIT_ULL(layer_level);
>   				handled_accesses |= BIT_ULL(access_bit);
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 60a3c4d4d961..77349764e111 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -266,14 +266,15 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>   	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
>   	       ACCESS_FS_INITIALLY_DENIED;
>   }
> -bool landlock_unmask_layers(
> -	const struct landlock_rule *const rule,
> -	const access_mask_t access_request,
> -	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
> +bool landlock_unmask_layers(const struct landlock_rule *const rule,
> +			    const access_mask_t access_request,
> +			    layer_mask_t (*const layer_masks)[],
> +			    const size_t masks_array_size);
>   
> -access_mask_t landlock_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
> +landlock_init_layer_masks(const struct landlock_ruleset *const domain,
> +			  const access_mask_t access_request,
> +			  layer_mask_t (*const layer_masks)[],
> +			  const enum landlock_key_type key_type);
>   
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */

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

* Re: [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall
  2023-01-16  8:58 ` [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
@ 2023-02-10 17:38   ` Mickaël Salaün
  2023-02-14 10:18     ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:38 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> Change the landlock_add_rule() syscall to support new rule types
> in future Landlock versions. Add the add_rule_path_beneath() helper
> to support current filesystem rules.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Refactors commit message.
> * Minor fixes.
> 
> Changes since v7:
> * None
> 
> Changes since v6:
> * None
> 
> Changes since v5:
> * Refactors syscall landlock_add_rule() and add_rule_path_beneath() helper
> to make argument check ordering consistent and get rid of partial revertings
> in following patches.
> * Rolls back refactoring base_test.c seltest.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors add_rule_path_beneath() and landlock_add_rule() functions
> to optimize code usage.
> * Refactors base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
> rule type in landlock_add_rule() call.
> 
> Changes since v3:
> * Split commit.
> * Refactors landlock_add_rule syscall.
> 
> ---
>   security/landlock/syscalls.c | 94 +++++++++++++++++++-----------------
>   1 file changed, 50 insertions(+), 44 deletions(-)
> 
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index d35cd5d304db..73c80cd2cdbe 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -274,6 +274,49 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>   	return err;
>   }
>   
> +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
> +				 const void __user *const rule_attr)
> +{
> +	struct landlock_path_beneath_attr path_beneath_attr;
> +	struct path path;
> +	int res, err;
> +	access_mask_t mask;
> +
> +	/* Copies raw user space buffer, only one type for now. */
> +	res = copy_from_user(&path_beneath_attr, rule_attr,
> +			     sizeof(path_beneath_attr));
> +	if (res)
> +		return -EFAULT;
> +
> +	/*
> +	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
> +	 * are ignored in path walks.
> +	 */
> +	if (!path_beneath_attr.allowed_access) {
> +		return -ENOMSG;
> +	}

Please follows the ./scripts/checkpatch.pl conventions (i.e. no curly 
braces). You should add an empty line after this return though.



> +	/*
> +	 * Checks that allowed_access matches the @ruleset constraints
> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
> +	 */
> +	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
> +	if ((path_beneath_attr.allowed_access | mask) != mask) {
> +		return -EINVAL;
> +	}

Same here.

> +
> +	/* Gets and checks the new rule. */
> +	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
> +	if (err)
> +		return err;
> +
> +	/* Imports the new rule. */
> +	err = landlock_append_fs_rule(ruleset, &path,
> +				      path_beneath_attr.allowed_access);
> +	path_put(&path);
> +

No need for this empty line.

> +	return err;
> +}
> +

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

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-01-16  8:58 ` [PATCH v9 08/12] landlock: Add network rules and TCP hooks support Konstantin Meskhidze
@ 2023-02-10 17:39   ` Mickaël Salaün
  2023-02-14 10:19     ` Konstantin Meskhidze (A)
  2023-03-13  9:33     ` Konstantin Meskhidze (A)
  2023-02-21 18:04   ` Mickaël Salaün
  1 sibling, 2 replies; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:39 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> This commit adds network rules support in the ruleset management
> helpers and the landlock_create_ruleset syscall.
> Refactor user space API to support network actions. Add new network
> access flags, network rule and network attributes. Increment Landlock
> ABI version. Expand access_masks_t to u32 to be sure network access
> rights can be stored. Implement socket_bind() and socket_connect()
> LSM hooks, which enable to restrict TCP socket binding and connection
> to specific ports.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Squashes commits.
> * Refactors commit message.
> * Changes UAPI port field to __be16.
> * Changes logic of bind/connect hooks with AF_UNSPEC families.
> * Adds address length checking.
> * Minor fixes.
> 
> Changes since v7:
> * Squashes commits.
> * Increments ABI version to 4.
> * Refactors commit message.
> * Minor fixes.
> 
> Changes since v6:
> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>    because it OR values.
> * Makes landlock_add_net_access_mask() more resilient incorrect values.
> * Refactors landlock_get_net_access_mask().
> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>    LANDLOCK_NUM_ACCESS_FS as value.
> * Updates access_masks_t to u32 to support network access actions.
> * Refactors landlock internal functions to support network actions with
>    landlock_key/key_type/id types.
> 
> Changes since v5:
> * Gets rid of partial revert from landlock_add_rule
> syscall.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors landlock_create_ruleset() - splits ruleset and
> masks checks.
> * Refactors landlock_create_ruleset() and landlock mask
> setters/getters to support two rule types.
> * Refactors landlock_add_rule syscall add_rule_path_beneath
> function by factoring out get_ruleset_from_fd() and
> landlock_put_ruleset().
> 
> Changes since v3:
> * Splits commit.
> * Adds network rule support for internal landlock functions.
> * Adds set_mask and get_mask for network.
> * Adds rb_root root_net_port.

[...]

> +static int check_socket_access(const struct landlock_ruleset *const domain,
> +			       struct sockaddr *address, __be16 port,
> +			       access_mask_t access_request)
> +{
> +	bool allowed = false;
> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
> +	const struct landlock_rule *rule;
> +	access_mask_t handled_access;
> +	const struct landlock_id id = {
> +		.key.data = port,
> +		.type = LANDLOCK_KEY_NET_PORT,
> +	};
> +
> +	if (WARN_ON_ONCE(!domain))
> +		return 0;
> +	if (WARN_ON_ONCE(domain->num_layers < 1))
> +		return -EACCES;
> +
> +	switch (address->sa_family) {
> +	case AF_UNSPEC:
> +		/*
> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
> +		 * association, which have the same effect as closing the
> +		 * connection while retaining the socket object (i.e., the file
> +		 * descriptor).  As for dropping privileges, closing
> +		 * connections is always allowed.
> +		 */
> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
> +			return 0;
> +
> +		/*
> +		 * For compatibility reason, accept AF_UNSPEC for bind
> +		 * accesses (mapped to AF_INET) only if the address is
> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
> +		 * required to not wrongfully return -EACCES instead of
> +		 * -EAFNOSUPPORT.
> +		 */
> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
> +			const struct sockaddr_in *const sockaddr =
> +				(struct sockaddr_in *)address;
> +
> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
> +				return -EAFNOSUPPORT;
> +		}
> +
> +		fallthrough;
> +	case AF_INET:
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6:
> +#endif
> +		rule = landlock_find_rule(domain, id);
> +		handled_access = landlock_init_layer_masks(
> +			domain, access_request, &layer_masks,
> +			LANDLOCK_KEY_NET_PORT);
> +		allowed = landlock_unmask_layers(rule, handled_access,
> +						 &layer_masks,
> +						 ARRAY_SIZE(layer_masks));
> +
> +		fallthrough;

You can remove this fallthrough.


> +	}
> +	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 sockaddr->sin_port;
> +	}
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6: {
> +		const struct sockaddr_in6 *const sockaddr_ip6 =
> +			(struct sockaddr_in6 *)address;
> +		return sockaddr_ip6->sin6_port;
> +	}
> +#endif
> +	}
> +	WARN_ON_ONCE(1);
> +	return 0;
> +}
> +
> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
> +			    int addrlen)
> +{
> +	int ret;
> +	const struct landlock_ruleset *const dom =
> +		landlock_get_current_domain();

landlock_get_current_domain() should only be called by a 
get_current_net_domain() wrapper that checks if the current domain 
handles network accesses. See get_current_fs_domain() in patch 2/12.


> +
> +	if (!dom)
> +		return 0;
> +
> +	/* Check if it's a TCP socket. */
> +	if (sock->type != SOCK_STREAM)
> +		return 0;
> +
> +	ret = check_addrlen(address, addrlen);
> +	if (ret)
> +		return ret;
> +
> +	return check_socket_access(dom, address, get_port(address),
> +				   LANDLOCK_ACCESS_NET_BIND_TCP);
> +}

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

* Re: [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-01-16  8:58 ` [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
@ 2023-02-10 17:40   ` Mickaël Salaün
  2023-02-14 10:36     ` Konstantin Meskhidze (A)
  2023-02-21 18:05   ` Mickaël Salaün
  1 sibling, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-10 17:40 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> These test suites try to check edge cases for TCP sockets
> bind() and connect() actions.
> 
> socket:
> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
> for bind action with AF_UNSPEC socket family.
> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
> for connect action with AF_UNSPEC socket family.
> * ruleset_overlap: Tests with overlapping rules for one port.
> * ruleset_expanding: Tests with expanding rulesets in which rules are
> gradually added one by one, restricting sockets' connections.
> * inval: Tests with invalid user space supplied data:
>      - out of range ruleset attribute;
>      - unhandled allowed access;
>      - zero port value;
>      - zero access value;
>      - legitimate access values;
> * bind_connect_inval_addrlen: Tests with invalid address length
> for ipv4/ipv6 sockets.
> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
> 
> layout1:
> * with_net: Tests with network bind() socket action within
> filesystem directory access test.
> 
> Test coverage for security/landlock is 94.1% of 946 lines according
> to gcc/gcov-11.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
> * Refactors AF_UNSPEC tests.
> * Adds address length checking tests.
> * Convert ports in all tests to __be16.
> * Adds invalid port values tests.
> * Minor fixes.
> 
> Changes since v7:
> * Squashes all selftest commits.
> * Adds fs test with network bind() socket action.
> * Minor fixes.
> 
> ---
>   tools/testing/selftests/landlock/config     |    4 +
>   tools/testing/selftests/landlock/fs_test.c  |   65 ++
>   tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>   3 files changed, 1226 insertions(+)
>   create mode 100644 tools/testing/selftests/landlock/net_test.c
> 
> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
> index 0f0a65287bac..71f7e9a8a64c 100644
> --- a/tools/testing/selftests/landlock/config
> +++ b/tools/testing/selftests/landlock/config
> @@ -1,3 +1,7 @@
> +CONFIG_INET=y
> +CONFIG_IPV6=y
> +CONFIG_NET=y
> +CONFIG_NET_NS=y
>   CONFIG_OVERLAY_FS=y
>   CONFIG_SECURITY_LANDLOCK=y
>   CONFIG_SECURITY_PATH=y
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index b762b5419a89..5de4559c7fbb 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -8,8 +8,10 @@
>    */
>   
>   #define _GNU_SOURCE
> +#include <arpa/inet.h>
>   #include <fcntl.h>
>   #include <linux/landlock.h>
> +#include <netinet/in.h>
>   #include <sched.h>
>   #include <stdio.h>
>   #include <string.h>
> @@ -17,6 +19,7 @@
>   #include <sys/mount.h>
>   #include <sys/prctl.h>
>   #include <sys/sendfile.h>
> +#include <sys/socket.h>
>   #include <sys/stat.h>
>   #include <sys/sysmacros.h>
>   #include <unistd.h>
> @@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>   	}
>   }
>   
> +#define IP_ADDRESS "127.0.0.1"
> +
> +TEST_F_FORK(layout1, with_net)
> +{
> +	int sockfd;
> +	int sock_port = 15000;
> +	struct sockaddr_in addr4;
> +
> +	addr4.sin_family = AF_INET;
> +	addr4.sin_port = htons(sock_port);
> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
> +	memset(&addr4.sin_zero, '\0', 8);

Please don't mix declaration and code. Follow the 
./scripts/checkpatch.pl recommendations, except the "braces {} are not 
necessary" for tests (not kernel) code because of ASSERT_* macros.


> +
> +	const struct rule rules[] = {
> +		{
> +			.path = dir_s1d2,
> +			.access = ACCESS_RO,
> +		},
> +		{},
> +	};
> +
> +	struct landlock_ruleset_attr ruleset_attr_net = {
> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +	};
> +	struct landlock_net_service_attr net_service = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> +
> +		.port = htons(sock_port),
> +	};
> +
> +	/* Creates ruleset for network access. */
> +	const int ruleset_fd_net = landlock_create_ruleset(
> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
> +	ASSERT_LE(0, ruleset_fd_net);
> +
> +	/* Adds a network rule. */
> +	ASSERT_EQ(0,
> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
> +				    &net_service, 0));
> +
> +	enforce_ruleset(_metadata, ruleset_fd_net);
> +	ASSERT_EQ(0, close(ruleset_fd_net));
> +
> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
> +	ASSERT_LE(0, ruleset_fd);
> +	enforce_ruleset(_metadata, ruleset_fd);
> +	ASSERT_EQ(0, close(ruleset_fd));
> +
> +	/* Tests on a directory with the network rule loaded. */
> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
> +
> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port 15000. */
> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
> +
> +	/* Closes bounded socket. */
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
>   TEST_HARNESS_MAIN
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> new file mode 100644
> index 000000000000..b9543089a4d3
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/net_test.c

[...]

> +FIXTURE_TEARDOWN(socket){};

Remove such trailing ";" and format with clang-format for both 
FIXTURE_TEARDOWN().


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

* Re: [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type
  2023-02-10 17:34   ` Mickaël Salaün
@ 2023-02-14  8:51     ` Konstantin Meskhidze (A)
  2023-02-14 12:07       ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14  8:51 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:34 PM, Mickaël Salaün пишет:
> Hi Konstantin,
> 
> I think this patch series is almost ready. Here is a first batch of
> review, I'll send more next week.
> 
   Hi Mickaёl.
   thnaks for the review.

> 
> I forgot to update the documentation. Can you please squash the
> following patch into this one?

   No problem. I will squash.
   Can I download this doc patch from your repo or I can use the diff below?
> 
> 
> diff --git a/Documentation/userspace-api/landlock.rst
> b/Documentation/userspace-api/landlock.rst
> index 980558b879d6..fc2be89b423f 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -416,9 +416,9 @@ Current limitations
>    Filesystem topology modification
>    --------------------------------
> 
> -As for file renaming and linking, a sandboxed thread cannot modify its
> -filesystem topology, whether via :manpage:`mount(2)` or
> -:manpage:`pivot_root(2)`.  However, :manpage:`chroot(2)` calls are not
> denied.
> +Threads sandboxed with filesystem restrictions cannot modify filesystem
> +topology, whether via :manpage:`mount(2)` or :manpage:`pivot_root(2)`.
> +However, :manpage:`chroot(2)` calls are not denied.
> 
>    Special filesystems
>    -------------------
> 
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> From: Mickaël Salaün <mic@digikod.net>
>> 
>> Allow mount point and root directory changes when there is no filesystem
>> rule tied to the current Landlock domain.  This doesn't change anything
>> for now because a domain must have at least a (filesystem) rule, but
>> this will change when other rule types will come.  For instance, a
>> domain only restricting the network should have no impact on filesystem
>> restrictions.
>> 
>> Add a new get_current_fs_domain() helper to quickly check filesystem
>> rule existence for all filesystem LSM hooks.
>> 
>> Remove unnecessary inlining.
>> 
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>> ---
>> 
>> Changes since v8:
>> * Refactors get_handled_fs_accesses().
>> * Adds landlock_get_raw_fs_access_mask() helper.
>> 
> .

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

* Re: [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule
  2023-02-10 17:36   ` Mickaël Salaün
@ 2023-02-14 10:15     ` Konstantin Meskhidze (A)
  2023-02-14 12:09       ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14 10:15 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:36 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> Add a new landlock_key union and landlock_id structure to support
>> a socket port rule type. A struct landlock_id identifies a unique entry
>> in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
>> port). There is one red-black tree per key type.
>> 
>> This patch also adds is_object_pointer() and get_root() helpers.
>> is_object_pointer() returns true if key type is LANDLOCK_KEY_INODE.
>> get_root() helper returns a red_black tree root pointer according to
>> a key type.
>> 
>> Refactor landlock_insert_rule() and landlock_find_rule() to support coming
>> network modifications. Adding or searching a rule in ruleset can now be
>> done thanks to a Landlock ID argument passed to these helpers.
>> 
>> Remove unnecessary inlining.
>> 
> 
> You need to keep the Co-developed-by before the Signed-off-by for my entry.

   Got it.
> 
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Refactors commit message.
>> * Removes inlining.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * Completes all the new field descriptions landlock_key,
>>    landlock_key_type, landlock_id.
>> * Refactors commit message, adds a co-developer.
>> 
>> Changes since v6:
>> * Adds union landlock_key, enum landlock_key_type, and struct
>>    landlock_id.
>> * Refactors ruleset functions and improves switch/cases: create_rule(),
>>    insert_rule(), get_root(), is_object_pointer(), free_rule(),
>>    landlock_find_rule().
>> * Refactors landlock_append_fs_rule() functions to support new
>>    landlock_id type.
>> 
>> Changes since v5:
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors insert_rule() and create_rule() functions by deleting
>> rule_type from their arguments list, it helps to reduce useless code.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Refactors landlock_insert_rule and landlock_find_rule functions.
>> * Rename new_ruleset->root_inode.
>> 
>> ---
>>   security/landlock/fs.c      |  49 ++++++------
>>   security/landlock/ruleset.c | 148 +++++++++++++++++++++++++-----------
>>   security/landlock/ruleset.h |  65 +++++++++++++---
>>   3 files changed, 185 insertions(+), 77 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 0ae54a639e16..273ed8549da1 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
> 
> [...]
> 
>> @@ -191,12 +193,15 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>    *
>>    * Returns NULL if no rule is found or if @dentry is negative.
>>    */
>> -static inline const struct landlock_rule *
>> +static const struct landlock_rule *
> 
> Can you please create a (previous) dedicated patch for all the inlining
> changes?
>  
   a patch with just inlining changes?
> 
>>   find_rule(const struct landlock_ruleset *const domain,
>>   	  const struct dentry *const dentry)
>>   {
>>   	const struct landlock_rule *rule;
>>   	const struct inode *inode;
>> +	struct landlock_id id = {
>> +		.type = LANDLOCK_KEY_INODE,
>> +	};
>>   
>>   	/* Ignores nonexistent leafs. */
>>   	if (d_is_negative(dentry))
> 
> [...]
> 
>> @@ -652,7 +657,7 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
>>   }
>>   
>>   static int current_check_access_path(const struct path *const path,
>> -					    const access_mask_t access_request)
>> +				     const access_mask_t access_request)
> 
> This syntax fix should be moved to patch 2/12.
> 
   Ok. Got it.
> 
> [...]
> 
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 1f3188b4e313..c5c88a100f74 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
> 
> [...]
> 
>> @@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
> 
> 
>> -		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
>> -			err = -EINVAL;
>> -			goto out_unlock;
>> -		}
>> -		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
>> -			err = -EINVAL;
>> -			goto out_unlock;
>> -		}
>> +		if (WARN_ON_ONCE(walker_rule->num_layers != 1))
>> +			return -EINVAL;
>> +		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
>> +			return -EINVAL;
> 
> This introduces two potential bugs. Why change this code?

   My bad. These changes will appear in 4/12. Will be fixed.
   Thanks.
> .

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

* Re: [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks()
  2023-02-10 17:37   ` Mickaël Salaün
@ 2023-02-14 10:15     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14 10:15 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:37 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> This patch renames and moves unmask_layers() and init_layer_masks()
>> helpers to ruleset.c to share them with Landlock network implementation
>> in following commits.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Refactors commit message.
>> * Adds "landlock_" prefix for moved helpers.
>> 
>> Changes since v7:
>> * Refactors commit message.
>> 
>> Changes since v6:
>> * Moves get_handled_accesses() helper from ruleset.c back to fs.c,
>>    cause it's not used in coming network commits.
>> 
>> Changes since v5:
>> * Splits commit.
>> * Moves init_layer_masks() and get_handled_accesses() helpers
>> to ruleset.c and makes then non-static.
>> * Formats code with clang-format-14.
>> 
>> ---
> 
> [...]
> 
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 3e1cffda128e..22590cac3d56 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -572,3 +572,101 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>>   	}
>>   	return NULL;
>>   }
>> +
>> +/*
>> + * @layer_masks is read and may be updated according to the access request and
>> + * the matching rule.
>> + *
>> + * Returns true if the request is allowed (i.e. relevant layer masks for the
>> + * request are empty).
>> + */
>> +bool landlock_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;
>> +}
>> +
>> +/*
> 
> Please keep the original "/**"

   Got it. Thanks.
> 
> 
>> + * init_layer_masks - Initialize layer masks from an access request
>> + *
>> + * Populates @layer_masks such that for each access right in @access_request,
>> + * the bits for all the layers are set where this access right is handled.
>> + *
>> + * @domain: The domain that defines the current restrictions.
>> + * @access_request: The requested access rights to check.
>> + * @layer_masks: The layer masks to populate.
>> + *
>> + * Returns: An access mask where each access right bit is set which is handled
>> + * in any of the active layers in @domain.
>> + */
> .

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

* Re: [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks()
  2023-02-10 17:38   ` Mickaël Salaün
@ 2023-02-14 10:16     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14 10:16 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:38 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> Add new key_type argument to the landlock_init_layer_masks() helper.
>> Add a masks_array_size argument to the landlock_unmask_layers() helper.
>> These modifications support implementing new rule types in the next
>> Landlock versions.
>> 
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * None.
>> 
>> Changes since v7:
>> * Refactors commit message, adds a co-developer.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Removes masks_size attribute from init_layer_masks().
>> * Refactors init_layer_masks() with new landlock_key_type.
>> 
>> Changes since v5:
>> * Splits commit.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors init_layer_masks(), get_handled_accesses()
>> and unmask_layers() functions to support multiple rule types.
>> * Refactors landlock_get_fs_access_mask() function with
>> LANDLOCK_MASK_ACCESS_FS mask.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Refactors landlock_unmask_layers functions.
>> 
>> ---
>>   security/landlock/fs.c      | 43 ++++++++++++++++--------------
>>   security/landlock/ruleset.c | 52 ++++++++++++++++++++++++++-----------
>>   security/landlock/ruleset.h | 17 ++++++------
>>   3 files changed, 70 insertions(+), 42 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 73a7399f93ba..a73dbd3f9ddb 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>>
> 
> [...]
> 
>> @@ -658,10 +677,13 @@ access_mask_t landlock_init_layer_masks(
>>   		const unsigned long access_req = access_request;
>>   		unsigned long access_bit;
>>   
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> +		for_each_set_bit(access_bit, &access_req, num_access) {
>> +			/*
>> +			 * Artificially handles all initially denied by default
>> +			 * access rights.
>> +			 */
> 
> No need to re-add this old comment which was removed with patch 2/12.
> 
  Ok. Will be fixed.
> 
>>   			if (BIT_ULL(access_bit) &
>> -			    landlock_get_fs_access_mask(domain, layer_level)) {
>> +			    get_access_mask(domain, layer_level)) {
>>   				(*layer_masks)[access_bit] |=
>>   					BIT_ULL(layer_level);
>>   				handled_accesses |= BIT_ULL(access_bit);
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index 60a3c4d4d961..77349764e111 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -266,14 +266,15 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>>   	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
>>   	       ACCESS_FS_INITIALLY_DENIED;
>>   }
>> -bool landlock_unmask_layers(
>> -	const struct landlock_rule *const rule,
>> -	const access_mask_t access_request,
>> -	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>> +bool landlock_unmask_layers(const struct landlock_rule *const rule,
>> +			    const access_mask_t access_request,
>> +			    layer_mask_t (*const layer_masks)[],
>> +			    const size_t masks_array_size);
>>   
>> -access_mask_t landlock_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
>> +landlock_init_layer_masks(const struct landlock_ruleset *const domain,
>> +			  const access_mask_t access_request,
>> +			  layer_mask_t (*const layer_masks)[],
>> +			  const enum landlock_key_type key_type);
>>   
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> .

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

* Re: [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall
  2023-02-10 17:38   ` Mickaël Salaün
@ 2023-02-14 10:18     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14 10:18 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:38 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> Change the landlock_add_rule() syscall to support new rule types
>> in future Landlock versions. Add the add_rule_path_beneath() helper
>> to support current filesystem rules.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * None
>> 
>> Changes since v6:
>> * None
>> 
>> Changes since v5:
>> * Refactors syscall landlock_add_rule() and add_rule_path_beneath() helper
>> to make argument check ordering consistent and get rid of partial revertings
>> in following patches.
>> * Rolls back refactoring base_test.c seltest.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors add_rule_path_beneath() and landlock_add_rule() functions
>> to optimize code usage.
>> * Refactors base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH
>> rule type in landlock_add_rule() call.
>> 
>> Changes since v3:
>> * Split commit.
>> * Refactors landlock_add_rule syscall.
>> 
>> ---
>>   security/landlock/syscalls.c | 94 +++++++++++++++++++-----------------
>>   1 file changed, 50 insertions(+), 44 deletions(-)
>> 
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index d35cd5d304db..73c80cd2cdbe 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -274,6 +274,49 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>>   	return err;
>>   }
>>   
>> +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>> +				 const void __user *const rule_attr)
>> +{
>> +	struct landlock_path_beneath_attr path_beneath_attr;
>> +	struct path path;
>> +	int res, err;
>> +	access_mask_t mask;
>> +
>> +	/* Copies raw user space buffer, only one type for now. */
>> +	res = copy_from_user(&path_beneath_attr, rule_attr,
>> +			     sizeof(path_beneath_attr));
>> +	if (res)
>> +		return -EFAULT;
>> +
>> +	/*
>> +	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
>> +	 * are ignored in path walks.
>> +	 */
>> +	if (!path_beneath_attr.allowed_access) {
>> +		return -ENOMSG;
>> +	}
> 
> Please follows the ./scripts/checkpatch.pl conventions (i.e. no curly
> braces). You should add an empty line after this return though.
> 
  Ok. I will fix it.
> 
> 
>> +	/*
>> +	 * Checks that allowed_access matches the @ruleset constraints
>> +	 * (ruleset->access_masks[0] is automatically upgraded to 64-bits).
>> +	 */
>> +	mask = landlock_get_raw_fs_access_mask(ruleset, 0);
>> +	if ((path_beneath_attr.allowed_access | mask) != mask) {
>> +		return -EINVAL;
>> +	}
> 
> Same here.

   Got it.
> 
>> +
>> +	/* Gets and checks the new rule. */
>> +	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
>> +	if (err)
>> +		return err;
>> +
>> +	/* Imports the new rule. */
>> +	err = landlock_append_fs_rule(ruleset, &path,
>> +				      path_beneath_attr.allowed_access);
>> +	path_put(&path);
>> +
> 
> No need for this empty line.

   Ok. Thanks for noticing.
> 
>> +	return err;
>> +}
>> +
> .

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

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-02-10 17:39   ` Mickaël Salaün
@ 2023-02-14 10:19     ` Konstantin Meskhidze (A)
  2023-03-13  9:33     ` Konstantin Meskhidze (A)
  1 sibling, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14 10:19 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:39 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> This commit adds network rules support in the ruleset management
>> helpers and the landlock_create_ruleset syscall.
>> Refactor user space API to support network actions. Add new network
>> access flags, network rule and network attributes. Increment Landlock
>> ABI version. Expand access_masks_t to u32 to be sure network access
>> rights can be stored. Implement socket_bind() and socket_connect()
>> LSM hooks, which enable to restrict TCP socket binding and connection
>> to specific ports.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Squashes commits.
>> * Refactors commit message.
>> * Changes UAPI port field to __be16.
>> * Changes logic of bind/connect hooks with AF_UNSPEC families.
>> * Adds address length checking.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * Squashes commits.
>> * Increments ABI version to 4.
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>    because it OR values.
>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_net_access_mask().
>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>    LANDLOCK_NUM_ACCESS_FS as value.
>> * Updates access_masks_t to u32 to support network access actions.
>> * Refactors landlock internal functions to support network actions with
>>    landlock_key/key_type/id types.
>> 
>> Changes since v5:
>> * Gets rid of partial revert from landlock_add_rule
>> syscall.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactors landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds network rule support for internal landlock functions.
>> * Adds set_mask and get_mask for network.
>> * Adds rb_root root_net_port.
> 
> [...]
> 
>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>> +			       struct sockaddr *address, __be16 port,
>> +			       access_mask_t access_request)
>> +{
>> +	bool allowed = false;
>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>> +	const struct landlock_rule *rule;
>> +	access_mask_t handled_access;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +
>> +	if (WARN_ON_ONCE(!domain))
>> +		return 0;
>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>> +		return -EACCES;
>> +
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +		/*
>> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
>> +		 * association, which have the same effect as closing the
>> +		 * connection while retaining the socket object (i.e., the file
>> +		 * descriptor).  As for dropping privileges, closing
>> +		 * connections is always allowed.
>> +		 */
>> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> +			return 0;
>> +
>> +		/*
>> +		 * For compatibility reason, accept AF_UNSPEC for bind
>> +		 * accesses (mapped to AF_INET) only if the address is
>> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
>> +		 * required to not wrongfully return -EACCES instead of
>> +		 * -EAFNOSUPPORT.
>> +		 */
>> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
>> +			const struct sockaddr_in *const sockaddr =
>> +				(struct sockaddr_in *)address;
>> +
>> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
>> +				return -EAFNOSUPPORT;
>> +		}
>> +
>> +		fallthrough;
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		rule = landlock_find_rule(domain, id);
>> +		handled_access = landlock_init_layer_masks(
>> +			domain, access_request, &layer_masks,
>> +			LANDLOCK_KEY_NET_PORT);
>> +		allowed = landlock_unmask_layers(rule, handled_access,
>> +						 &layer_masks,
>> +						 ARRAY_SIZE(layer_masks));
>> +
>> +		fallthrough;
> 
> You can remove this fallthrough.

   Got it.
> 
> 
>> +	}
>> +	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 sockaddr->sin_port;
>> +	}
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6: {
>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>> +			(struct sockaddr_in6 *)address;
>> +		return sockaddr_ip6->sin6_port;
>> +	}
>> +#endif
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>> +			    int addrlen)
>> +{
>> +	int ret;
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
> 
> landlock_get_current_domain() should only be called by a
> get_current_net_domain() wrapper that checks if the current domain
> handles network accesses. See get_current_fs_domain() in patch 2/12.
> 
   Ok. Thanks for a tip.
> 
>> +
>> +	if (!dom)
>> +		return 0;
>> +
>> +	/* Check if it's a TCP socket. */
>> +	if (sock->type != SOCK_STREAM)
>> +		return 0;
>> +
>> +	ret = check_addrlen(address, addrlen);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return check_socket_access(dom, address, get_port(address),
>> +				   LANDLOCK_ACCESS_NET_BIND_TCP);
>> +}
> .

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

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



2/10/2023 8:40 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> These test suites try to check edge cases for TCP sockets
>> bind() and connect() actions.
>> 
>> socket:
>> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
>> for bind action with AF_UNSPEC socket family.
>> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
>> for connect action with AF_UNSPEC socket family.
>> * ruleset_overlap: Tests with overlapping rules for one port.
>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>> gradually added one by one, restricting sockets' connections.
>> * inval: Tests with invalid user space supplied data:
>>      - out of range ruleset attribute;
>>      - unhandled allowed access;
>>      - zero port value;
>>      - zero access value;
>>      - legitimate access values;
>> * bind_connect_inval_addrlen: Tests with invalid address length
>> for ipv4/ipv6 sockets.
>> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
>> 
>> layout1:
>> * with_net: Tests with network bind() socket action within
>> filesystem directory access test.
>> 
>> Test coverage for security/landlock is 94.1% of 946 lines according
>> to gcc/gcov-11.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
>> * Refactors AF_UNSPEC tests.
>> * Adds address length checking tests.
>> * Convert ports in all tests to __be16.
>> * Adds invalid port values tests.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * Squashes all selftest commits.
>> * Adds fs test with network bind() socket action.
>> * Minor fixes.
>> 
>> ---
>>   tools/testing/selftests/landlock/config     |    4 +
>>   tools/testing/selftests/landlock/fs_test.c  |   65 ++
>>   tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>>   3 files changed, 1226 insertions(+)
>>   create mode 100644 tools/testing/selftests/landlock/net_test.c
>> 
>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>> index 0f0a65287bac..71f7e9a8a64c 100644
>> --- a/tools/testing/selftests/landlock/config
>> +++ b/tools/testing/selftests/landlock/config
>> @@ -1,3 +1,7 @@
>> +CONFIG_INET=y
>> +CONFIG_IPV6=y
>> +CONFIG_NET=y
>> +CONFIG_NET_NS=y
>>   CONFIG_OVERLAY_FS=y
>>   CONFIG_SECURITY_LANDLOCK=y
>>   CONFIG_SECURITY_PATH=y
>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>> index b762b5419a89..5de4559c7fbb 100644
>> --- a/tools/testing/selftests/landlock/fs_test.c
>> +++ b/tools/testing/selftests/landlock/fs_test.c
>> @@ -8,8 +8,10 @@
>>    */
>>   
>>   #define _GNU_SOURCE
>> +#include <arpa/inet.h>
>>   #include <fcntl.h>
>>   #include <linux/landlock.h>
>> +#include <netinet/in.h>
>>   #include <sched.h>
>>   #include <stdio.h>
>>   #include <string.h>
>> @@ -17,6 +19,7 @@
>>   #include <sys/mount.h>
>>   #include <sys/prctl.h>
>>   #include <sys/sendfile.h>
>> +#include <sys/socket.h>
>>   #include <sys/stat.h>
>>   #include <sys/sysmacros.h>
>>   #include <unistd.h>
>> @@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>   	}
>>   }
>>   
>> +#define IP_ADDRESS "127.0.0.1"
>> +
>> +TEST_F_FORK(layout1, with_net)
>> +{
>> +	int sockfd;
>> +	int sock_port = 15000;
>> +	struct sockaddr_in addr4;
>> +
>> +	addr4.sin_family = AF_INET;
>> +	addr4.sin_port = htons(sock_port);
>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>> +	memset(&addr4.sin_zero, '\0', 8);
> 
> Please don't mix declaration and code. Follow the
> ./scripts/checkpatch.pl recommendations, except the "braces {} are not
> necessary" for tests (not kernel) code because of ASSERT_* macros.
> 
   Ok. Will be moved down the code.
   Thank you.
> 
>> +
>> +	const struct rule rules[] = {
>> +		{
>> +			.path = dir_s1d2,
>> +			.access = ACCESS_RO,
>> +		},
>> +		{},
>> +	};
>> +
>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +	};
>> +	struct landlock_net_service_attr net_service = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> +
>> +		.port = htons(sock_port),
>> +	};
>> +
>> +	/* Creates ruleset for network access. */
>> +	const int ruleset_fd_net = landlock_create_ruleset(
>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>> +	ASSERT_LE(0, ruleset_fd_net);
>> +
>> +	/* Adds a network rule. */
>> +	ASSERT_EQ(0,
>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>> +				    &net_service, 0));
>> +
>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>> +
>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>> +	ASSERT_LE(0, ruleset_fd);
>> +	enforce_ruleset(_metadata, ruleset_fd);
>> +	ASSERT_EQ(0, close(ruleset_fd));
>> +
>> +	/* Tests on a directory with the network rule loaded. */
>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>> +
>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port 15000. */
>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>> +
>> +	/* Closes bounded socket. */
>> +	ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>>   TEST_HARNESS_MAIN
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> new file mode 100644
>> index 000000000000..b9543089a4d3
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/net_test.c
> 
> [...]
> 
>> +FIXTURE_TEARDOWN(socket){};
> 
> Remove such trailing ";" and format with clang-format for both
> FIXTURE_TEARDOWN().

Like this :

FIXTURE_TEARDOWN(socket)
{
}

???
> 
> .

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

* Re: [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type
  2023-02-14  8:51     ` Konstantin Meskhidze (A)
@ 2023-02-14 12:07       ` Mickaël Salaün
  2023-02-14 12:57         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-14 12:07 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 14/02/2023 09:51, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/10/2023 8:34 PM, Mickaël Salaün пишет:
>> Hi Konstantin,
>>
>> I think this patch series is almost ready. Here is a first batch of
>> review, I'll send more next week.
>>
>     Hi Mickaёl.
>     thnaks for the review.
> 
>>
>> I forgot to update the documentation. Can you please squash the
>> following patch into this one?
> 
>     No problem. I will squash.
>     Can I download this doc patch from your repo or I can use the diff below?

You can take the diff below.

>>
>>
>> diff --git a/Documentation/userspace-api/landlock.rst
>> b/Documentation/userspace-api/landlock.rst
>> index 980558b879d6..fc2be89b423f 100644
>> --- a/Documentation/userspace-api/landlock.rst
>> +++ b/Documentation/userspace-api/landlock.rst
>> @@ -416,9 +416,9 @@ Current limitations
>>     Filesystem topology modification
>>     --------------------------------
>>
>> -As for file renaming and linking, a sandboxed thread cannot modify its
>> -filesystem topology, whether via :manpage:`mount(2)` or
>> -:manpage:`pivot_root(2)`.  However, :manpage:`chroot(2)` calls are not
>> denied.
>> +Threads sandboxed with filesystem restrictions cannot modify filesystem
>> +topology, whether via :manpage:`mount(2)` or :manpage:`pivot_root(2)`.
>> +However, :manpage:`chroot(2)` calls are not denied.
>>
>>     Special filesystems
>>     -------------------
>>
>>
>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>> From: Mickaël Salaün <mic@digikod.net>
>>>
>>> Allow mount point and root directory changes when there is no filesystem
>>> rule tied to the current Landlock domain.  This doesn't change anything
>>> for now because a domain must have at least a (filesystem) rule, but
>>> this will change when other rule types will come.  For instance, a
>>> domain only restricting the network should have no impact on filesystem
>>> restrictions.
>>>
>>> Add a new get_current_fs_domain() helper to quickly check filesystem
>>> rule existence for all filesystem LSM hooks.
>>>
>>> Remove unnecessary inlining.
>>>
>>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>>> ---
>>>
>>> Changes since v8:
>>> * Refactors get_handled_fs_accesses().
>>> * Adds landlock_get_raw_fs_access_mask() helper.
>>>
>> .

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

* Re: [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule
  2023-02-14 10:15     ` Konstantin Meskhidze (A)
@ 2023-02-14 12:09       ` Mickaël Salaün
  2023-02-14 13:28         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-14 12:09 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 14/02/2023 11:15, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/10/2023 8:36 PM, Mickaël Salaün пишет:
>>
>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>> Add a new landlock_key union and landlock_id structure to support
>>> a socket port rule type. A struct landlock_id identifies a unique entry
>>> in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
>>> port). There is one red-black tree per key type.
>>>
>>> This patch also adds is_object_pointer() and get_root() helpers.
>>> is_object_pointer() returns true if key type is LANDLOCK_KEY_INODE.
>>> get_root() helper returns a red_black tree root pointer according to
>>> a key type.
>>>
>>> Refactor landlock_insert_rule() and landlock_find_rule() to support coming
>>> network modifications. Adding or searching a rule in ruleset can now be
>>> done thanks to a Landlock ID argument passed to these helpers.
>>>
>>> Remove unnecessary inlining.
>>>
>>
>> You need to keep the Co-developed-by before the Signed-off-by for my entry.
> 
>     Got it.
>>
>>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v8:
>>> * Refactors commit message.
>>> * Removes inlining.
>>> * Minor fixes.
>>>
>>> Changes since v7:
>>> * Completes all the new field descriptions landlock_key,
>>>     landlock_key_type, landlock_id.
>>> * Refactors commit message, adds a co-developer.
>>>
>>> Changes since v6:
>>> * Adds union landlock_key, enum landlock_key_type, and struct
>>>     landlock_id.
>>> * Refactors ruleset functions and improves switch/cases: create_rule(),
>>>     insert_rule(), get_root(), is_object_pointer(), free_rule(),
>>>     landlock_find_rule().
>>> * Refactors landlock_append_fs_rule() functions to support new
>>>     landlock_id type.
>>>
>>> Changes since v5:
>>> * Formats code with clang-format-14.
>>>
>>> Changes since v4:
>>> * Refactors insert_rule() and create_rule() functions by deleting
>>> rule_type from their arguments list, it helps to reduce useless code.
>>>
>>> Changes since v3:
>>> * Splits commit.
>>> * Refactors landlock_insert_rule and landlock_find_rule functions.
>>> * Rename new_ruleset->root_inode.
>>>
>>> ---
>>>    security/landlock/fs.c      |  49 ++++++------
>>>    security/landlock/ruleset.c | 148 +++++++++++++++++++++++++-----------
>>>    security/landlock/ruleset.h |  65 +++++++++++++---
>>>    3 files changed, 185 insertions(+), 77 deletions(-)
>>>
>>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>>> index 0ae54a639e16..273ed8549da1 100644
>>> --- a/security/landlock/fs.c
>>> +++ b/security/landlock/fs.c
>>
>> [...]
>>
>>> @@ -191,12 +193,15 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>>     *
>>>     * Returns NULL if no rule is found or if @dentry is negative.
>>>     */
>>> -static inline const struct landlock_rule *
>>> +static const struct landlock_rule *
>>
>> Can you please create a (previous) dedicated patch for all the inlining
>> changes?
>>   
>     a patch with just inlining changes?

Yes, a new patch with just the inlining changes extracted from this patch.

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

* Re: [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-02-14 10:36     ` Konstantin Meskhidze (A)
@ 2023-02-14 12:13       ` Mickaël Salaün
  2023-02-14 13:28         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-14 12:13 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 14/02/2023 11:36, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/10/2023 8:40 PM, Mickaël Salaün пишет:
>>
>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>> These test suites try to check edge cases for TCP sockets
>>> bind() and connect() actions.
>>>
>>> socket:
>>> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
>>> for bind action with AF_UNSPEC socket family.
>>> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
>>> for connect action with AF_UNSPEC socket family.
>>> * ruleset_overlap: Tests with overlapping rules for one port.
>>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>>> gradually added one by one, restricting sockets' connections.
>>> * inval: Tests with invalid user space supplied data:
>>>       - out of range ruleset attribute;
>>>       - unhandled allowed access;
>>>       - zero port value;
>>>       - zero access value;
>>>       - legitimate access values;
>>> * bind_connect_inval_addrlen: Tests with invalid address length
>>> for ipv4/ipv6 sockets.
>>> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
>>>
>>> layout1:
>>> * with_net: Tests with network bind() socket action within
>>> filesystem directory access test.
>>>
>>> Test coverage for security/landlock is 94.1% of 946 lines according
>>> to gcc/gcov-11.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v8:
>>> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
>>> * Refactors AF_UNSPEC tests.
>>> * Adds address length checking tests.
>>> * Convert ports in all tests to __be16.
>>> * Adds invalid port values tests.
>>> * Minor fixes.
>>>
>>> Changes since v7:
>>> * Squashes all selftest commits.
>>> * Adds fs test with network bind() socket action.
>>> * Minor fixes.
>>>
>>> ---
>>>    tools/testing/selftests/landlock/config     |    4 +
>>>    tools/testing/selftests/landlock/fs_test.c  |   65 ++
>>>    tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>>>    3 files changed, 1226 insertions(+)
>>>    create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>
>>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>>> index 0f0a65287bac..71f7e9a8a64c 100644
>>> --- a/tools/testing/selftests/landlock/config
>>> +++ b/tools/testing/selftests/landlock/config
>>> @@ -1,3 +1,7 @@
>>> +CONFIG_INET=y
>>> +CONFIG_IPV6=y
>>> +CONFIG_NET=y
>>> +CONFIG_NET_NS=y
>>>    CONFIG_OVERLAY_FS=y
>>>    CONFIG_SECURITY_LANDLOCK=y
>>>    CONFIG_SECURITY_PATH=y
>>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>>> index b762b5419a89..5de4559c7fbb 100644
>>> --- a/tools/testing/selftests/landlock/fs_test.c
>>> +++ b/tools/testing/selftests/landlock/fs_test.c
>>> @@ -8,8 +8,10 @@
>>>     */
>>>    
>>>    #define _GNU_SOURCE
>>> +#include <arpa/inet.h>
>>>    #include <fcntl.h>
>>>    #include <linux/landlock.h>
>>> +#include <netinet/in.h>
>>>    #include <sched.h>
>>>    #include <stdio.h>
>>>    #include <string.h>
>>> @@ -17,6 +19,7 @@
>>>    #include <sys/mount.h>
>>>    #include <sys/prctl.h>
>>>    #include <sys/sendfile.h>
>>> +#include <sys/socket.h>
>>>    #include <sys/stat.h>
>>>    #include <sys/sysmacros.h>
>>>    #include <unistd.h>
>>> @@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>>    	}
>>>    }
>>>    
>>> +#define IP_ADDRESS "127.0.0.1"
>>> +
>>> +TEST_F_FORK(layout1, with_net)
>>> +{
>>> +	int sockfd;
>>> +	int sock_port = 15000;
>>> +	struct sockaddr_in addr4;
>>> +
>>> +	addr4.sin_family = AF_INET;
>>> +	addr4.sin_port = htons(sock_port);
>>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>>> +	memset(&addr4.sin_zero, '\0', 8);
>>
>> Please don't mix declaration and code. Follow the
>> ./scripts/checkpatch.pl recommendations, except the "braces {} are not
>> necessary" for tests (not kernel) code because of ASSERT_* macros.
>>
>     Ok. Will be moved down the code.
>     Thank you.
>>
>>> +
>>> +	const struct rule rules[] = {
>>> +		{
>>> +			.path = dir_s1d2,
>>> +			.access = ACCESS_RO,
>>> +		},
>>> +		{},
>>> +	};
>>> +
>>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> +	};
>>> +	struct landlock_net_service_attr net_service = {
>>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>> +
>>> +		.port = htons(sock_port),
>>> +	};
>>> +
>>> +	/* Creates ruleset for network access. */
>>> +	const int ruleset_fd_net = landlock_create_ruleset(
>>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>>> +	ASSERT_LE(0, ruleset_fd_net);
>>> +
>>> +	/* Adds a network rule. */
>>> +	ASSERT_EQ(0,
>>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>>> +				    &net_service, 0));
>>> +
>>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>>> +
>>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>>> +	ASSERT_LE(0, ruleset_fd);
>>> +	enforce_ruleset(_metadata, ruleset_fd);
>>> +	ASSERT_EQ(0, close(ruleset_fd));
>>> +
>>> +	/* Tests on a directory with the network rule loaded. */
>>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>>> +
>>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>>> +	ASSERT_LE(0, sockfd);
>>> +	/* Binds a socket to port 15000. */
>>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>>> +
>>> +	/* Closes bounded socket. */
>>> +	ASSERT_EQ(0, close(sockfd));
>>> +}
>>> +
>>>    TEST_HARNESS_MAIN
>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>> new file mode 100644
>>> index 000000000000..b9543089a4d3
>>> --- /dev/null
>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>
>> [...]
>>
>>> +FIXTURE_TEARDOWN(socket){};
>>
>> Remove such trailing ";" and format with clang-format for both
>> FIXTURE_TEARDOWN().
> 
> Like this :
> 
> FIXTURE_TEARDOWN(socket)
> {
> }
> 
> ???

Yes, it may not looks nice but it pleases both checkpatch.pl and 
clang-format. :)

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

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



2/14/2023 3:07 PM, Mickaël Salaün пишет:
> 
> On 14/02/2023 09:51, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 2/10/2023 8:34 PM, Mickaël Salaün пишет:
>>> Hi Konstantin,
>>>
>>> I think this patch series is almost ready. Here is a first batch of
>>> review, I'll send more next week.
>>>
>>     Hi Mickaёl.
>>     thnaks for the review.
>> 
>>>
>>> I forgot to update the documentation. Can you please squash the
>>> following patch into this one?
>> 
>>     No problem. I will squash.
>>     Can I download this doc patch from your repo or I can use the diff below?
> 
> You can take the diff below.

   Ok. Will be done.
> 
>>>
>>>
>>> diff --git a/Documentation/userspace-api/landlock.rst
>>> b/Documentation/userspace-api/landlock.rst
>>> index 980558b879d6..fc2be89b423f 100644
>>> --- a/Documentation/userspace-api/landlock.rst
>>> +++ b/Documentation/userspace-api/landlock.rst
>>> @@ -416,9 +416,9 @@ Current limitations
>>>     Filesystem topology modification
>>>     --------------------------------
>>>
>>> -As for file renaming and linking, a sandboxed thread cannot modify its
>>> -filesystem topology, whether via :manpage:`mount(2)` or
>>> -:manpage:`pivot_root(2)`.  However, :manpage:`chroot(2)` calls are not
>>> denied.
>>> +Threads sandboxed with filesystem restrictions cannot modify filesystem
>>> +topology, whether via :manpage:`mount(2)` or :manpage:`pivot_root(2)`.
>>> +However, :manpage:`chroot(2)` calls are not denied.
>>>
>>>     Special filesystems
>>>     -------------------
>>>
>>>
>>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>>> From: Mickaël Salaün <mic@digikod.net>
>>>>
>>>> Allow mount point and root directory changes when there is no filesystem
>>>> rule tied to the current Landlock domain.  This doesn't change anything
>>>> for now because a domain must have at least a (filesystem) rule, but
>>>> this will change when other rule types will come.  For instance, a
>>>> domain only restricting the network should have no impact on filesystem
>>>> restrictions.
>>>>
>>>> Add a new get_current_fs_domain() helper to quickly check filesystem
>>>> rule existence for all filesystem LSM hooks.
>>>>
>>>> Remove unnecessary inlining.
>>>>
>>>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>>>> ---
>>>>
>>>> Changes since v8:
>>>> * Refactors get_handled_fs_accesses().
>>>> * Adds landlock_get_raw_fs_access_mask() helper.
>>>>
>>> .
> .

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

* Re: [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule
  2023-02-14 12:09       ` Mickaël Salaün
@ 2023-02-14 13:28         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-02-14 13:28 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/14/2023 3:09 PM, Mickaël Salaün пишет:
> 
> On 14/02/2023 11:15, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 2/10/2023 8:36 PM, Mickaël Salaün пишет:
>>>
>>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>>> Add a new landlock_key union and landlock_id structure to support
>>>> a socket port rule type. A struct landlock_id identifies a unique entry
>>>> in a ruleset: either a kernel object (e.g inode) or typed data (e.g TCP
>>>> port). There is one red-black tree per key type.
>>>>
>>>> This patch also adds is_object_pointer() and get_root() helpers.
>>>> is_object_pointer() returns true if key type is LANDLOCK_KEY_INODE.
>>>> get_root() helper returns a red_black tree root pointer according to
>>>> a key type.
>>>>
>>>> Refactor landlock_insert_rule() and landlock_find_rule() to support coming
>>>> network modifications. Adding or searching a rule in ruleset can now be
>>>> done thanks to a Landlock ID argument passed to these helpers.
>>>>
>>>> Remove unnecessary inlining.
>>>>
>>>
>>> You need to keep the Co-developed-by before the Signed-off-by for my entry.
>> 
>>     Got it.
>>>
>>>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v8:
>>>> * Refactors commit message.
>>>> * Removes inlining.
>>>> * Minor fixes.
>>>>
>>>> Changes since v7:
>>>> * Completes all the new field descriptions landlock_key,
>>>>     landlock_key_type, landlock_id.
>>>> * Refactors commit message, adds a co-developer.
>>>>
>>>> Changes since v6:
>>>> * Adds union landlock_key, enum landlock_key_type, and struct
>>>>     landlock_id.
>>>> * Refactors ruleset functions and improves switch/cases: create_rule(),
>>>>     insert_rule(), get_root(), is_object_pointer(), free_rule(),
>>>>     landlock_find_rule().
>>>> * Refactors landlock_append_fs_rule() functions to support new
>>>>     landlock_id type.
>>>>
>>>> Changes since v5:
>>>> * Formats code with clang-format-14.
>>>>
>>>> Changes since v4:
>>>> * Refactors insert_rule() and create_rule() functions by deleting
>>>> rule_type from their arguments list, it helps to reduce useless code.
>>>>
>>>> Changes since v3:
>>>> * Splits commit.
>>>> * Refactors landlock_insert_rule and landlock_find_rule functions.
>>>> * Rename new_ruleset->root_inode.
>>>>
>>>> ---
>>>>    security/landlock/fs.c      |  49 ++++++------
>>>>    security/landlock/ruleset.c | 148 +++++++++++++++++++++++++-----------
>>>>    security/landlock/ruleset.h |  65 +++++++++++++---
>>>>    3 files changed, 185 insertions(+), 77 deletions(-)
>>>>
>>>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>>>> index 0ae54a639e16..273ed8549da1 100644
>>>> --- a/security/landlock/fs.c
>>>> +++ b/security/landlock/fs.c
>>>
>>> [...]
>>>
>>>> @@ -191,12 +193,15 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>>>>     *
>>>>     * Returns NULL if no rule is found or if @dentry is negative.
>>>>     */
>>>> -static inline const struct landlock_rule *
>>>> +static const struct landlock_rule *
>>>
>>> Can you please create a (previous) dedicated patch for all the inlining
>>> changes?
>>>   
>>     a patch with just inlining changes?
> 
> Yes, a new patch with just the inlining changes extracted from this patch.

   Ok.
> .

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

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



2/14/2023 3:13 PM, Mickaël Salaün пишет:
> 
> On 14/02/2023 11:36, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 2/10/2023 8:40 PM, Mickaël Salaün пишет:
>>>
>>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>>> These test suites try to check edge cases for TCP sockets
>>>> bind() and connect() actions.
>>>>
>>>> socket:
>>>> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>>> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>>> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
>>>> for bind action with AF_UNSPEC socket family.
>>>> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
>>>> for connect action with AF_UNSPEC socket family.
>>>> * ruleset_overlap: Tests with overlapping rules for one port.
>>>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>>>> gradually added one by one, restricting sockets' connections.
>>>> * inval: Tests with invalid user space supplied data:
>>>>       - out of range ruleset attribute;
>>>>       - unhandled allowed access;
>>>>       - zero port value;
>>>>       - zero access value;
>>>>       - legitimate access values;
>>>> * bind_connect_inval_addrlen: Tests with invalid address length
>>>> for ipv4/ipv6 sockets.
>>>> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
>>>>
>>>> layout1:
>>>> * with_net: Tests with network bind() socket action within
>>>> filesystem directory access test.
>>>>
>>>> Test coverage for security/landlock is 94.1% of 946 lines according
>>>> to gcc/gcov-11.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v8:
>>>> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
>>>> * Refactors AF_UNSPEC tests.
>>>> * Adds address length checking tests.
>>>> * Convert ports in all tests to __be16.
>>>> * Adds invalid port values tests.
>>>> * Minor fixes.
>>>>
>>>> Changes since v7:
>>>> * Squashes all selftest commits.
>>>> * Adds fs test with network bind() socket action.
>>>> * Minor fixes.
>>>>
>>>> ---
>>>>    tools/testing/selftests/landlock/config     |    4 +
>>>>    tools/testing/selftests/landlock/fs_test.c  |   65 ++
>>>>    tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>>>>    3 files changed, 1226 insertions(+)
>>>>    create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>>
>>>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>>>> index 0f0a65287bac..71f7e9a8a64c 100644
>>>> --- a/tools/testing/selftests/landlock/config
>>>> +++ b/tools/testing/selftests/landlock/config
>>>> @@ -1,3 +1,7 @@
>>>> +CONFIG_INET=y
>>>> +CONFIG_IPV6=y
>>>> +CONFIG_NET=y
>>>> +CONFIG_NET_NS=y
>>>>    CONFIG_OVERLAY_FS=y
>>>>    CONFIG_SECURITY_LANDLOCK=y
>>>>    CONFIG_SECURITY_PATH=y
>>>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>>>> index b762b5419a89..5de4559c7fbb 100644
>>>> --- a/tools/testing/selftests/landlock/fs_test.c
>>>> +++ b/tools/testing/selftests/landlock/fs_test.c
>>>> @@ -8,8 +8,10 @@
>>>>     */
>>>>    
>>>>    #define _GNU_SOURCE
>>>> +#include <arpa/inet.h>
>>>>    #include <fcntl.h>
>>>>    #include <linux/landlock.h>
>>>> +#include <netinet/in.h>
>>>>    #include <sched.h>
>>>>    #include <stdio.h>
>>>>    #include <string.h>
>>>> @@ -17,6 +19,7 @@
>>>>    #include <sys/mount.h>
>>>>    #include <sys/prctl.h>
>>>>    #include <sys/sendfile.h>
>>>> +#include <sys/socket.h>
>>>>    #include <sys/stat.h>
>>>>    #include <sys/sysmacros.h>
>>>>    #include <unistd.h>
>>>> @@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>>>    	}
>>>>    }
>>>>    
>>>> +#define IP_ADDRESS "127.0.0.1"
>>>> +
>>>> +TEST_F_FORK(layout1, with_net)
>>>> +{
>>>> +	int sockfd;
>>>> +	int sock_port = 15000;
>>>> +	struct sockaddr_in addr4;
>>>> +
>>>> +	addr4.sin_family = AF_INET;
>>>> +	addr4.sin_port = htons(sock_port);
>>>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>>>> +	memset(&addr4.sin_zero, '\0', 8);
>>>
>>> Please don't mix declaration and code. Follow the
>>> ./scripts/checkpatch.pl recommendations, except the "braces {} are not
>>> necessary" for tests (not kernel) code because of ASSERT_* macros.
>>>
>>     Ok. Will be moved down the code.
>>     Thank you.
>>>
>>>> +
>>>> +	const struct rule rules[] = {
>>>> +		{
>>>> +			.path = dir_s1d2,
>>>> +			.access = ACCESS_RO,
>>>> +		},
>>>> +		{},
>>>> +	};
>>>> +
>>>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>>>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>> +	};
>>>> +	struct landlock_net_service_attr net_service = {
>>>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>> +
>>>> +		.port = htons(sock_port),
>>>> +	};
>>>> +
>>>> +	/* Creates ruleset for network access. */
>>>> +	const int ruleset_fd_net = landlock_create_ruleset(
>>>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>>>> +	ASSERT_LE(0, ruleset_fd_net);
>>>> +
>>>> +	/* Adds a network rule. */
>>>> +	ASSERT_EQ(0,
>>>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>>>> +				    &net_service, 0));
>>>> +
>>>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>>>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>>>> +
>>>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>>>> +	ASSERT_LE(0, ruleset_fd);
>>>> +	enforce_ruleset(_metadata, ruleset_fd);
>>>> +	ASSERT_EQ(0, close(ruleset_fd));
>>>> +
>>>> +	/* Tests on a directory with the network rule loaded. */
>>>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>>>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>>>> +
>>>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>>>> +	ASSERT_LE(0, sockfd);
>>>> +	/* Binds a socket to port 15000. */
>>>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>>>> +
>>>> +	/* Closes bounded socket. */
>>>> +	ASSERT_EQ(0, close(sockfd));
>>>> +}
>>>> +
>>>>    TEST_HARNESS_MAIN
>>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>>> new file mode 100644
>>>> index 000000000000..b9543089a4d3
>>>> --- /dev/null
>>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>>
>>> [...]
>>>
>>>> +FIXTURE_TEARDOWN(socket){};
>>>
>>> Remove such trailing ";" and format with clang-format for both
>>> FIXTURE_TEARDOWN().
>> 
>> Like this :
>> 
>> FIXTURE_TEARDOWN(socket)
>> {
>> }
>> 
>> ???
> 
> Yes, it may not looks nice but it pleases both checkpatch.pl and
> clang-format. :)

   Got it. Thanks.
> .

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-01-30 10:03         ` Konstantin Meskhidze (A)
@ 2023-02-21 16:16           ` Mickaël Salaün
  2023-03-06 13:43             ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-21 16:16 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 30/01/2023 11:03, Konstantin Meskhidze (A) wrote:
> 
> 
> 1/27/2023 9:22 PM, Mickaël Salaün пишет:
>>
>> On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 1/22/2023 2:07 AM, Günther Noack пишет:
>>
>> [...]
>>
>>>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>>>    ABI version.  In this example, this is not required because all of the requested
>>>>>    ``allowed_access`` rights are already available in ABI 1.
>>>>>    
>>>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>>>> -denying all other handled accesses for the filesystem.  The next step is to
>>>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>>>> -binary).
>>>>> +For network access-control, we can add a set of rules that allow to use a port
>>>>> +number for a specific action. All ports values must be defined in network byte
>>>>> +order.
>>>>
>>>> What is the point of asking user space to convert this to network byte
>>>> order? It seems to me that the kernel would be able to convert it to
>>>> network byte order very easily internally and in a single place -- why
>>>> ask all of the users to deal with that complexity? Am I overlooking
>>>> something?
>>>
>>>     I had a discussion about this issue with Mickaёl.
>>>     Please check these threads:
>>>     1.
>>> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>>>     2.
>>> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
>>
>> I'm definitely not sure if this is the right solution, or if there is
>> one. The rationale is to make it close to the current (POSIX) API. We
>> didn't get many opinion about that but I'd really like to have a
>> discussion about port endianness for this Landlock API.
> 
>     As for me, the kernel should take care about port converting. This
> work should be done under the hood.
> 
>     Any thoughts?
> 
>>
>> I looked at some code (e.g. see [1]) and it seems that using htons()
>> might make application patching more complex after all. What do you
>> think? Is there some network (syscall) API that don't use this convention?
>>
>> [1] https://github.com/landlock-lsm/tuto-lighttpd
>>
>>>>
>>>>> +
>>>>> +.. code-block:: c
>>>>> +
>>>>> +    struct landlock_net_service_attr net_service = {
>>>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>> +        .port = htons(8080),
>>>>> +    };
>>>>
>>>> This is a more high-level comment:
>>>>
>>>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>>>> how do you envision this struct to evolve if other protocols need to
>>>> be supported in the future?
>>>
>>>      When TCP restrictions land into Linux, we need to think about UDP
>>> support. Then other protocols will be on the road. Anyway you are right
>>> this struct will be evolving in long term, but I don't have a particular
>>> envision now. Thanks for the question - we need to think about it.
>>>>
>>>> Should this struct and the associated constants have "TCP" in its
>>>> name, and other protocols use a separate struct in the future?
>>
>> Other protocols such as AF_VSOCK uses a 32-bit port. We could use a
>> 32-bits port field or ever a 64-bit one. The later could make more sense
>> because each field would eventually be aligned on 64-bit. Picking a
>> 16-bit value was to help developers (and compilers/linters) with the
>> "correct" type (for TCP).

Thinking more about this, let's use a __u64 port (and remove the 
explicit packing). The landlock_append_net_rule() function should use a 
__u16 port argument, but the add_rule_net_service() function should 
check that there is no overflow with the port attribute (not higher than 
U16_MAX) before passing it to landlock_append_net_rule(). We should 
prioritize flexibility for the kernel UAPI over stricter types. User 
space libraries can improve this kind of types with a more complex API.

Big endian can make sense for a pure network API because the port value 
(and the IP address) is passed to other machines through the network, 
as-is. However, with Landlock, the port value is only used by the 
kernel. Moreover, in practice, port values are mostly converted when 
filling the sockaddr*_in structs. It would then make it more risky to 
ask developers another explicit htons() conversion for Landlock 
syscalls. Let's stick to the host endianess and let the kernel do the 
conversion.

Please include these rationales in code comments. We also need to update 
the tests for endianess, but still check big and little endian 
consistency as it is currently done in these tests. A new test should be 
added to check port boundaries with:
- port = 0
- port = U16_MAX
- port = U16_MAX + 1 (which should get an EINVAL)
- port = U16_MAX + 2 (to check u16 casting != 0)
- port = U32_MAX + 1
- port = U32_MAX + 2


>>
>> If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it
>> could make sense to have a dedicated attr struct specifying other
>> properties (e.g. CID). Anyway, the API is flexible but it would be nice
>> to not mess with it too much. What do you think?
>>
>>
>>>>
>>>>> +
>>>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>>> +                            &net_service, 0);
>>>>> +
>>>>> +The next step is to restrict the current thread from gaining more privileges
>>>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>>>             ^^^^^^
>>>>             "through" a SUID binary? "thanks to" sounds like it's desired
>>>>             to do that, while we're actually trying to prevent it here?
>>>
>>>      This is Mickaёl's part. Let's ask his opinion here.
>>>
>>>      Mickaёl, any thoughts?
>>
>> Yep, "through" looks better.
>> .

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

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-01-16  8:58 ` [PATCH v9 08/12] landlock: Add network rules and TCP hooks support Konstantin Meskhidze
  2023-02-10 17:39   ` Mickaël Salaün
@ 2023-02-21 18:04   ` Mickaël Salaün
  2023-03-06 10:18     ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-21 18:04 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> This commit adds network rules support in the ruleset management
> helpers and the landlock_create_ruleset syscall.
> Refactor user space API to support network actions. Add new network
> access flags, network rule and network attributes. Increment Landlock
> ABI version. Expand access_masks_t to u32 to be sure network access
> rights can be stored. Implement socket_bind() and socket_connect()
> LSM hooks, which enable to restrict TCP socket binding and connection
> to specific ports.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Squashes commits.
> * Refactors commit message.
> * Changes UAPI port field to __be16.
> * Changes logic of bind/connect hooks with AF_UNSPEC families.
> * Adds address length checking.
> * Minor fixes.
> 
> Changes since v7:
> * Squashes commits.
> * Increments ABI version to 4.
> * Refactors commit message.
> * Minor fixes.
> 
> Changes since v6:
> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>    because it OR values.
> * Makes landlock_add_net_access_mask() more resilient incorrect values.
> * Refactors landlock_get_net_access_mask().
> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>    LANDLOCK_NUM_ACCESS_FS as value.
> * Updates access_masks_t to u32 to support network access actions.
> * Refactors landlock internal functions to support network actions with
>    landlock_key/key_type/id types.
> 
> Changes since v5:
> * Gets rid of partial revert from landlock_add_rule
> syscall.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors landlock_create_ruleset() - splits ruleset and
> masks checks.
> * Refactors landlock_create_ruleset() and landlock mask
> setters/getters to support two rule types.
> * Refactors landlock_add_rule syscall add_rule_path_beneath
> function by factoring out get_ruleset_from_fd() and
> landlock_put_ruleset().
> 
> Changes since v3:
> * Splits commit.
> * Adds network rule support for internal landlock functions.
> * Adds set_mask and get_mask for network.
> * Adds rb_root root_net_port.
> 
> ---
>   include/uapi/linux/landlock.h                |  49 +++++
>   security/landlock/Kconfig                    |   1 +
>   security/landlock/Makefile                   |   2 +
>   security/landlock/limits.h                   |   6 +-
>   security/landlock/net.c                      | 200 +++++++++++++++++++
>   security/landlock/net.h                      |  26 +++
>   security/landlock/ruleset.c                  |  52 ++++-
>   security/landlock/ruleset.h                  |  63 +++++-
>   security/landlock/setup.c                    |   2 +
>   security/landlock/syscalls.c                 |  72 ++++++-
>   tools/testing/selftests/landlock/base_test.c |   2 +-
>   11 files changed, 452 insertions(+), 23 deletions(-)
>   create mode 100644 security/landlock/net.c
>   create mode 100644 security/landlock/net.h
> 
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index f3223f964691..ae11c663c975 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.
> +	 */
> +	__be16 port;
> +
> +} __attribute__((packed));
> +
>   /**
>    * DOC: fs_access
>    *
> @@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
>   #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
>   /* clang-format on */
>   
> +/**
> + * DOC: net_access
> + *
> + * Network flags
> + * ~~~~~~~~~~~~~~~~
> + *
> + * These flags enable to restrict a sandboxed process to a set of network
> + * actions.
> + *
> + * TCP sockets with allowed actions:
> + *
> + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
> + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
> + *   a remote port.
> + */
> +/* clang-format off */
> +#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
> +#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
> +/* clang-format on */
>   #endif /* _UAPI_LINUX_LANDLOCK_H */
> diff --git a/security/landlock/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/limits.h b/security/landlock/limits.h
> index bafb3b8dc677..8a1a6463c64e 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -23,6 +23,10 @@
>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>   #define LANDLOCK_SHIFT_ACCESS_FS	0
>   
> -/* clang-format on */
> +#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
> +#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
> +#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
> +#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
>   
> +/* clang-format on */
>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> new file mode 100644
> index 000000000000..338bd6dd8e3f
> --- /dev/null
> +++ b/security/landlock/net.c
> @@ -0,0 +1,200 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Landlock LSM - Network management and hooks
> + *
> + * Copyright © 2022 Huawei Tech. Co., Ltd.
> + * Copyright © 2022 Microsoft Corporation
> + */
> +
> +#include <linux/in.h>
> +#include <linux/net.h>
> +#include <linux/socket.h>
> +#include <net/ipv6.h>
> +
> +#include "common.h"
> +#include "cred.h"
> +#include "limits.h"
> +#include "net.h"
> +#include "ruleset.h"
> +
> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> +			     const __be16 port, access_mask_t access_rights)
> +{
> +	int err;
> +	const struct landlock_id id = {
> +		.key.data = port,
> +		.type = LANDLOCK_KEY_NET_PORT,
> +	};
> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
> +
> +	/* Transforms relative access rights to absolute ones. */
> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
> +			 ~landlock_get_net_access_mask(ruleset, 0);
> +
> +	mutex_lock(&ruleset->lock);
> +	err = landlock_insert_rule(ruleset, id, access_rights);
> +	mutex_unlock(&ruleset->lock);
> +
> +	return err;
> +}
> +
> +static int check_addrlen(const struct sockaddr *const address, int addrlen)
> +{
> +	if (addrlen < offsetofend(struct sockaddr, sa_family))
> +		return -EINVAL;
> +	switch (address->sa_family) {
> +	case AF_UNSPEC:
> +	case AF_INET:
> +		if (addrlen < sizeof(struct sockaddr_in))
> +			return -EINVAL;
> +		return 0;
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6:
> +		if (addrlen < SIN6_LEN_RFC2133)
> +			return -EINVAL;
> +		return 0;
> +#endif
> +	}
> +	WARN_ON_ONCE(1);
> +	return 0;
> +}
> +
> +static int check_socket_access(const struct landlock_ruleset *const domain,
> +			       struct sockaddr *address, __be16 port,
> +			       access_mask_t access_request)
> +{
> +	bool allowed = false;
> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
> +	const struct landlock_rule *rule;
> +	access_mask_t handled_access;
> +	const struct landlock_id id = {
> +		.key.data = port,
> +		.type = LANDLOCK_KEY_NET_PORT,
> +	};
> +
> +	if (WARN_ON_ONCE(!domain))
> +		return 0;
> +	if (WARN_ON_ONCE(domain->num_layers < 1))
> +		return -EACCES;
> +
> +	switch (address->sa_family) {
> +	case AF_UNSPEC:
> +		/*
> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
> +		 * association, which have the same effect as closing the
> +		 * connection while retaining the socket object (i.e., the file
> +		 * descriptor).  As for dropping privileges, closing
> +		 * connections is always allowed.
> +		 */
> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
> +			return 0;
> +
> +		/*
> +		 * For compatibility reason, accept AF_UNSPEC for bind
> +		 * accesses (mapped to AF_INET) only if the address is
> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
> +		 * required to not wrongfully return -EACCES instead of
> +		 * -EAFNOSUPPORT.
> +		 */
> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
> +			const struct sockaddr_in *const sockaddr =
> +				(struct sockaddr_in *)address;
> +
> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
> +				return -EAFNOSUPPORT;
> +		}
> +
> +		fallthrough;
> +	case AF_INET:
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6:
> +#endif
> +		rule = landlock_find_rule(domain, id);
> +		handled_access = landlock_init_layer_masks(
> +			domain, access_request, &layer_masks,
> +			LANDLOCK_KEY_NET_PORT);
> +		allowed = landlock_unmask_layers(rule, handled_access,
> +						 &layer_masks,
> +						 ARRAY_SIZE(layer_masks));
> +
> +		fallthrough;
> +	}
> +	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 sockaddr->sin_port;
> +	}
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case AF_INET6: {
> +		const struct sockaddr_in6 *const sockaddr_ip6 =
> +			(struct sockaddr_in6 *)address;
> +		return sockaddr_ip6->sin6_port;
> +	}
> +#endif
> +	}
> +	WARN_ON_ONCE(1);
> +	return 0;
> +}
> +
> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
> +			    int addrlen)
> +{
> +	int ret;
> +	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;
> +
> +	ret = check_addrlen(address, addrlen);
> +	if (ret)
> +		return ret;
> +
> +	return check_socket_access(dom, address, get_port(address),
> +				   LANDLOCK_ACCESS_NET_BIND_TCP);

Both hook_socket_bind() and hook_socket_connect() looks the same except 
the access right. All this code could be moved to check_socket_access(). 
get_port() would need to be defined before.


> +}
> +
> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
> +			       int addrlen)
> +{
> +	int ret;
> +	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;
> +
> +	ret = check_addrlen(address, addrlen);
> +	if (ret)
> +		return ret;
> +
> +	return check_socket_access(dom, address, get_port(address),
> +				   LANDLOCK_ACCESS_NET_CONNECT_TCP);
> +}

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

* Re: [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-01-16  8:58 ` [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
  2023-02-10 17:40   ` Mickaël Salaün
@ 2023-02-21 18:05   ` Mickaël Salaün
  2023-03-06 12:03     ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-21 18:05 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> These test suites try to check edge cases for TCP sockets
> bind() and connect() actions.
> 
> socket:
> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
> for bind action with AF_UNSPEC socket family.
> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
> for connect action with AF_UNSPEC socket family.
> * ruleset_overlap: Tests with overlapping rules for one port.
> * ruleset_expanding: Tests with expanding rulesets in which rules are
> gradually added one by one, restricting sockets' connections.
> * inval: Tests with invalid user space supplied data:
>      - out of range ruleset attribute;
>      - unhandled allowed access;
>      - zero port value;
>      - zero access value;
>      - legitimate access values;
> * bind_connect_inval_addrlen: Tests with invalid address length
> for ipv4/ipv6 sockets.
> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
> 
> layout1:
> * with_net: Tests with network bind() socket action within
> filesystem directory access test.
> 
> Test coverage for security/landlock is 94.1% of 946 lines according
> to gcc/gcov-11.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
> * Refactors AF_UNSPEC tests.
> * Adds address length checking tests.
> * Convert ports in all tests to __be16.
> * Adds invalid port values tests.
> * Minor fixes.
> 
> Changes since v7:
> * Squashes all selftest commits.
> * Adds fs test with network bind() socket action.
> * Minor fixes.
> 
> ---
>   tools/testing/selftests/landlock/config     |    4 +
>   tools/testing/selftests/landlock/fs_test.c  |   65 ++
>   tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>   3 files changed, 1226 insertions(+)
>   create mode 100644 tools/testing/selftests/landlock/net_test.c
> 
> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
> index 0f0a65287bac..71f7e9a8a64c 100644
> --- a/tools/testing/selftests/landlock/config
> +++ b/tools/testing/selftests/landlock/config
> @@ -1,3 +1,7 @@
> +CONFIG_INET=y
> +CONFIG_IPV6=y
> +CONFIG_NET=y
> +CONFIG_NET_NS=y
>   CONFIG_OVERLAY_FS=y
>   CONFIG_SECURITY_LANDLOCK=y
>   CONFIG_SECURITY_PATH=y
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index b762b5419a89..5de4559c7fbb 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -8,8 +8,10 @@
>    */
>   
>   #define _GNU_SOURCE
> +#include <arpa/inet.h>
>   #include <fcntl.h>
>   #include <linux/landlock.h>
> +#include <netinet/in.h>
>   #include <sched.h>
>   #include <stdio.h>
>   #include <string.h>
> @@ -17,6 +19,7 @@
>   #include <sys/mount.h>
>   #include <sys/prctl.h>
>   #include <sys/sendfile.h>
> +#include <sys/socket.h>
>   #include <sys/stat.h>
>   #include <sys/sysmacros.h>
>   #include <unistd.h>
> @@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>   	}
>   }
>   
> +#define IP_ADDRESS "127.0.0.1"
> +
> +TEST_F_FORK(layout1, with_net)
> +{
> +	int sockfd;
> +	int sock_port = 15000;
> +	struct sockaddr_in addr4;
> +
> +	addr4.sin_family = AF_INET;
> +	addr4.sin_port = htons(sock_port);
> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
> +	memset(&addr4.sin_zero, '\0', 8);
> +
> +	const struct rule rules[] = {
> +		{
> +			.path = dir_s1d2,
> +			.access = ACCESS_RO,
> +		},
> +		{},
> +	};
> +
> +	struct landlock_ruleset_attr ruleset_attr_net = {
> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +	};
> +	struct landlock_net_service_attr net_service = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> +
> +		.port = htons(sock_port),
> +	};
> +
> +	/* Creates ruleset for network access. */
> +	const int ruleset_fd_net = landlock_create_ruleset(
> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
> +	ASSERT_LE(0, ruleset_fd_net);
> +
> +	/* Adds a network rule. */
> +	ASSERT_EQ(0,
> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
> +				    &net_service, 0));
> +
> +	enforce_ruleset(_metadata, ruleset_fd_net);
> +	ASSERT_EQ(0, close(ruleset_fd_net));
> +
> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
> +	ASSERT_LE(0, ruleset_fd);
> +	enforce_ruleset(_metadata, ruleset_fd);
> +	ASSERT_EQ(0, close(ruleset_fd));
> +
> +	/* Tests on a directory with the network rule loaded. */
> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
> +
> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port 15000. */
> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
> +
> +	/* Closes bounded socket. */
> +	ASSERT_EQ(0, close(sockfd));
> +}
> +
>   TEST_HARNESS_MAIN
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> new file mode 100644
> index 000000000000..b9543089a4d3
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -0,0 +1,1157 @@
> +// 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 <linux/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_IPv4 "127.0.0.1"

Please use a capital "V".

> +#define IP_ADDRESS_IPv6 "::1"

ditto


> +#define SOCK_PORT 15000
> +
> +/* Number pending connections queue to be hold. */
> +#define BACKLOG 10
> +
> +const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
> +
> +/* Invalid attribute, out of landlock network access range. */
> +#define LANDLOCK_INVAL_ATTR 7
> +
> +FIXTURE(socket)
> +{
> +	uint port[MAX_SOCKET_NUM];
> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
> +};
> +
> +/* struct _fixture_variant_socket */
> +FIXTURE_VARIANT(socket)
> +{
> +	const bool is_ipv4;
> +	const bool is_sandboxed;
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket, ipv4) {
> +	/* clang-format on */
> +	.is_ipv4 = true,
> +	.is_sandboxed = false,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket, ipv4_sandboxed) {
> +	/* clang-format on */
> +	.is_ipv4 = true,
> +	.is_sandboxed = true,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket, ipv6) {
> +	/* clang-format on */
> +	.is_ipv4 = false,
> +	.is_sandboxed = false,
> +};
> +
> +/* clang-format off */
> +FIXTURE_VARIANT_ADD(socket, ipv6_sandboxed) {
> +	/* clang-format on */
> +	.is_ipv4 = false,
> +	.is_sandboxed = true,
> +};
> +
> +static int
> +create_socket_variant(const struct _fixture_variant_socket *const variant,

If all "struct _fixture_variant_socket" can be replaced with 
"FIXTURE_VARIANT(socket)" while keeping clang-format and checkpatch.pl 
happy, please do it. It seems that some clang-format issues have been 
fixed. Same for _test_data and FIXTURE_DATA. Please remove the outdated 
comments about these structs (see socket_standalone, and socket variant 
definitions).


> +		      const int type)
> +{
> +	if (variant->is_ipv4)
> +		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
> +	else
> +		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
> +}
> +
> +static int bind_variant(const struct _fixture_variant_socket *const variant,
> +			const int sockfd,
> +			const struct _test_data_socket *const self,
> +			const size_t index, const bool zero_size)
> +

Extra new line.

> +{
> +	if (variant->is_ipv4)
> +		return bind(sockfd, &self->addr4[index],
> +			    (zero_size ? 0 : sizeof(self->addr4[index])));

Is the zero_size really useful? Do calling bind and connect with this 
argument reaches the Landlock code (check_addrlen) or is it caught by 
the network code beforehand?


> +	else
> +		return bind(sockfd, &self->addr6[index],
> +			    (zero_size ? 0 : sizeof(self->addr6[index])));
> +}
> +
> +static int connect_variant(const struct _fixture_variant_socket *const variant,
> +			   const int sockfd,
> +			   const struct _test_data_socket *const self,
> +			   const size_t index, const bool zero_size)
> +{
> +	if (variant->is_ipv4)
> +		return connect(sockfd, &self->addr4[index],
> +			       (zero_size ? 0 : sizeof(self->addr4[index])));
> +	else
> +		return connect(sockfd, &self->addr6[index],
> +			       (zero_size ? 0 : sizeof(self->addr6[index])));
> +}


[...]

> +
> +TEST_F_FORK(socket, bind)
> +{
> +	int sockfd;
> +
> +	struct landlock_ruleset_attr ruleset_attr = {
> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +	};
> +	struct landlock_net_service_attr net_service_1 = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> +				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +		.port = htons(self->port[0]),
> +	};
> +	struct landlock_net_service_attr net_service_2 = {
> +		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> +		.port = htons(self->port[1]),
> +	};
> +	struct landlock_net_service_attr net_service_3 = {
> +		.allowed_access = 0,
> +		.port = htons(self->port[2]),
> +	};
> +	int ruleset_fd, ret;
> +
> +	if (variant->is_sandboxed) {
> +		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> +						     sizeof(ruleset_attr), 0);
> +		ASSERT_LE(0, ruleset_fd);
> +
> +		/*
> +		 * Allows connect and bind operations to the port[0]
> +		 * socket.
> +		 */
> +		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
> +					       LANDLOCK_RULE_NET_SERVICE,
> +					       &net_service_1, 0));
> +		/*
> +		 * Allows connect and deny bind operations to the port[1]
> +		 * socket.
> +		 */
> +		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
> +					       LANDLOCK_RULE_NET_SERVICE,
> +					       &net_service_2, 0));
> +		/*
> +		 * Empty allowed_access (i.e. deny rules) are ignored in
> +		 * network actions for port[2] socket.
> +		 */
> +		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
> +						LANDLOCK_RULE_NET_SERVICE,
> +						&net_service_3, 0));
> +		ASSERT_EQ(ENOMSG, errno);
> +
> +		/* Enforces the ruleset. */
> +		enforce_ruleset(_metadata, ruleset_fd);
> +	}
> +
> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
> +	ASSERT_LE(0, sockfd);
> +	/* Binds a socket to port[0]. */
> +	ret = bind_variant(variant, sockfd, self, 0, false);
> +	if (variant->is_sandboxed) {
> +		ASSERT_EQ(0, ret);
> +	} else {
> +		ASSERT_EQ(0, ret);
> +	
The condition is useless here. Same on multiple other locations.


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

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

* Re: [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks()
  2023-01-16  8:58 ` [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks() Konstantin Meskhidze
  2023-02-10 17:38   ` Mickaël Salaün
@ 2023-02-21 18:07   ` Mickaël Salaün
  2023-03-06  7:52     ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-02-21 18:07 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

It's not "_unmask_layers() and _init_layer_masks()": there is no "_" 
prefixes.
Using "landlock_unmask_layers()" in the subject would be too long, so 
you can replace it with "landlock: Refactor layer helpers".
For consistency, you can change the previous patch's subject to 
"landlock: Move and rename layer helpers"

Anyway, please send a new patch series. Most of the kernel code should 
be good and I could then push it to -next for testing while reviewing 
the last parts.


On 16/01/2023 09:58, Konstantin Meskhidze wrote:
> Add new key_type argument to the landlock_init_layer_masks() helper.
> Add a masks_array_size argument to the landlock_unmask_layers() helper.
> These modifications support implementing new rule types in the next
> Landlock versions.
> 
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v8:
> * None.
> 
> Changes since v7:
> * Refactors commit message, adds a co-developer.
> * Minor fixes.
> 
> Changes since v6:
> * Removes masks_size attribute from init_layer_masks().
> * Refactors init_layer_masks() with new landlock_key_type.
> 
> Changes since v5:
> * Splits commit.
> * Formats code with clang-format-14.
> 
> Changes since v4:
> * Refactors init_layer_masks(), get_handled_accesses()
> and unmask_layers() functions to support multiple rule types.
> * Refactors landlock_get_fs_access_mask() function with
> LANDLOCK_MASK_ACCESS_FS mask.
> 
> Changes since v3:
> * Splits commit.
> * Refactors landlock_unmask_layers functions.
> 
> ---
>   security/landlock/fs.c      | 43 ++++++++++++++++--------------
>   security/landlock/ruleset.c | 52 ++++++++++++++++++++++++++-----------
>   security/landlock/ruleset.h | 17 ++++++------
>   3 files changed, 70 insertions(+), 42 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 73a7399f93ba..a73dbd3f9ddb 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -441,20 +441,22 @@ static bool is_access_to_paths_allowed(
>   	}
>   
>   	if (unlikely(dentry_child1)) {
> -		landlock_unmask_layers(find_rule(domain, dentry_child1),
> -				       landlock_init_layer_masks(
> -					       domain, LANDLOCK_MASK_ACCESS_FS,
> -					       &_layer_masks_child1),
> -				       &_layer_masks_child1);
> +		landlock_unmask_layers(
> +			find_rule(domain, dentry_child1),
> +			landlock_init_layer_masks(
> +				domain, LANDLOCK_MASK_ACCESS_FS,
> +				&_layer_masks_child1, LANDLOCK_KEY_INODE),
> +			&_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
>   		layer_masks_child1 = &_layer_masks_child1;
>   		child1_is_directory = d_is_dir(dentry_child1);
>   	}
>   	if (unlikely(dentry_child2)) {
> -		landlock_unmask_layers(find_rule(domain, dentry_child2),
> -				       landlock_init_layer_masks(
> -					       domain, LANDLOCK_MASK_ACCESS_FS,
> -					       &_layer_masks_child2),
> -				       &_layer_masks_child2);
> +		landlock_unmask_layers(
> +			find_rule(domain, dentry_child2),
> +			landlock_init_layer_masks(
> +				domain, LANDLOCK_MASK_ACCESS_FS,
> +				&_layer_masks_child2, LANDLOCK_KEY_INODE),
> +			&_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
>   		layer_masks_child2 = &_layer_masks_child2;
>   		child2_is_directory = d_is_dir(dentry_child2);
>   	}
> @@ -507,14 +509,15 @@ static bool is_access_to_paths_allowed(
>   
>   		rule = find_rule(domain, walker_path.dentry);
>   		allowed_parent1 = landlock_unmask_layers(
> -			rule, access_masked_parent1, layer_masks_parent1);
> +			rule, access_masked_parent1, layer_masks_parent1,
> +			ARRAY_SIZE(*layer_masks_parent1));
>   		allowed_parent2 = landlock_unmask_layers(
> -			rule, access_masked_parent2, layer_masks_parent2);
> +			rule, access_masked_parent2, layer_masks_parent2,
> +			ARRAY_SIZE(*layer_masks_parent2));
>   
>   		/* Stops when a rule from each layer grants access. */
>   		if (allowed_parent1 && allowed_parent2)
>   			break;
> -
>   jump_up:
>   		if (walker_path.dentry == walker_path.mnt->mnt_root) {
>   			if (follow_up(&walker_path)) {
> @@ -553,8 +556,8 @@ static int check_access_path(const struct landlock_ruleset *const domain,
>   {
>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>   
> -	access_request =
> -		landlock_init_layer_masks(domain, access_request, &layer_masks);
> +	access_request = landlock_init_layer_masks(
> +		domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
>   	if (is_access_to_paths_allowed(domain, path, access_request,
>   				       &layer_masks, NULL, 0, NULL, NULL))
>   		return 0;
> @@ -640,7 +643,8 @@ static bool collect_domain_accesses(
>   		return true;
>   
>   	access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> -					       layer_masks_dom);
> +					       layer_masks_dom,
> +					       LANDLOCK_KEY_INODE);
>   
>   	dget(dir);
>   	while (true) {
> @@ -648,7 +652,8 @@ static bool collect_domain_accesses(
>   
>   		/* Gets all layers allowing all domain accesses. */
>   		if (landlock_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.
> @@ -763,7 +768,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>   		 */
>   		access_request_parent1 = landlock_init_layer_masks(
>   			dom, access_request_parent1 | access_request_parent2,
> -			&layer_masks_parent1);
> +			&layer_masks_parent1, LANDLOCK_KEY_INODE);
>   		if (is_access_to_paths_allowed(
>   			    dom, new_dir, access_request_parent1,
>   			    &layer_masks_parent1, NULL, 0, NULL, NULL))
> @@ -1139,7 +1144,7 @@ static int hook_file_open(struct file *const file)
>   	if (is_access_to_paths_allowed(
>   		    dom, &file->f_path,
>   		    landlock_init_layer_masks(dom, full_access_request,
> -					      &layer_masks),
> +					      &layer_masks, LANDLOCK_KEY_INODE),
>   		    &layer_masks, NULL, 0, NULL, NULL)) {
>   		allowed_access = full_access_request;
>   	} else {
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 22590cac3d56..9748b54b42fe 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -576,14 +576,15 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>   /*
>    * @layer_masks is read and may be updated according to the access request and
>    * the matching rule.
> + * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
>    *
>    * Returns true if the request is allowed (i.e. relevant layer masks for the
>    * request are empty).
>    */
> -bool landlock_unmask_layers(
> -	const struct landlock_rule *const rule,
> -	const access_mask_t access_request,
> -	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
> +bool landlock_unmask_layers(const struct landlock_rule *const rule,
> +			    const access_mask_t access_request,
> +			    layer_mask_t (*const layer_masks)[],
> +			    const size_t masks_array_size)
>   {
>   	size_t layer_level;
>   
> @@ -615,8 +616,7 @@ bool landlock_unmask_layers(
>   		 * requested access.
>   		 */
>   		is_empty = true;
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> +		for_each_set_bit(access_bit, &access_req, masks_array_size) {
>   			if (layer->access & BIT_ULL(access_bit))
>   				(*layer_masks)[access_bit] &= ~layer_bit;
>   			is_empty = is_empty && !(*layer_masks)[access_bit];
> @@ -627,6 +627,10 @@ bool landlock_unmask_layers(
>   	return false;
>   }
>   
> +typedef access_mask_t
> +get_access_mask_t(const struct landlock_ruleset *const ruleset,
> +		  const u16 layer_level);
> +
>   /*
>    * init_layer_masks - Initialize layer masks from an access request
>    *
> @@ -636,19 +640,34 @@ bool landlock_unmask_layers(
>    * @domain: The domain that defines the current restrictions.
>    * @access_request: The requested access rights to check.
>    * @layer_masks: The layer masks to populate.
> + * @key_type: The key type to switch between access masks of different types.
>    *
>    * Returns: An access mask where each access right bit is set which is handled
>    * in any of the active layers in @domain.
>    */
> -access_mask_t landlock_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
> +landlock_init_layer_masks(const struct landlock_ruleset *const domain,
> +			  const access_mask_t access_request,
> +			  layer_mask_t (*const layer_masks)[],
> +			  const enum landlock_key_type key_type)
>   {
>   	access_mask_t handled_accesses = 0;
> -	size_t layer_level;
> +	size_t layer_level, num_access;
> +	get_access_mask_t *get_access_mask;
> +
> +	switch (key_type) {
> +	case LANDLOCK_KEY_INODE:
> +		get_access_mask = landlock_get_fs_access_mask;
> +		num_access = LANDLOCK_NUM_ACCESS_FS;
> +		break;
> +	default:
> +		WARN_ON_ONCE(1);
> +		return 0;
> +	}
> +
> +	memset(layer_masks, 0,
> +	       array_size(sizeof((*layer_masks)[0]), num_access));
>   
> -	memset(layer_masks, 0, sizeof(*layer_masks));
>   	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
>   	if (!access_request)
>   		return 0;
> @@ -658,10 +677,13 @@ access_mask_t landlock_init_layer_masks(
>   		const unsigned long access_req = access_request;
>   		unsigned long access_bit;
>   
> -		for_each_set_bit(access_bit, &access_req,
> -				 ARRAY_SIZE(*layer_masks)) {
> +		for_each_set_bit(access_bit, &access_req, num_access) {
> +			/*
> +			 * Artificially handles all initially denied by default
> +			 * access rights.
> +			 */
>   			if (BIT_ULL(access_bit) &
> -			    landlock_get_fs_access_mask(domain, layer_level)) {
> +			    get_access_mask(domain, layer_level)) {
>   				(*layer_masks)[access_bit] |=
>   					BIT_ULL(layer_level);
>   				handled_accesses |= BIT_ULL(access_bit);
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 60a3c4d4d961..77349764e111 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -266,14 +266,15 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>   	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
>   	       ACCESS_FS_INITIALLY_DENIED;
>   }
> -bool landlock_unmask_layers(
> -	const struct landlock_rule *const rule,
> -	const access_mask_t access_request,
> -	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
> +bool landlock_unmask_layers(const struct landlock_rule *const rule,
> +			    const access_mask_t access_request,
> +			    layer_mask_t (*const layer_masks)[],
> +			    const size_t masks_array_size);
>   
> -access_mask_t landlock_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
> +landlock_init_layer_masks(const struct landlock_ruleset *const domain,
> +			  const access_mask_t access_request,
> +			  layer_mask_t (*const layer_masks)[],
> +			  const enum landlock_key_type key_type);
>   
>   #endif /* _SECURITY_LANDLOCK_RULESET_H */

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

* Re: [PATCH v9 00/12] Network support for Landlock
  2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
                   ` (11 preceding siblings ...)
  2023-01-16  8:58 ` [PATCH v9 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
@ 2023-02-23 22:17 ` Günther Noack
  2023-03-06  7:45   ` Konstantin Meskhidze (A)
  2023-03-13 17:16   ` Konstantin Meskhidze (A)
  12 siblings, 2 replies; 74+ messages in thread
From: Günther Noack @ 2023-02-23 22:17 UTC (permalink / raw)
  To: Konstantin Meskhidze
  Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin

Hello Konstantin!

Sorry for asking such fundamental questions again so late in the review.

After playing with patch V9 with the Go-Landlock library, I'm still
having trouble understanding these questions -- they probably have
good answers, but I also did not see them explained in the
documentation. Maybe it would help to clarify it there?

* What is the use case for permitting processes to connect to a given
  TCP port, but leaving unspecified what the IP address is?

  Example: If a Landlock ruleset permits connecting to TCP port 53,
  that makes it possible to talk to any IP address on the internet (at
  least if the process runs on a normal Linux desktop machine), and we
  can't really control whether that is the system's proper (TCP-)DNS
  server or whether that is an attacker-controlled service for
  accepting leaked secrets from the process...?

  Is the plan that IP address support should be added in a follow-up
  patch?  Will it become part of the landlock_net_service_attr struct?

* Given the list of obscure network protocols listed in the socket(2)
  man page, I find it slightly weird to have rules for the use of TCP,
  but to leave less prominent protocols unrestricted.

  For example, a process with an enabled Landlock network ruleset may
  connect only to certain TCP ports, but at the same time it can
  happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?

  I'm mentioning these more obscure protocols, because I doubt that
  Landlock will grow more sophisticated support for them anytime soon,
  so maybe the best option would be to just make it possible to
  disable these?  Is that also part of the plan?

  (I think there would be a lot of value in restricting network
  access, even when it's done very broadly.  There are many programs
  that don't need network at all, and among those that do need
  network, most only require IP networking.

  Btw, the argument for more broad disabling of network access was
  already made at https://cr.yp.to/unix/disablenetwork.html in the
  past.)

* This one is more of an implementation question: I don't understand
  why we are storing the networking rules in the same RB tree as the
  file system rules. - It looks a bit like "YAGNI" to me...?

  Would it be more efficient to keep the file system rules in the
  existing RB tree, and store the networking rules *separately* next
  to it in a different RB tree, or even in a more optimized data
  structure? In pseudocode:

    struct fast_lookup_int_set bind_tcp_ports;
    struct fast_lookup_int_set connect_tcp_ports;
    struct landlock_rb_tree fs_rules;

  It seems that there should be a data structure that supports this
  well and which uses the fact that we only need to store small
  integers?

Thanks,
–Günther

P.S.: Apologies if some of it was discussed previously. I did my best
to catch up on previous threads, but it's long, and it's possible that
I missed parts of the discussion.

On Mon, Jan 16, 2023 at 04:58:06PM +0800, Konstantin Meskhidze wrote:
> Hi,
> This is a new V9 patch related to Landlock LSM network confinement.
> It is based on the landlock's -next branch on top of v6.2-rc3 kernel version:
> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next
> 
> It brings refactoring of previous patch version V8.
> Mostly there are fixes of logic and typos, adding new tests.
> 
> All test were run in QEMU evironment and compiled with
>  -static flag.
>  1. network_test: 32/32 tests passed.
>  2. base_test: 7/7 tests passed.
>  3. fs_test: 78/78 tests passed.
>  4. ptrace_test: 8/8 tests passed.
> 
> Previous versions:
> v8: https://lore.kernel.org/linux-security-module/20221021152644.155136-1-konstantin.meskhidze@huawei.com/
> v7: https://lore.kernel.org/linux-security-module/20220829170401.834298-1-konstantin.meskhidze@huawei.com/
> v6: https://lore.kernel.org/linux-security-module/20220621082313.3330667-1-konstantin.meskhidze@huawei.com/
> v5: https://lore.kernel.org/linux-security-module/20220516152038.39594-1-konstantin.meskhidze@huawei.com
> v4: https://lore.kernel.org/linux-security-module/20220309134459.6448-1-konstantin.meskhidze@huawei.com/
> v3: https://lore.kernel.org/linux-security-module/20220124080215.265538-1-konstantin.meskhidze@huawei.com/
> v2: https://lore.kernel.org/linux-security-module/20211228115212.703084-1-konstantin.meskhidze@huawei.com/
> v1: https://lore.kernel.org/linux-security-module/20211210072123.386713-1-konstantin.meskhidze@huawei.com/
> 
> Konstantin Meskhidze (11):
>   landlock: Make ruleset's access masks more generic
>   landlock: Refactor landlock_find_rule/insert_rule
>   landlock: Refactor merge/inherit_ruleset functions
>   landlock: Move and rename umask_layers() and init_layer_masks()
>   landlock: Refactor _unmask_layers() and _init_layer_masks()
>   landlock: Refactor landlock_add_rule() syscall
>   landlock: Add network rules and TCP hooks support
>   selftests/landlock: Share enforce_ruleset()
>   selftests/landlock: Add 10 new test suites dedicated to network
>   samples/landlock: Add network demo
>   landlock: Document Landlock's network support
> 
> Mickaël Salaün (1):
>   landlock: Allow filesystem layout changes for domains without such
>     rule type
> 
>  Documentation/userspace-api/landlock.rst     |   72 +-
>  include/uapi/linux/landlock.h                |   49 +
>  samples/landlock/sandboxer.c                 |  131 +-
>  security/landlock/Kconfig                    |    1 +
>  security/landlock/Makefile                   |    2 +
>  security/landlock/fs.c                       |  255 ++--
>  security/landlock/limits.h                   |    7 +-
>  security/landlock/net.c                      |  200 +++
>  security/landlock/net.h                      |   26 +
>  security/landlock/ruleset.c                  |  409 +++++--
>  security/landlock/ruleset.h                  |  185 ++-
>  security/landlock/setup.c                    |    2 +
>  security/landlock/syscalls.c                 |  165 ++-
>  tools/testing/selftests/landlock/base_test.c |    2 +-
>  tools/testing/selftests/landlock/common.h    |   10 +
>  tools/testing/selftests/landlock/config      |    4 +
>  tools/testing/selftests/landlock/fs_test.c   |   75 +-
>  tools/testing/selftests/landlock/net_test.c  | 1157 ++++++++++++++++++
>  18 files changed, 2398 insertions(+), 354 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] 74+ messages in thread

* Re: [PATCH v9 00/12] Network support for Landlock
  2023-02-23 22:17 ` [PATCH v9 00/12] Network support for Landlock Günther Noack
@ 2023-03-06  7:45   ` Konstantin Meskhidze (A)
  2023-03-13 17:16   ` Konstantin Meskhidze (A)
  1 sibling, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06  7:45 UTC (permalink / raw)
  To: Günther Noack
  Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/24/2023 1:17 AM, Günther Noack пишет:
> Hello Konstantin!

   Hi, Günther.
   Sorry for late reply. I was in short vacation.
   Thanks for the questions. I will try to give you answers ASAP.
> 
> Sorry for asking such fundamental questions again so late in the review.
> 
> After playing with patch V9 with the Go-Landlock library, I'm still
> having trouble understanding these questions -- they probably have
> good answers, but I also did not see them explained in the
> documentation. Maybe it would help to clarify it there?
> 
> * What is the use case for permitting processes to connect to a given
>    TCP port, but leaving unspecified what the IP address is?
> 
>    Example: If a Landlock ruleset permits connecting to TCP port 53,
>    that makes it possible to talk to any IP address on the internet (at
>    least if the process runs on a normal Linux desktop machine), and we
>    can't really control whether that is the system's proper (TCP-)DNS
>    server or whether that is an attacker-controlled service for
>    accepting leaked secrets from the process...?
> 
>    Is the plan that IP address support should be added in a follow-up
>    patch?  Will it become part of the landlock_net_service_attr struct?
> 
> * Given the list of obscure network protocols listed in the socket(2)
>    man page, I find it slightly weird to have rules for the use of TCP,
>    but to leave less prominent protocols unrestricted.
> 
>    For example, a process with an enabled Landlock network ruleset may
>    connect only to certain TCP ports, but at the same time it can
>    happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?
> 
>    I'm mentioning these more obscure protocols, because I doubt that
>    Landlock will grow more sophisticated support for them anytime soon,
>    so maybe the best option would be to just make it possible to
>    disable these?  Is that also part of the plan?
> 
>    (I think there would be a lot of value in restricting network
>    access, even when it's done very broadly.  There are many programs
>    that don't need network at all, and among those that do need
>    network, most only require IP networking.
> 
>    Btw, the argument for more broad disabling of network access was
>    already made at https://cr.yp.to/unix/disablenetwork.html in the
>    past.)
> 
> * This one is more of an implementation question: I don't understand
>    why we are storing the networking rules in the same RB tree as the
>    file system rules. - It looks a bit like "YAGNI" to me...?
> 
>    Would it be more efficient to keep the file system rules in the
>    existing RB tree, and store the networking rules *separately* next
>    to it in a different RB tree, or even in a more optimized data
>    structure? In pseudocode:
> 
>      struct fast_lookup_int_set bind_tcp_ports;
>      struct fast_lookup_int_set connect_tcp_ports;
>      struct landlock_rb_tree fs_rules;
> 
>    It seems that there should be a data structure that supports this
>    well and which uses the fact that we only need to store small
>    integers?
> 
> Thanks,
> –Günther
> 
> P.S.: Apologies if some of it was discussed previously. I did my best
> to catch up on previous threads, but it's long, and it's possible that
> I missed parts of the discussion.
> 
> On Mon, Jan 16, 2023 at 04:58:06PM +0800, Konstantin Meskhidze wrote:
>> Hi,
>> This is a new V9 patch related to Landlock LSM network confinement.
>> It is based on the landlock's -next branch on top of v6.2-rc3 kernel version:
>> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next
>> 
>> It brings refactoring of previous patch version V8.
>> Mostly there are fixes of logic and typos, adding new tests.
>> 
>> All test were run in QEMU evironment and compiled with
>>  -static flag.
>>  1. network_test: 32/32 tests passed.
>>  2. base_test: 7/7 tests passed.
>>  3. fs_test: 78/78 tests passed.
>>  4. ptrace_test: 8/8 tests passed.
>> 
>> Previous versions:
>> v8: https://lore.kernel.org/linux-security-module/20221021152644.155136-1-konstantin.meskhidze@huawei.com/
>> v7: https://lore.kernel.org/linux-security-module/20220829170401.834298-1-konstantin.meskhidze@huawei.com/
>> v6: https://lore.kernel.org/linux-security-module/20220621082313.3330667-1-konstantin.meskhidze@huawei.com/
>> v5: https://lore.kernel.org/linux-security-module/20220516152038.39594-1-konstantin.meskhidze@huawei.com
>> v4: https://lore.kernel.org/linux-security-module/20220309134459.6448-1-konstantin.meskhidze@huawei.com/
>> v3: https://lore.kernel.org/linux-security-module/20220124080215.265538-1-konstantin.meskhidze@huawei.com/
>> v2: https://lore.kernel.org/linux-security-module/20211228115212.703084-1-konstantin.meskhidze@huawei.com/
>> v1: https://lore.kernel.org/linux-security-module/20211210072123.386713-1-konstantin.meskhidze@huawei.com/
>> 
>> Konstantin Meskhidze (11):
>>   landlock: Make ruleset's access masks more generic
>>   landlock: Refactor landlock_find_rule/insert_rule
>>   landlock: Refactor merge/inherit_ruleset functions
>>   landlock: Move and rename umask_layers() and init_layer_masks()
>>   landlock: Refactor _unmask_layers() and _init_layer_masks()
>>   landlock: Refactor landlock_add_rule() syscall
>>   landlock: Add network rules and TCP hooks support
>>   selftests/landlock: Share enforce_ruleset()
>>   selftests/landlock: Add 10 new test suites dedicated to network
>>   samples/landlock: Add network demo
>>   landlock: Document Landlock's network support
>> 
>> Mickaël Salaün (1):
>>   landlock: Allow filesystem layout changes for domains without such
>>     rule type
>> 
>>  Documentation/userspace-api/landlock.rst     |   72 +-
>>  include/uapi/linux/landlock.h                |   49 +
>>  samples/landlock/sandboxer.c                 |  131 +-
>>  security/landlock/Kconfig                    |    1 +
>>  security/landlock/Makefile                   |    2 +
>>  security/landlock/fs.c                       |  255 ++--
>>  security/landlock/limits.h                   |    7 +-
>>  security/landlock/net.c                      |  200 +++
>>  security/landlock/net.h                      |   26 +
>>  security/landlock/ruleset.c                  |  409 +++++--
>>  security/landlock/ruleset.h                  |  185 ++-
>>  security/landlock/setup.c                    |    2 +
>>  security/landlock/syscalls.c                 |  165 ++-
>>  tools/testing/selftests/landlock/base_test.c |    2 +-
>>  tools/testing/selftests/landlock/common.h    |   10 +
>>  tools/testing/selftests/landlock/config      |    4 +
>>  tools/testing/selftests/landlock/fs_test.c   |   75 +-
>>  tools/testing/selftests/landlock/net_test.c  | 1157 ++++++++++++++++++
>>  18 files changed, 2398 insertions(+), 354 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] 74+ messages in thread

* Re: [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks()
  2023-02-21 18:07   ` Mickaël Salaün
@ 2023-03-06  7:52     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06  7:52 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/21/2023 9:07 PM, Mickaël Salaün пишет:
> It's not "_unmask_layers() and _init_layer_masks()": there is no "_"
> prefixes.
> Using "landlock_unmask_layers()" in the subject would be too long, so
> you can replace it with "landlock: Refactor layer helpers".
> For consistency, you can change the previous patch's subject to
> "landlock: Move and rename layer helpers"
> 
> Anyway, please send a new patch series. Most of the kernel code should
> be good and I could then push it to -next for testing while reviewing
> the last parts.

   Hi, Mickaël. I was on a short vacation. Sorry for late reply.
   I will reply your review and send a new patch ASAP.

   Regards,
   Konstantin

> 
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> Add new key_type argument to the landlock_init_layer_masks() helper.
>> Add a masks_array_size argument to the landlock_unmask_layers() helper.
>> These modifications support implementing new rule types in the next
>> Landlock versions.
>> 
>> Signed-off-by: Mickaël Salaün <mic@digikod.net>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * None.
>> 
>> Changes since v7:
>> * Refactors commit message, adds a co-developer.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Removes masks_size attribute from init_layer_masks().
>> * Refactors init_layer_masks() with new landlock_key_type.
>> 
>> Changes since v5:
>> * Splits commit.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors init_layer_masks(), get_handled_accesses()
>> and unmask_layers() functions to support multiple rule types.
>> * Refactors landlock_get_fs_access_mask() function with
>> LANDLOCK_MASK_ACCESS_FS mask.
>> 
>> Changes since v3:
>> * Splits commit.
>> * Refactors landlock_unmask_layers functions.
>> 
>> ---
>>   security/landlock/fs.c      | 43 ++++++++++++++++--------------
>>   security/landlock/ruleset.c | 52 ++++++++++++++++++++++++++-----------
>>   security/landlock/ruleset.h | 17 ++++++------
>>   3 files changed, 70 insertions(+), 42 deletions(-)
>> 
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 73a7399f93ba..a73dbd3f9ddb 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -441,20 +441,22 @@ static bool is_access_to_paths_allowed(
>>   	}
>>   
>>   	if (unlikely(dentry_child1)) {
>> -		landlock_unmask_layers(find_rule(domain, dentry_child1),
>> -				       landlock_init_layer_masks(
>> -					       domain, LANDLOCK_MASK_ACCESS_FS,
>> -					       &_layer_masks_child1),
>> -				       &_layer_masks_child1);
>> +		landlock_unmask_layers(
>> +			find_rule(domain, dentry_child1),
>> +			landlock_init_layer_masks(
>> +				domain, LANDLOCK_MASK_ACCESS_FS,
>> +				&_layer_masks_child1, LANDLOCK_KEY_INODE),
>> +			&_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
>>   		layer_masks_child1 = &_layer_masks_child1;
>>   		child1_is_directory = d_is_dir(dentry_child1);
>>   	}
>>   	if (unlikely(dentry_child2)) {
>> -		landlock_unmask_layers(find_rule(domain, dentry_child2),
>> -				       landlock_init_layer_masks(
>> -					       domain, LANDLOCK_MASK_ACCESS_FS,
>> -					       &_layer_masks_child2),
>> -				       &_layer_masks_child2);
>> +		landlock_unmask_layers(
>> +			find_rule(domain, dentry_child2),
>> +			landlock_init_layer_masks(
>> +				domain, LANDLOCK_MASK_ACCESS_FS,
>> +				&_layer_masks_child2, LANDLOCK_KEY_INODE),
>> +			&_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
>>   		layer_masks_child2 = &_layer_masks_child2;
>>   		child2_is_directory = d_is_dir(dentry_child2);
>>   	}
>> @@ -507,14 +509,15 @@ static bool is_access_to_paths_allowed(
>>   
>>   		rule = find_rule(domain, walker_path.dentry);
>>   		allowed_parent1 = landlock_unmask_layers(
>> -			rule, access_masked_parent1, layer_masks_parent1);
>> +			rule, access_masked_parent1, layer_masks_parent1,
>> +			ARRAY_SIZE(*layer_masks_parent1));
>>   		allowed_parent2 = landlock_unmask_layers(
>> -			rule, access_masked_parent2, layer_masks_parent2);
>> +			rule, access_masked_parent2, layer_masks_parent2,
>> +			ARRAY_SIZE(*layer_masks_parent2));
>>   
>>   		/* Stops when a rule from each layer grants access. */
>>   		if (allowed_parent1 && allowed_parent2)
>>   			break;
>> -
>>   jump_up:
>>   		if (walker_path.dentry == walker_path.mnt->mnt_root) {
>>   			if (follow_up(&walker_path)) {
>> @@ -553,8 +556,8 @@ static int check_access_path(const struct landlock_ruleset *const domain,
>>   {
>>   	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
>>   
>> -	access_request =
>> -		landlock_init_layer_masks(domain, access_request, &layer_masks);
>> +	access_request = landlock_init_layer_masks(
>> +		domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
>>   	if (is_access_to_paths_allowed(domain, path, access_request,
>>   				       &layer_masks, NULL, 0, NULL, NULL))
>>   		return 0;
>> @@ -640,7 +643,8 @@ static bool collect_domain_accesses(
>>   		return true;
>>   
>>   	access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>> -					       layer_masks_dom);
>> +					       layer_masks_dom,
>> +					       LANDLOCK_KEY_INODE);
>>   
>>   	dget(dir);
>>   	while (true) {
>> @@ -648,7 +652,8 @@ static bool collect_domain_accesses(
>>   
>>   		/* Gets all layers allowing all domain accesses. */
>>   		if (landlock_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.
>> @@ -763,7 +768,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
>>   		 */
>>   		access_request_parent1 = landlock_init_layer_masks(
>>   			dom, access_request_parent1 | access_request_parent2,
>> -			&layer_masks_parent1);
>> +			&layer_masks_parent1, LANDLOCK_KEY_INODE);
>>   		if (is_access_to_paths_allowed(
>>   			    dom, new_dir, access_request_parent1,
>>   			    &layer_masks_parent1, NULL, 0, NULL, NULL))
>> @@ -1139,7 +1144,7 @@ static int hook_file_open(struct file *const file)
>>   	if (is_access_to_paths_allowed(
>>   		    dom, &file->f_path,
>>   		    landlock_init_layer_masks(dom, full_access_request,
>> -					      &layer_masks),
>> +					      &layer_masks, LANDLOCK_KEY_INODE),
>>   		    &layer_masks, NULL, 0, NULL, NULL)) {
>>   		allowed_access = full_access_request;
>>   	} else {
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 22590cac3d56..9748b54b42fe 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -576,14 +576,15 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
>>   /*
>>    * @layer_masks is read and may be updated according to the access request and
>>    * the matching rule.
>> + * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
>>    *
>>    * Returns true if the request is allowed (i.e. relevant layer masks for the
>>    * request are empty).
>>    */
>> -bool landlock_unmask_layers(
>> -	const struct landlock_rule *const rule,
>> -	const access_mask_t access_request,
>> -	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
>> +bool landlock_unmask_layers(const struct landlock_rule *const rule,
>> +			    const access_mask_t access_request,
>> +			    layer_mask_t (*const layer_masks)[],
>> +			    const size_t masks_array_size)
>>   {
>>   	size_t layer_level;
>>   
>> @@ -615,8 +616,7 @@ bool landlock_unmask_layers(
>>   		 * requested access.
>>   		 */
>>   		is_empty = true;
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> +		for_each_set_bit(access_bit, &access_req, masks_array_size) {
>>   			if (layer->access & BIT_ULL(access_bit))
>>   				(*layer_masks)[access_bit] &= ~layer_bit;
>>   			is_empty = is_empty && !(*layer_masks)[access_bit];
>> @@ -627,6 +627,10 @@ bool landlock_unmask_layers(
>>   	return false;
>>   }
>>   
>> +typedef access_mask_t
>> +get_access_mask_t(const struct landlock_ruleset *const ruleset,
>> +		  const u16 layer_level);
>> +
>>   /*
>>    * init_layer_masks - Initialize layer masks from an access request
>>    *
>> @@ -636,19 +640,34 @@ bool landlock_unmask_layers(
>>    * @domain: The domain that defines the current restrictions.
>>    * @access_request: The requested access rights to check.
>>    * @layer_masks: The layer masks to populate.
>> + * @key_type: The key type to switch between access masks of different types.
>>    *
>>    * Returns: An access mask where each access right bit is set which is handled
>>    * in any of the active layers in @domain.
>>    */
>> -access_mask_t landlock_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
>> +landlock_init_layer_masks(const struct landlock_ruleset *const domain,
>> +			  const access_mask_t access_request,
>> +			  layer_mask_t (*const layer_masks)[],
>> +			  const enum landlock_key_type key_type)
>>   {
>>   	access_mask_t handled_accesses = 0;
>> -	size_t layer_level;
>> +	size_t layer_level, num_access;
>> +	get_access_mask_t *get_access_mask;
>> +
>> +	switch (key_type) {
>> +	case LANDLOCK_KEY_INODE:
>> +		get_access_mask = landlock_get_fs_access_mask;
>> +		num_access = LANDLOCK_NUM_ACCESS_FS;
>> +		break;
>> +	default:
>> +		WARN_ON_ONCE(1);
>> +		return 0;
>> +	}
>> +
>> +	memset(layer_masks, 0,
>> +	       array_size(sizeof((*layer_masks)[0]), num_access));
>>   
>> -	memset(layer_masks, 0, sizeof(*layer_masks));
>>   	/* An empty access request can happen because of O_WRONLY | O_RDWR. */
>>   	if (!access_request)
>>   		return 0;
>> @@ -658,10 +677,13 @@ access_mask_t landlock_init_layer_masks(
>>   		const unsigned long access_req = access_request;
>>   		unsigned long access_bit;
>>   
>> -		for_each_set_bit(access_bit, &access_req,
>> -				 ARRAY_SIZE(*layer_masks)) {
>> +		for_each_set_bit(access_bit, &access_req, num_access) {
>> +			/*
>> +			 * Artificially handles all initially denied by default
>> +			 * access rights.
>> +			 */
>>   			if (BIT_ULL(access_bit) &
>> -			    landlock_get_fs_access_mask(domain, layer_level)) {
>> +			    get_access_mask(domain, layer_level)) {
>>   				(*layer_masks)[access_bit] |=
>>   					BIT_ULL(layer_level);
>>   				handled_accesses |= BIT_ULL(access_bit);
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index 60a3c4d4d961..77349764e111 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -266,14 +266,15 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
>>   	return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
>>   	       ACCESS_FS_INITIALLY_DENIED;
>>   }
>> -bool landlock_unmask_layers(
>> -	const struct landlock_rule *const rule,
>> -	const access_mask_t access_request,
>> -	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]);
>> +bool landlock_unmask_layers(const struct landlock_rule *const rule,
>> +			    const access_mask_t access_request,
>> +			    layer_mask_t (*const layer_masks)[],
>> +			    const size_t masks_array_size);
>>   
>> -access_mask_t landlock_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
>> +landlock_init_layer_masks(const struct landlock_ruleset *const domain,
>> +			  const access_mask_t access_request,
>> +			  layer_mask_t (*const layer_masks)[],
>> +			  const enum landlock_key_type key_type);
>>   
>>   #endif /* _SECURITY_LANDLOCK_RULESET_H */
> .

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

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-02-21 18:04   ` Mickaël Salaün
@ 2023-03-06 10:18     ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06 10:18 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/21/2023 9:04 PM, Mickaël Salaün пишет:
> 
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> This commit adds network rules support in the ruleset management
>> helpers and the landlock_create_ruleset syscall.
>> Refactor user space API to support network actions. Add new network
>> access flags, network rule and network attributes. Increment Landlock
>> ABI version. Expand access_masks_t to u32 to be sure network access
>> rights can be stored. Implement socket_bind() and socket_connect()
>> LSM hooks, which enable to restrict TCP socket binding and connection
>> to specific ports.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Squashes commits.
>> * Refactors commit message.
>> * Changes UAPI port field to __be16.
>> * Changes logic of bind/connect hooks with AF_UNSPEC families.
>> * Adds address length checking.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * Squashes commits.
>> * Increments ABI version to 4.
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>    because it OR values.
>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_net_access_mask().
>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>    LANDLOCK_NUM_ACCESS_FS as value.
>> * Updates access_masks_t to u32 to support network access actions.
>> * Refactors landlock internal functions to support network actions with
>>    landlock_key/key_type/id types.
>> 
>> Changes since v5:
>> * Gets rid of partial revert from landlock_add_rule
>> syscall.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactors landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds network rule support for internal landlock functions.
>> * Adds set_mask and get_mask for network.
>> * Adds rb_root root_net_port.
>> 
>> ---
>>   include/uapi/linux/landlock.h                |  49 +++++
>>   security/landlock/Kconfig                    |   1 +
>>   security/landlock/Makefile                   |   2 +
>>   security/landlock/limits.h                   |   6 +-
>>   security/landlock/net.c                      | 200 +++++++++++++++++++
>>   security/landlock/net.h                      |  26 +++
>>   security/landlock/ruleset.c                  |  52 ++++-
>>   security/landlock/ruleset.h                  |  63 +++++-
>>   security/landlock/setup.c                    |   2 +
>>   security/landlock/syscalls.c                 |  72 ++++++-
>>   tools/testing/selftests/landlock/base_test.c |   2 +-
>>   11 files changed, 452 insertions(+), 23 deletions(-)
>>   create mode 100644 security/landlock/net.c
>>   create mode 100644 security/landlock/net.h
>> 
>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>> index f3223f964691..ae11c663c975 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.
>> +	 */
>> +	__be16 port;
>> +
>> +} __attribute__((packed));
>> +
>>   /**
>>    * DOC: fs_access
>>    *
>> @@ -173,4 +203,23 @@ struct landlock_path_beneath_attr {
>>   #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
>>   /* clang-format on */
>>   
>> +/**
>> + * DOC: net_access
>> + *
>> + * Network flags
>> + * ~~~~~~~~~~~~~~~~
>> + *
>> + * These flags enable to restrict a sandboxed process to a set of network
>> + * actions.
>> + *
>> + * TCP sockets with allowed actions:
>> + *
>> + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
>> + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
>> + *   a remote port.
>> + */
>> +/* clang-format off */
>> +#define LANDLOCK_ACCESS_NET_BIND_TCP			(1ULL << 0)
>> +#define LANDLOCK_ACCESS_NET_CONNECT_TCP			(1ULL << 1)
>> +/* clang-format on */
>>   #endif /* _UAPI_LINUX_LANDLOCK_H */
>> diff --git a/security/landlock/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/limits.h b/security/landlock/limits.h
>> index bafb3b8dc677..8a1a6463c64e 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -23,6 +23,10 @@
>>   #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>   #define LANDLOCK_SHIFT_ACCESS_FS	0
>>   
>> -/* clang-format on */
>> +#define LANDLOCK_LAST_ACCESS_NET	LANDLOCK_ACCESS_NET_CONNECT_TCP
>> +#define LANDLOCK_MASK_ACCESS_NET	((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
>> +#define LANDLOCK_NUM_ACCESS_NET		__const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>> +#define LANDLOCK_SHIFT_ACCESS_NET	LANDLOCK_NUM_ACCESS_FS
>>   
>> +/* clang-format on */
>>   #endif /* _SECURITY_LANDLOCK_LIMITS_H */
>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>> new file mode 100644
>> index 000000000000..338bd6dd8e3f
>> --- /dev/null
>> +++ b/security/landlock/net.c
>> @@ -0,0 +1,200 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Landlock LSM - Network management and hooks
>> + *
>> + * Copyright © 2022 Huawei Tech. Co., Ltd.
>> + * Copyright © 2022 Microsoft Corporation
>> + */
>> +
>> +#include <linux/in.h>
>> +#include <linux/net.h>
>> +#include <linux/socket.h>
>> +#include <net/ipv6.h>
>> +
>> +#include "common.h"
>> +#include "cred.h"
>> +#include "limits.h"
>> +#include "net.h"
>> +#include "ruleset.h"
>> +
>> +int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>> +			     const __be16 port, access_mask_t access_rights)
>> +{
>> +	int err;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +	BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>> +
>> +	/* Transforms relative access rights to absolute ones. */
>> +	access_rights |= LANDLOCK_MASK_ACCESS_NET &
>> +			 ~landlock_get_net_access_mask(ruleset, 0);
>> +
>> +	mutex_lock(&ruleset->lock);
>> +	err = landlock_insert_rule(ruleset, id, access_rights);
>> +	mutex_unlock(&ruleset->lock);
>> +
>> +	return err;
>> +}
>> +
>> +static int check_addrlen(const struct sockaddr *const address, int addrlen)
>> +{
>> +	if (addrlen < offsetofend(struct sockaddr, sa_family))
>> +		return -EINVAL;
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +	case AF_INET:
>> +		if (addrlen < sizeof(struct sockaddr_in))
>> +			return -EINVAL;
>> +		return 0;
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +		if (addrlen < SIN6_LEN_RFC2133)
>> +			return -EINVAL;
>> +		return 0;
>> +#endif
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>> +			       struct sockaddr *address, __be16 port,
>> +			       access_mask_t access_request)
>> +{
>> +	bool allowed = false;
>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>> +	const struct landlock_rule *rule;
>> +	access_mask_t handled_access;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +
>> +	if (WARN_ON_ONCE(!domain))
>> +		return 0;
>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>> +		return -EACCES;
>> +
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +		/*
>> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
>> +		 * association, which have the same effect as closing the
>> +		 * connection while retaining the socket object (i.e., the file
>> +		 * descriptor).  As for dropping privileges, closing
>> +		 * connections is always allowed.
>> +		 */
>> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> +			return 0;
>> +
>> +		/*
>> +		 * For compatibility reason, accept AF_UNSPEC for bind
>> +		 * accesses (mapped to AF_INET) only if the address is
>> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
>> +		 * required to not wrongfully return -EACCES instead of
>> +		 * -EAFNOSUPPORT.
>> +		 */
>> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
>> +			const struct sockaddr_in *const sockaddr =
>> +				(struct sockaddr_in *)address;
>> +
>> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
>> +				return -EAFNOSUPPORT;
>> +		}
>> +
>> +		fallthrough;
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		rule = landlock_find_rule(domain, id);
>> +		handled_access = landlock_init_layer_masks(
>> +			domain, access_request, &layer_masks,
>> +			LANDLOCK_KEY_NET_PORT);
>> +		allowed = landlock_unmask_layers(rule, handled_access,
>> +						 &layer_masks,
>> +						 ARRAY_SIZE(layer_masks));
>> +
>> +		fallthrough;
>> +	}
>> +	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 sockaddr->sin_port;
>> +	}
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6: {
>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>> +			(struct sockaddr_in6 *)address;
>> +		return sockaddr_ip6->sin6_port;
>> +	}
>> +#endif
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>> +			    int addrlen)
>> +{
>> +	int ret;
>> +	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;
>> +
>> +	ret = check_addrlen(address, addrlen);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return check_socket_access(dom, address, get_port(address),
>> +				   LANDLOCK_ACCESS_NET_BIND_TCP);
> 
> Both hook_socket_bind() and hook_socket_connect() looks the same except
> the access right. All this code could be moved to check_socket_access().
> get_port() would need to be defined before.
> 
   Ok. I got it.
> 
>> +}
>> +
>> +static int hook_socket_connect(struct socket *sock, struct sockaddr *address,
>> +			       int addrlen)
>> +{
>> +	int ret;
>> +	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;
>> +
>> +	ret = check_addrlen(address, addrlen);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return check_socket_access(dom, address, get_port(address),
>> +				   LANDLOCK_ACCESS_NET_CONNECT_TCP);
>> +}
> .

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

* Re: [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-02-21 18:05   ` Mickaël Salaün
@ 2023-03-06 12:03     ` Konstantin Meskhidze (A)
  2023-03-06 16:00       ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06 12:03 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/21/2023 9:05 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> These test suites try to check edge cases for TCP sockets
>> bind() and connect() actions.
>> 
>> socket:
>> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
>> for bind action with AF_UNSPEC socket family.
>> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
>> for connect action with AF_UNSPEC socket family.
>> * ruleset_overlap: Tests with overlapping rules for one port.
>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>> gradually added one by one, restricting sockets' connections.
>> * inval: Tests with invalid user space supplied data:
>>      - out of range ruleset attribute;
>>      - unhandled allowed access;
>>      - zero port value;
>>      - zero access value;
>>      - legitimate access values;
>> * bind_connect_inval_addrlen: Tests with invalid address length
>> for ipv4/ipv6 sockets.
>> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
>> 
>> layout1:
>> * with_net: Tests with network bind() socket action within
>> filesystem directory access test.
>> 
>> Test coverage for security/landlock is 94.1% of 946 lines according
>> to gcc/gcov-11.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
>> * Refactors AF_UNSPEC tests.
>> * Adds address length checking tests.
>> * Convert ports in all tests to __be16.
>> * Adds invalid port values tests.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * Squashes all selftest commits.
>> * Adds fs test with network bind() socket action.
>> * Minor fixes.
>> 
>> ---
>>   tools/testing/selftests/landlock/config     |    4 +
>>   tools/testing/selftests/landlock/fs_test.c  |   65 ++
>>   tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>>   3 files changed, 1226 insertions(+)
>>   create mode 100644 tools/testing/selftests/landlock/net_test.c
>> 
>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>> index 0f0a65287bac..71f7e9a8a64c 100644
>> --- a/tools/testing/selftests/landlock/config
>> +++ b/tools/testing/selftests/landlock/config
>> @@ -1,3 +1,7 @@
>> +CONFIG_INET=y
>> +CONFIG_IPV6=y
>> +CONFIG_NET=y
>> +CONFIG_NET_NS=y
>>   CONFIG_OVERLAY_FS=y
>>   CONFIG_SECURITY_LANDLOCK=y
>>   CONFIG_SECURITY_PATH=y
>> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
>> index b762b5419a89..5de4559c7fbb 100644
>> --- a/tools/testing/selftests/landlock/fs_test.c
>> +++ b/tools/testing/selftests/landlock/fs_test.c
>> @@ -8,8 +8,10 @@
>>    */
>>   
>>   #define _GNU_SOURCE
>> +#include <arpa/inet.h>
>>   #include <fcntl.h>
>>   #include <linux/landlock.h>
>> +#include <netinet/in.h>
>>   #include <sched.h>
>>   #include <stdio.h>
>>   #include <string.h>
>> @@ -17,6 +19,7 @@
>>   #include <sys/mount.h>
>>   #include <sys/prctl.h>
>>   #include <sys/sendfile.h>
>> +#include <sys/socket.h>
>>   #include <sys/stat.h>
>>   #include <sys/sysmacros.h>
>>   #include <unistd.h>
>> @@ -4413,4 +4416,66 @@ TEST_F_FORK(layout2_overlay, same_content_different_file)
>>   	}
>>   }
>>   
>> +#define IP_ADDRESS "127.0.0.1"
>> +
>> +TEST_F_FORK(layout1, with_net)
>> +{
>> +	int sockfd;
>> +	int sock_port = 15000;
>> +	struct sockaddr_in addr4;
>> +
>> +	addr4.sin_family = AF_INET;
>> +	addr4.sin_port = htons(sock_port);
>> +	addr4.sin_addr.s_addr = inet_addr(IP_ADDRESS);
>> +	memset(&addr4.sin_zero, '\0', 8);
>> +
>> +	const struct rule rules[] = {
>> +		{
>> +			.path = dir_s1d2,
>> +			.access = ACCESS_RO,
>> +		},
>> +		{},
>> +	};
>> +
>> +	struct landlock_ruleset_attr ruleset_attr_net = {
>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +	};
>> +	struct landlock_net_service_attr net_service = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> +
>> +		.port = htons(sock_port),
>> +	};
>> +
>> +	/* Creates ruleset for network access. */
>> +	const int ruleset_fd_net = landlock_create_ruleset(
>> +		&ruleset_attr_net, sizeof(ruleset_attr_net), 0);
>> +	ASSERT_LE(0, ruleset_fd_net);
>> +
>> +	/* Adds a network rule. */
>> +	ASSERT_EQ(0,
>> +		  landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE,
>> +				    &net_service, 0));
>> +
>> +	enforce_ruleset(_metadata, ruleset_fd_net);
>> +	ASSERT_EQ(0, close(ruleset_fd_net));
>> +
>> +	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
>> +	ASSERT_LE(0, ruleset_fd);
>> +	enforce_ruleset(_metadata, ruleset_fd);
>> +	ASSERT_EQ(0, close(ruleset_fd));
>> +
>> +	/* Tests on a directory with the network rule loaded. */
>> +	ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY));
>> +	ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY));
>> +
>> +	sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port 15000. */
>> +	ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4)));
>> +
>> +	/* Closes bounded socket. */
>> +	ASSERT_EQ(0, close(sockfd));
>> +}
>> +
>>   TEST_HARNESS_MAIN
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> new file mode 100644
>> index 000000000000..b9543089a4d3
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -0,0 +1,1157 @@
>> +// 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 <linux/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_IPv4 "127.0.0.1"
> 
> Please use a capital "V".

   Ok. Got it.
> 
>> +#define IP_ADDRESS_IPv6 "::1"
> 
> ditto

   Ok.
> 
> 
>> +#define SOCK_PORT 15000
>> +
>> +/* Number pending connections queue to be hold. */
>> +#define BACKLOG 10
>> +
>> +const struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC };
>> +
>> +/* Invalid attribute, out of landlock network access range. */
>> +#define LANDLOCK_INVAL_ATTR 7
>> +
>> +FIXTURE(socket)
>> +{
>> +	uint port[MAX_SOCKET_NUM];
>> +	struct sockaddr_in addr4[MAX_SOCKET_NUM];
>> +	struct sockaddr_in6 addr6[MAX_SOCKET_NUM];
>> +};
>> +
>> +/* struct _fixture_variant_socket */
>> +FIXTURE_VARIANT(socket)
>> +{
>> +	const bool is_ipv4;
>> +	const bool is_sandboxed;
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(socket, ipv4) {
>> +	/* clang-format on */
>> +	.is_ipv4 = true,
>> +	.is_sandboxed = false,
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(socket, ipv4_sandboxed) {
>> +	/* clang-format on */
>> +	.is_ipv4 = true,
>> +	.is_sandboxed = true,
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(socket, ipv6) {
>> +	/* clang-format on */
>> +	.is_ipv4 = false,
>> +	.is_sandboxed = false,
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(socket, ipv6_sandboxed) {
>> +	/* clang-format on */
>> +	.is_ipv4 = false,
>> +	.is_sandboxed = true,
>> +};
>> +
>> +static int
>> +create_socket_variant(const struct _fixture_variant_socket *const variant,
> 
> If all "struct _fixture_variant_socket" can be replaced with
> "FIXTURE_VARIANT(socket)" while keeping clang-format and checkpatch.pl
> happy, please do it. It seems that some clang-format issues have been
> fixed. Same for _test_data and FIXTURE_DATA. Please remove the outdated
> comments about these structs (see socket_standalone, and socket variant
> definitions).
> 
   Ok. Thanks.
> 
>> +		      const int type)
>> +{
>> +	if (variant->is_ipv4)
>> +		return socket(AF_INET, type | SOCK_CLOEXEC, 0);
>> +	else
>> +		return socket(AF_INET6, type | SOCK_CLOEXEC, 0);
>> +}
>> +
>> +static int bind_variant(const struct _fixture_variant_socket *const variant,
>> +			const int sockfd,
>> +			const struct _test_data_socket *const self,
>> +			const size_t index, const bool zero_size)
>> +
> 
> Extra new line.

   Will be deleted. Thanks.
> 
>> +{
>> +	if (variant->is_ipv4)
>> +		return bind(sockfd, &self->addr4[index],
>> +			    (zero_size ? 0 : sizeof(self->addr4[index])));
> 
> Is the zero_size really useful? Do calling bind and connect with this
> argument reaches the Landlock code (check_addrlen) or is it caught by
> the network code beforehand?

   In __sys_bind() syscall security_socket_bind() function goes before
   sock->ops->bind() method. Selinux and Smacks provide such checks in
   bind()/connect() hooks, so I think Landlock should do the same.
   What do you think?
> 
> 
>> +	else
>> +		return bind(sockfd, &self->addr6[index],
>> +			    (zero_size ? 0 : sizeof(self->addr6[index])));
>> +}
>> +
>> +static int connect_variant(const struct _fixture_variant_socket *const variant,
>> +			   const int sockfd,
>> +			   const struct _test_data_socket *const self,
>> +			   const size_t index, const bool zero_size)
>> +{
>> +	if (variant->is_ipv4)
>> +		return connect(sockfd, &self->addr4[index],
>> +			       (zero_size ? 0 : sizeof(self->addr4[index])));
>> +	else
>> +		return connect(sockfd, &self->addr6[index],
>> +			       (zero_size ? 0 : sizeof(self->addr6[index])));
>> +}
> 
> 
> [...]
> 
>> +
>> +TEST_F_FORK(socket, bind)
>> +{
>> +	int sockfd;
>> +
>> +	struct landlock_ruleset_attr ruleset_attr = {
>> +		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +	};
>> +	struct landlock_net_service_attr net_service_1 = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> +				  LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +		.port = htons(self->port[0]),
>> +	};
>> +	struct landlock_net_service_attr net_service_2 = {
>> +		.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> +		.port = htons(self->port[1]),
>> +	};
>> +	struct landlock_net_service_attr net_service_3 = {
>> +		.allowed_access = 0,
>> +		.port = htons(self->port[2]),
>> +	};
>> +	int ruleset_fd, ret;
>> +
>> +	if (variant->is_sandboxed) {
>> +		ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>> +						     sizeof(ruleset_attr), 0);
>> +		ASSERT_LE(0, ruleset_fd);
>> +
>> +		/*
>> +		 * Allows connect and bind operations to the port[0]
>> +		 * socket.
>> +		 */
>> +		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
>> +					       LANDLOCK_RULE_NET_SERVICE,
>> +					       &net_service_1, 0));
>> +		/*
>> +		 * Allows connect and deny bind operations to the port[1]
>> +		 * socket.
>> +		 */
>> +		ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
>> +					       LANDLOCK_RULE_NET_SERVICE,
>> +					       &net_service_2, 0));
>> +		/*
>> +		 * Empty allowed_access (i.e. deny rules) are ignored in
>> +		 * network actions for port[2] socket.
>> +		 */
>> +		ASSERT_EQ(-1, landlock_add_rule(ruleset_fd,
>> +						LANDLOCK_RULE_NET_SERVICE,
>> +						&net_service_3, 0));
>> +		ASSERT_EQ(ENOMSG, errno);
>> +
>> +		/* Enforces the ruleset. */
>> +		enforce_ruleset(_metadata, ruleset_fd);
>> +	}
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port[0]. */
>> +	ret = bind_variant(variant, sockfd, self, 0, false);
>> +	if (variant->is_sandboxed) {
>> +		ASSERT_EQ(0, ret);
>> +	} else {
>> +		ASSERT_EQ(0, ret);
>> +	
> The condition is useless here. Same on multiple other locations.
> 
   Ok. Will be fixed.
> 
>> +
>> +	/* Closes bounded socket. */
>> +	ASSERT_EQ(0, close(sockfd));
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port[1]. */
>> +	ret = bind_variant(variant, sockfd, self, 1, false);
>> +	if (variant->is_sandboxed) {
>> +		ASSERT_EQ(-1, ret);
>> +		ASSERT_EQ(EACCES, errno);
>> +	} else {
>> +		ASSERT_EQ(0, ret);
>> +	}
>> +
>> +	sockfd = create_socket_variant(variant, SOCK_STREAM);
>> +	ASSERT_LE(0, sockfd);
>> +	/* Binds a socket to port[2]. */
>> +	ret = bind_variant(variant, sockfd, self, 2, false);
>> +	if (variant->is_sandboxed) {
>> +		ASSERT_EQ(-1, ret);
>> +		ASSERT_EQ(EACCES, errno);
>> +	} else {
>> +		ASSERT_EQ(0, ret);
>> +	}
>> +}
> .

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-02-21 16:16           ` Mickaël Salaün
@ 2023-03-06 13:43             ` Konstantin Meskhidze (A)
  2023-03-06 16:09               ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06 13:43 UTC (permalink / raw)
  To: Mickaël Salaün, Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/21/2023 7:16 PM, Mickaël Salaün пишет:
> 
> On 30/01/2023 11:03, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 1/27/2023 9:22 PM, Mickaël Salaün пишет:
>>>
>>> On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 1/22/2023 2:07 AM, Günther Noack пишет:
>>>
>>> [...]
>>>
>>>>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>>>>    ABI version.  In this example, this is not required because all of the requested
>>>>>>    ``allowed_access`` rights are already available in ABI 1.
>>>>>>    
>>>>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>>>>> -denying all other handled accesses for the filesystem.  The next step is to
>>>>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>>>>> -binary).
>>>>>> +For network access-control, we can add a set of rules that allow to use a port
>>>>>> +number for a specific action. All ports values must be defined in network byte
>>>>>> +order.
>>>>>
>>>>> What is the point of asking user space to convert this to network byte
>>>>> order? It seems to me that the kernel would be able to convert it to
>>>>> network byte order very easily internally and in a single place -- why
>>>>> ask all of the users to deal with that complexity? Am I overlooking
>>>>> something?
>>>>
>>>>     I had a discussion about this issue with Mickaёl.
>>>>     Please check these threads:
>>>>     1.
>>>> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>>>>     2.
>>>> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
>>>
>>> I'm definitely not sure if this is the right solution, or if there is
>>> one. The rationale is to make it close to the current (POSIX) API. We
>>> didn't get many opinion about that but I'd really like to have a
>>> discussion about port endianness for this Landlock API.
>> 
>>     As for me, the kernel should take care about port converting. This
>> work should be done under the hood.
>> 
>>     Any thoughts?
>> 
>>>
>>> I looked at some code (e.g. see [1]) and it seems that using htons()
>>> might make application patching more complex after all. What do you
>>> think? Is there some network (syscall) API that don't use this convention?
>>>
>>> [1] https://github.com/landlock-lsm/tuto-lighttpd
>>>
>>>>>
>>>>>> +
>>>>>> +.. code-block:: c
>>>>>> +
>>>>>> +    struct landlock_net_service_attr net_service = {
>>>>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>>> +        .port = htons(8080),
>>>>>> +    };
>>>>>
>>>>> This is a more high-level comment:
>>>>>
>>>>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>>>>> how do you envision this struct to evolve if other protocols need to
>>>>> be supported in the future?
>>>>
>>>>      When TCP restrictions land into Linux, we need to think about UDP
>>>> support. Then other protocols will be on the road. Anyway you are right
>>>> this struct will be evolving in long term, but I don't have a particular
>>>> envision now. Thanks for the question - we need to think about it.
>>>>>
>>>>> Should this struct and the associated constants have "TCP" in its
>>>>> name, and other protocols use a separate struct in the future?
>>>
>>> Other protocols such as AF_VSOCK uses a 32-bit port. We could use a
>>> 32-bits port field or ever a 64-bit one. The later could make more sense
>>> because each field would eventually be aligned on 64-bit. Picking a
>>> 16-bit value was to help developers (and compilers/linters) with the
>>> "correct" type (for TCP).
> 
> Thinking more about this, let's use a __u64 port (and remove the
> explicit packing). The landlock_append_net_rule() function should use a
> __u16 port argument, but the add_rule_net_service() function should
> check that there is no overflow with the port attribute (not higher than
> U16_MAX) before passing it to landlock_append_net_rule(). We should
> prioritize flexibility for the kernel UAPI over stricter types. User
> space libraries can improve this kind of types with a more complex API.
> 
> Big endian can make sense for a pure network API because the port value
> (and the IP address) is passed to other machines through the network,
> as-is. However, with Landlock, the port value is only used by the
> kernel. Moreover, in practice, port values are mostly converted when
> filling the sockaddr*_in structs. It would then make it more risky to
> ask developers another explicit htons() conversion for Landlock
> syscalls. Let's stick to the host endianess and let the kernel do the
> conversion.
> 
> Please include these rationales in code comments. We also need to update
> the tests for endianess, but still check big and little endian
> consistency as it is currently done in these tests. A new test should be
> added to check port boundaries with:
> - port = 0
> - port = U16_MAX
     port = U16_MAX value passes.

> - port = U16_MAX + 1 (which should get an EINVAL)
     port = U16_MAX + 1 after casting is 0, EINVAL is returned.

> - port = U16_MAX + 2 (to check u16 casting != 0)
     port = U16_MAX + 2 after casting is 1, is it passes?

> - port = U32_MAX + 1
> - port = U32_MAX + 2

     Don't you think that all port values >= U16_MAX + 1, EINVAL should
     be returned?
> 
> 
>>>
>>> If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it
>>> could make sense to have a dedicated attr struct specifying other
>>> properties (e.g. CID). Anyway, the API is flexible but it would be nice
>>> to not mess with it too much. What do you think?
>>>
>>>
>>>>>
>>>>>> +
>>>>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>>>> +                            &net_service, 0);
>>>>>> +
>>>>>> +The next step is to restrict the current thread from gaining more privileges
>>>>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>>>>             ^^^^^^
>>>>>             "through" a SUID binary? "thanks to" sounds like it's desired
>>>>>             to do that, while we're actually trying to prevent it here?
>>>>
>>>>      This is Mickaёl's part. Let's ask his opinion here.
>>>>
>>>>      Mickaёl, any thoughts?
>>>
>>> Yep, "through" looks better.
>>> .
> .

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

* Re: [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-03-06 12:03     ` Konstantin Meskhidze (A)
@ 2023-03-06 16:00       ` Mickaël Salaün
  2023-03-06 18:13         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-03-06 16:00 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 06/03/2023 13:03, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/21/2023 9:05 PM, Mickaël Salaün пишет:
>>
>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>> These test suites try to check edge cases for TCP sockets
>>> bind() and connect() actions.
>>>
>>> socket:
>>> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
>>> for bind action with AF_UNSPEC socket family.
>>> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
>>> for connect action with AF_UNSPEC socket family.
>>> * ruleset_overlap: Tests with overlapping rules for one port.
>>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>>> gradually added one by one, restricting sockets' connections.
>>> * inval: Tests with invalid user space supplied data:
>>>       - out of range ruleset attribute;
>>>       - unhandled allowed access;
>>>       - zero port value;
>>>       - zero access value;
>>>       - legitimate access values;
>>> * bind_connect_inval_addrlen: Tests with invalid address length
>>> for ipv4/ipv6 sockets.
>>> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
>>>
>>> layout1:
>>> * with_net: Tests with network bind() socket action within
>>> filesystem directory access test.
>>>
>>> Test coverage for security/landlock is 94.1% of 946 lines according
>>> to gcc/gcov-11.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v8:
>>> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
>>> * Refactors AF_UNSPEC tests.
>>> * Adds address length checking tests.
>>> * Convert ports in all tests to __be16.
>>> * Adds invalid port values tests.
>>> * Minor fixes.
>>>
>>> Changes since v7:
>>> * Squashes all selftest commits.
>>> * Adds fs test with network bind() socket action.
>>> * Minor fixes.
>>>
>>> ---
>>>    tools/testing/selftests/landlock/config     |    4 +
>>>    tools/testing/selftests/landlock/fs_test.c  |   65 ++
>>>    tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>>>    3 files changed, 1226 insertions(+)
>>>    create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>
>>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>>> index 0f0a65287bac..71f7e9a8a64c 100644
>>> --- a/tools/testing/selftests/landlock/config
>>> +++ b/tools/testing/selftests/landlock/config

[...]

>>> +static int bind_variant(const struct _fixture_variant_socket *const variant,
>>> +			const int sockfd,
>>> +			const struct _test_data_socket *const self,
>>> +			const size_t index, const bool zero_size)
>>> +
>>
>> Extra new line.
> 
>    Will be deleted. Thanks. >>
>>> +{
>>> +	if (variant->is_ipv4)
>>> +		return bind(sockfd, &self->addr4[index],
>>> +			    (zero_size ? 0 : sizeof(self->addr4[index])));
>>
>> Is the zero_size really useful? Do calling bind and connect with this
>> argument reaches the Landlock code (check_addrlen) or is it caught by
>> the network code beforehand?
> 
>     In __sys_bind() syscall security_socket_bind() function goes before
>     sock->ops->bind() method. Selinux and Smacks provide such checks in
>     bind()/connect() hooks, so I think Landlock should do the same.
>     What do you think?

Yes, we should keep these checks. However, we should have a 
bind_variant() without the zero_size argument because it is only set to 
true once (in bind_connect_inval_addrlen). You can explicitly call 
bind() with a zero size in bind_connect_inval_addrlen().

Same for connect_variant().


>>
>>
>>> +	else
>>> +		return bind(sockfd, &self->addr6[index],
>>> +			    (zero_size ? 0 : sizeof(self->addr6[index])));
>>> +}
>>> +
>>> +static int connect_variant(const struct _fixture_variant_socket *const variant,
>>> +			   const int sockfd,
>>> +			   const struct _test_data_socket *const self,
>>> +			   const size_t index, const bool zero_size)
>>> +{
>>> +	if (variant->is_ipv4)
>>> +		return connect(sockfd, &self->addr4[index],
>>> +			       (zero_size ? 0 : sizeof(self->addr4[index])));
>>> +	else
>>> +		return connect(sockfd, &self->addr6[index],
>>> +			       (zero_size ? 0 : sizeof(self->addr6[index])));
>>> +}

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-03-06 13:43             ` Konstantin Meskhidze (A)
@ 2023-03-06 16:09               ` Mickaël Salaün
  2023-03-06 17:55                 ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-03-06 16:09 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 06/03/2023 14:43, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/21/2023 7:16 PM, Mickaël Salaün пишет:
>>
>> On 30/01/2023 11:03, Konstantin Meskhidze (A) wrote:
>>>
>>>
>>> 1/27/2023 9:22 PM, Mickaël Salaün пишет:
>>>>
>>>> On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
>>>>>
>>>>>
>>>>> 1/22/2023 2:07 AM, Günther Noack пишет:
>>>>
>>>> [...]
>>>>
>>>>>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>>>>>     ABI version.  In this example, this is not required because all of the requested
>>>>>>>     ``allowed_access`` rights are already available in ABI 1.
>>>>>>>     
>>>>>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>>>>>> -denying all other handled accesses for the filesystem.  The next step is to
>>>>>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>>>>>> -binary).
>>>>>>> +For network access-control, we can add a set of rules that allow to use a port
>>>>>>> +number for a specific action. All ports values must be defined in network byte
>>>>>>> +order.
>>>>>>
>>>>>> What is the point of asking user space to convert this to network byte
>>>>>> order? It seems to me that the kernel would be able to convert it to
>>>>>> network byte order very easily internally and in a single place -- why
>>>>>> ask all of the users to deal with that complexity? Am I overlooking
>>>>>> something?
>>>>>
>>>>>      I had a discussion about this issue with Mickaёl.
>>>>>      Please check these threads:
>>>>>      1.
>>>>> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>>>>>      2.
>>>>> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
>>>>
>>>> I'm definitely not sure if this is the right solution, or if there is
>>>> one. The rationale is to make it close to the current (POSIX) API. We
>>>> didn't get many opinion about that but I'd really like to have a
>>>> discussion about port endianness for this Landlock API.
>>>
>>>      As for me, the kernel should take care about port converting. This
>>> work should be done under the hood.
>>>
>>>      Any thoughts?
>>>
>>>>
>>>> I looked at some code (e.g. see [1]) and it seems that using htons()
>>>> might make application patching more complex after all. What do you
>>>> think? Is there some network (syscall) API that don't use this convention?
>>>>
>>>> [1] https://github.com/landlock-lsm/tuto-lighttpd
>>>>
>>>>>>
>>>>>>> +
>>>>>>> +.. code-block:: c
>>>>>>> +
>>>>>>> +    struct landlock_net_service_attr net_service = {
>>>>>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>>>> +        .port = htons(8080),
>>>>>>> +    };
>>>>>>
>>>>>> This is a more high-level comment:
>>>>>>
>>>>>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>>>>>> how do you envision this struct to evolve if other protocols need to
>>>>>> be supported in the future?
>>>>>
>>>>>       When TCP restrictions land into Linux, we need to think about UDP
>>>>> support. Then other protocols will be on the road. Anyway you are right
>>>>> this struct will be evolving in long term, but I don't have a particular
>>>>> envision now. Thanks for the question - we need to think about it.
>>>>>>
>>>>>> Should this struct and the associated constants have "TCP" in its
>>>>>> name, and other protocols use a separate struct in the future?
>>>>
>>>> Other protocols such as AF_VSOCK uses a 32-bit port. We could use a
>>>> 32-bits port field or ever a 64-bit one. The later could make more sense
>>>> because each field would eventually be aligned on 64-bit. Picking a
>>>> 16-bit value was to help developers (and compilers/linters) with the
>>>> "correct" type (for TCP).
>>
>> Thinking more about this, let's use a __u64 port (and remove the
>> explicit packing). The landlock_append_net_rule() function should use a
>> __u16 port argument, but the add_rule_net_service() function should
>> check that there is no overflow with the port attribute (not higher than
>> U16_MAX) before passing it to landlock_append_net_rule(). We should
>> prioritize flexibility for the kernel UAPI over stricter types. User
>> space libraries can improve this kind of types with a more complex API.
>>
>> Big endian can make sense for a pure network API because the port value
>> (and the IP address) is passed to other machines through the network,
>> as-is. However, with Landlock, the port value is only used by the
>> kernel. Moreover, in practice, port values are mostly converted when
>> filling the sockaddr*_in structs. It would then make it more risky to
>> ask developers another explicit htons() conversion for Landlock
>> syscalls. Let's stick to the host endianess and let the kernel do the
>> conversion.
>>
>> Please include these rationales in code comments. We also need to update
>> the tests for endianess, but still check big and little endian
>> consistency as it is currently done in these tests. A new test should be
>> added to check port boundaries with:
>> - port = 0
>> - port = U16_MAX
>       port = U16_MAX value passes.

correct

> 
>> - port = U16_MAX + 1 (which should get an EINVAL)
>       port = U16_MAX + 1 after casting is 0, EINVAL is returned.

In the tests, we want the casting to be be done by the kernel. The test 
should then pass 0x10000 to the struct and the kernel should return 
EINVAL because it is greater than U16_MAX, not because it is zero.

> 
>> - port = U16_MAX + 2 (to check u16 casting != 0)
>       port = U16_MAX + 2 after casting is 1, is it passes?

In this case, 0x10001 should be rejected by the kernel (and return 
EINVAL) because it is greater than U16_MAX.

> 
>> - port = U32_MAX + 1
>> - port = U32_MAX + 2
> 
>       Don't you think that all port values >= U16_MAX + 1, EINVAL should
>       be returned?

All port values > U16_MAX should indeed return EINVAL, and tests should 
check kernel casting (i.e. the kernel must check the 64-bit value before 
casting it to a 16-bit value and only check the casted zero). I didn't 
mean that these cases should pass, only that they should be tested, but 
I think you got it. ;)

>>
>>
>>>>
>>>> If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it
>>>> could make sense to have a dedicated attr struct specifying other
>>>> properties (e.g. CID). Anyway, the API is flexible but it would be nice
>>>> to not mess with it too much. What do you think?
>>>>
>>>>
>>>>>>
>>>>>>> +
>>>>>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>>>>> +                            &net_service, 0);
>>>>>>> +
>>>>>>> +The next step is to restrict the current thread from gaining more privileges
>>>>>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>>>>>              ^^^^^^
>>>>>>              "through" a SUID binary? "thanks to" sounds like it's desired
>>>>>>              to do that, while we're actually trying to prevent it here?
>>>>>
>>>>>       This is Mickaёl's part. Let's ask his opinion here.
>>>>>
>>>>>       Mickaёl, any thoughts?
>>>>
>>>> Yep, "through" looks better.
>>>> .
>> .

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

* Re: [PATCH v9 12/12] landlock: Document Landlock's network support
  2023-03-06 16:09               ` Mickaël Salaün
@ 2023-03-06 17:55                 ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06 17:55 UTC (permalink / raw)
  To: Mickaël Salaün, Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



3/6/2023 7:09 PM, Mickaël Salaün пишет:
> 
> On 06/03/2023 14:43, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 2/21/2023 7:16 PM, Mickaël Salaün пишет:
>>>
>>> On 30/01/2023 11:03, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 1/27/2023 9:22 PM, Mickaël Salaün пишет:
>>>>>
>>>>> On 23/01/2023 10:38, Konstantin Meskhidze (A) wrote:
>>>>>>
>>>>>>
>>>>>> 1/22/2023 2:07 AM, Günther Noack пишет:
>>>>>
>>>>> [...]
>>>>>
>>>>>>>> @@ -143,10 +157,24 @@ for the ruleset creation, by filtering access rights according to the Landlock
>>>>>>>>     ABI version.  In this example, this is not required because all of the requested
>>>>>>>>     ``allowed_access`` rights are already available in ABI 1.
>>>>>>>>     
>>>>>>>> -We now have a ruleset with one rule allowing read access to ``/usr`` while
>>>>>>>> -denying all other handled accesses for the filesystem.  The next step is to
>>>>>>>> -restrict the current thread from gaining more privileges (e.g. thanks to a SUID
>>>>>>>> -binary).
>>>>>>>> +For network access-control, we can add a set of rules that allow to use a port
>>>>>>>> +number for a specific action. All ports values must be defined in network byte
>>>>>>>> +order.
>>>>>>>
>>>>>>> What is the point of asking user space to convert this to network byte
>>>>>>> order? It seems to me that the kernel would be able to convert it to
>>>>>>> network byte order very easily internally and in a single place -- why
>>>>>>> ask all of the users to deal with that complexity? Am I overlooking
>>>>>>> something?
>>>>>>
>>>>>>      I had a discussion about this issue with Mickaёl.
>>>>>>      Please check these threads:
>>>>>>      1.
>>>>>> https://lore.kernel.org/netdev/49391484-7401-e7c7-d909-3bd6bd024731@digikod.net/
>>>>>>      2.
>>>>>> https://lore.kernel.org/netdev/1ed20e34-c252-b849-ab92-78c82901c979@huawei.com/
>>>>>
>>>>> I'm definitely not sure if this is the right solution, or if there is
>>>>> one. The rationale is to make it close to the current (POSIX) API. We
>>>>> didn't get many opinion about that but I'd really like to have a
>>>>> discussion about port endianness for this Landlock API.
>>>>
>>>>      As for me, the kernel should take care about port converting. This
>>>> work should be done under the hood.
>>>>
>>>>      Any thoughts?
>>>>
>>>>>
>>>>> I looked at some code (e.g. see [1]) and it seems that using htons()
>>>>> might make application patching more complex after all. What do you
>>>>> think? Is there some network (syscall) API that don't use this convention?
>>>>>
>>>>> [1] https://github.com/landlock-lsm/tuto-lighttpd
>>>>>
>>>>>>>
>>>>>>>> +
>>>>>>>> +.. code-block:: c
>>>>>>>> +
>>>>>>>> +    struct landlock_net_service_attr net_service = {
>>>>>>>> +        .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>>>>> +        .port = htons(8080),
>>>>>>>> +    };
>>>>>>>
>>>>>>> This is a more high-level comment:
>>>>>>>
>>>>>>> The notion of a 16-bit "port" seems to be specific to TCP and UDP --
>>>>>>> how do you envision this struct to evolve if other protocols need to
>>>>>>> be supported in the future?
>>>>>>
>>>>>>       When TCP restrictions land into Linux, we need to think about UDP
>>>>>> support. Then other protocols will be on the road. Anyway you are right
>>>>>> this struct will be evolving in long term, but I don't have a particular
>>>>>> envision now. Thanks for the question - we need to think about it.
>>>>>>>
>>>>>>> Should this struct and the associated constants have "TCP" in its
>>>>>>> name, and other protocols use a separate struct in the future?
>>>>>
>>>>> Other protocols such as AF_VSOCK uses a 32-bit port. We could use a
>>>>> 32-bits port field or ever a 64-bit one. The later could make more sense
>>>>> because each field would eventually be aligned on 64-bit. Picking a
>>>>> 16-bit value was to help developers (and compilers/linters) with the
>>>>> "correct" type (for TCP).
>>>
>>> Thinking more about this, let's use a __u64 port (and remove the
>>> explicit packing). The landlock_append_net_rule() function should use a
>>> __u16 port argument, but the add_rule_net_service() function should
>>> check that there is no overflow with the port attribute (not higher than
>>> U16_MAX) before passing it to landlock_append_net_rule(). We should
>>> prioritize flexibility for the kernel UAPI over stricter types. User
>>> space libraries can improve this kind of types with a more complex API.
>>>
>>> Big endian can make sense for a pure network API because the port value
>>> (and the IP address) is passed to other machines through the network,
>>> as-is. However, with Landlock, the port value is only used by the
>>> kernel. Moreover, in practice, port values are mostly converted when
>>> filling the sockaddr*_in structs. It would then make it more risky to
>>> ask developers another explicit htons() conversion for Landlock
>>> syscalls. Let's stick to the host endianess and let the kernel do the
>>> conversion.
>>>
>>> Please include these rationales in code comments. We also need to update
>>> the tests for endianess, but still check big and little endian
>>> consistency as it is currently done in these tests. A new test should be
>>> added to check port boundaries with:
>>> - port = 0
>>> - port = U16_MAX
>>       port = U16_MAX value passes.
> 
> correct
> 
>> 
>>> - port = U16_MAX + 1 (which should get an EINVAL)
>>       port = U16_MAX + 1 after casting is 0, EINVAL is returned.
> 
> In the tests, we want the casting to be be done by the kernel. The test
> should then pass 0x10000 to the struct and the kernel should return
> EINVAL because it is greater than U16_MAX, not because it is zero.
> 
>> 
>>> - port = U16_MAX + 2 (to check u16 casting != 0)
>>       port = U16_MAX + 2 after casting is 1, is it passes?
> 
> In this case, 0x10001 should be rejected by the kernel (and return
> EINVAL) because it is greater than U16_MAX.
> 
>> 
>>> - port = U32_MAX + 1
>>> - port = U32_MAX + 2
>> 
>>       Don't you think that all port values >= U16_MAX + 1, EINVAL should
>>       be returned?
> 
> All port values > U16_MAX should indeed return EINVAL, and tests should
> check kernel casting (i.e. the kernel must check the 64-bit value before
> casting it to a 16-bit value and only check the casted zero). I didn't
> mean that these cases should pass, only that they should be tested, but
> I think you got it. ;)

   Yep. I got the point. Thanks.
> 
>>>
>>>
>>>>>
>>>>> If we think about protocols other than TCP and UDP (e.g. AF_VSOCK), it
>>>>> could make sense to have a dedicated attr struct specifying other
>>>>> properties (e.g. CID). Anyway, the API is flexible but it would be nice
>>>>> to not mess with it too much. What do you think?
>>>>>
>>>>>
>>>>>>>
>>>>>>>> +
>>>>>>>> +    err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE,
>>>>>>>> +                            &net_service, 0);
>>>>>>>> +
>>>>>>>> +The next step is to restrict the current thread from gaining more privileges
>>>>>>>> +(e.g. thanks to a SUID binary). We now have a ruleset with the first rule allowing
>>>>>>>              ^^^^^^
>>>>>>>              "through" a SUID binary? "thanks to" sounds like it's desired
>>>>>>>              to do that, while we're actually trying to prevent it here?
>>>>>>
>>>>>>       This is Mickaёl's part. Let's ask his opinion here.
>>>>>>
>>>>>>       Mickaёl, any thoughts?
>>>>>
>>>>> Yep, "through" looks better.
>>>>> .
>>> .
> .

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

* Re: [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network
  2023-03-06 16:00       ` Mickaël Salaün
@ 2023-03-06 18:13         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-06 18:13 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



3/6/2023 7:00 PM, Mickaël Salaün пишет:
> 
> On 06/03/2023 13:03, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 2/21/2023 9:05 PM, Mickaël Salaün пишет:
>>>
>>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>>> These test suites try to check edge cases for TCP sockets
>>>> bind() and connect() actions.
>>>>
>>>> socket:
>>>> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>>> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets.
>>>> * bind_afunspec: Tests with non-landlocked/landlocked restrictions
>>>> for bind action with AF_UNSPEC socket family.
>>>> * connect_afunspec: Tests with non-landlocked/landlocked restrictions
>>>> for connect action with AF_UNSPEC socket family.
>>>> * ruleset_overlap: Tests with overlapping rules for one port.
>>>> * ruleset_expanding: Tests with expanding rulesets in which rules are
>>>> gradually added one by one, restricting sockets' connections.
>>>> * inval: Tests with invalid user space supplied data:
>>>>       - out of range ruleset attribute;
>>>>       - unhandled allowed access;
>>>>       - zero port value;
>>>>       - zero access value;
>>>>       - legitimate access values;
>>>> * bind_connect_inval_addrlen: Tests with invalid address length
>>>> for ipv4/ipv6 sockets.
>>>> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets.
>>>>
>>>> layout1:
>>>> * with_net: Tests with network bind() socket action within
>>>> filesystem directory access test.
>>>>
>>>> Test coverage for security/landlock is 94.1% of 946 lines according
>>>> to gcc/gcov-11.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v8:
>>>> * Adds is_sandboxed const for FIXTURE_VARIANT(socket).
>>>> * Refactors AF_UNSPEC tests.
>>>> * Adds address length checking tests.
>>>> * Convert ports in all tests to __be16.
>>>> * Adds invalid port values tests.
>>>> * Minor fixes.
>>>>
>>>> Changes since v7:
>>>> * Squashes all selftest commits.
>>>> * Adds fs test with network bind() socket action.
>>>> * Minor fixes.
>>>>
>>>> ---
>>>>    tools/testing/selftests/landlock/config     |    4 +
>>>>    tools/testing/selftests/landlock/fs_test.c  |   65 ++
>>>>    tools/testing/selftests/landlock/net_test.c | 1157 +++++++++++++++++++
>>>>    3 files changed, 1226 insertions(+)
>>>>    create mode 100644 tools/testing/selftests/landlock/net_test.c
>>>>
>>>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>>>> index 0f0a65287bac..71f7e9a8a64c 100644
>>>> --- a/tools/testing/selftests/landlock/config
>>>> +++ b/tools/testing/selftests/landlock/config
> 
> [...]
> 
>>>> +static int bind_variant(const struct _fixture_variant_socket *const variant,
>>>> +			const int sockfd,
>>>> +			const struct _test_data_socket *const self,
>>>> +			const size_t index, const bool zero_size)
>>>> +
>>>
>>> Extra new line.
>> 
>>    Will be deleted. Thanks. >>
>>>> +{
>>>> +	if (variant->is_ipv4)
>>>> +		return bind(sockfd, &self->addr4[index],
>>>> +			    (zero_size ? 0 : sizeof(self->addr4[index])));
>>>
>>> Is the zero_size really useful? Do calling bind and connect with this
>>> argument reaches the Landlock code (check_addrlen) or is it caught by
>>> the network code beforehand?
>> 
>>     In __sys_bind() syscall security_socket_bind() function goes before
>>     sock->ops->bind() method. Selinux and Smacks provide such checks in
>>     bind()/connect() hooks, so I think Landlock should do the same.
>>     What do you think?
> 
> Yes, we should keep these checks. However, we should have a
> bind_variant() without the zero_size argument because it is only set to
> true once (in bind_connect_inval_addrlen). You can explicitly call
> bind() with a zero size in bind_connect_inval_addrlen().
> 
> Same for connect_variant().

  Ok. Will be fixed.
> 
> 
>>>
>>>
>>>> +	else
>>>> +		return bind(sockfd, &self->addr6[index],
>>>> +			    (zero_size ? 0 : sizeof(self->addr6[index])));
>>>> +}
>>>> +
>>>> +static int connect_variant(const struct _fixture_variant_socket *const variant,
>>>> +			   const int sockfd,
>>>> +			   const struct _test_data_socket *const self,
>>>> +			   const size_t index, const bool zero_size)
>>>> +{
>>>> +	if (variant->is_ipv4)
>>>> +		return connect(sockfd, &self->addr4[index],
>>>> +			       (zero_size ? 0 : sizeof(self->addr4[index])));
>>>> +	else
>>>> +		return connect(sockfd, &self->addr6[index],
>>>> +			       (zero_size ? 0 : sizeof(self->addr6[index])));
>>>> +}
> .

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

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-02-10 17:39   ` Mickaël Salaün
  2023-02-14 10:19     ` Konstantin Meskhidze (A)
@ 2023-03-13  9:33     ` Konstantin Meskhidze (A)
  2023-03-14 12:13       ` Mickaël Salaün
  1 sibling, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-13  9:33 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/10/2023 8:39 PM, Mickaël Salaün пишет:
> 
> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>> This commit adds network rules support in the ruleset management
>> helpers and the landlock_create_ruleset syscall.
>> Refactor user space API to support network actions. Add new network
>> access flags, network rule and network attributes. Increment Landlock
>> ABI version. Expand access_masks_t to u32 to be sure network access
>> rights can be stored. Implement socket_bind() and socket_connect()
>> LSM hooks, which enable to restrict TCP socket binding and connection
>> to specific ports.
>> 
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>> 
>> Changes since v8:
>> * Squashes commits.
>> * Refactors commit message.
>> * Changes UAPI port field to __be16.
>> * Changes logic of bind/connect hooks with AF_UNSPEC families.
>> * Adds address length checking.
>> * Minor fixes.
>> 
>> Changes since v7:
>> * Squashes commits.
>> * Increments ABI version to 4.
>> * Refactors commit message.
>> * Minor fixes.
>> 
>> Changes since v6:
>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>    because it OR values.
>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>> * Refactors landlock_get_net_access_mask().
>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>    LANDLOCK_NUM_ACCESS_FS as value.
>> * Updates access_masks_t to u32 to support network access actions.
>> * Refactors landlock internal functions to support network actions with
>>    landlock_key/key_type/id types.
>> 
>> Changes since v5:
>> * Gets rid of partial revert from landlock_add_rule
>> syscall.
>> * Formats code with clang-format-14.
>> 
>> Changes since v4:
>> * Refactors landlock_create_ruleset() - splits ruleset and
>> masks checks.
>> * Refactors landlock_create_ruleset() and landlock mask
>> setters/getters to support two rule types.
>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>> function by factoring out get_ruleset_from_fd() and
>> landlock_put_ruleset().
>> 
>> Changes since v3:
>> * Splits commit.
>> * Adds network rule support for internal landlock functions.
>> * Adds set_mask and get_mask for network.
>> * Adds rb_root root_net_port.
> 
> [...]
> 
>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>> +			       struct sockaddr *address, __be16 port,
>> +			       access_mask_t access_request)
>> +{
>> +	bool allowed = false;
>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>> +	const struct landlock_rule *rule;
>> +	access_mask_t handled_access;
>> +	const struct landlock_id id = {
>> +		.key.data = port,
>> +		.type = LANDLOCK_KEY_NET_PORT,
>> +	};
>> +
>> +	if (WARN_ON_ONCE(!domain))
>> +		return 0;
>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>> +		return -EACCES;
>> +
>> +	switch (address->sa_family) {
>> +	case AF_UNSPEC:
>> +		/*
>> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
>> +		 * association, which have the same effect as closing the
>> +		 * connection while retaining the socket object (i.e., the file
>> +		 * descriptor).  As for dropping privileges, closing
>> +		 * connections is always allowed.
>> +		 */
>> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> +			return 0;
>> +
>> +		/*
>> +		 * For compatibility reason, accept AF_UNSPEC for bind
>> +		 * accesses (mapped to AF_INET) only if the address is
>> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
>> +		 * required to not wrongfully return -EACCES instead of
>> +		 * -EAFNOSUPPORT.
>> +		 */
>> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
>> +			const struct sockaddr_in *const sockaddr =
>> +				(struct sockaddr_in *)address;
>> +
>> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
>> +				return -EAFNOSUPPORT;
>> +		}
>> +
>> +		fallthrough;
>> +	case AF_INET:
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6:
>> +#endif
>> +		rule = landlock_find_rule(domain, id);
>> +		handled_access = landlock_init_layer_masks(
>> +			domain, access_request, &layer_masks,
>> +			LANDLOCK_KEY_NET_PORT);
>> +		allowed = landlock_unmask_layers(rule, handled_access,
>> +						 &layer_masks,
>> +						 ARRAY_SIZE(layer_masks));
>> +
>> +		fallthrough;
> 
> You can remove this fallthrough.
> 
> 
>> +	}
>> +	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 sockaddr->sin_port;
>> +	}
>> +#if IS_ENABLED(CONFIG_IPV6)
>> +	case AF_INET6: {
>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>> +			(struct sockaddr_in6 *)address;
>> +		return sockaddr_ip6->sin6_port;
>> +	}
>> +#endif
>> +	}
>> +	WARN_ON_ONCE(1);
>> +	return 0;
>> +}
>> +
>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>> +			    int addrlen)
>> +{
>> +	int ret;
>> +	const struct landlock_ruleset *const dom =
>> +		landlock_get_current_domain();
> 
> landlock_get_current_domain() should only be called by a
> get_current_net_domain() wrapper that checks if the current domain
> handles network accesses. See get_current_fs_domain() in patch 2/12.

   Hi Mickaël.
   I have question:

   static access_mask_t
get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
{
	access_mask_t access_dom = 0;
	size_t layer_level;

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

landlock_get_raw_fs_access_mask() function is already mask by 
LANDLOCK_MASK_ACCESS_FS. We could get rid of access_dom masking.
What do you think?

> 
> 
>> +
>> +	if (!dom)
>> +		return 0;
>> +
>> +	/* Check if it's a TCP socket. */
>> +	if (sock->type != SOCK_STREAM)
>> +		return 0;
>> +
>> +	ret = check_addrlen(address, addrlen);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return check_socket_access(dom, address, get_port(address),
>> +				   LANDLOCK_ACCESS_NET_BIND_TCP);
>> +}
> .

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

* Re: [PATCH v9 00/12] Network support for Landlock
  2023-02-23 22:17 ` [PATCH v9 00/12] Network support for Landlock Günther Noack
  2023-03-06  7:45   ` Konstantin Meskhidze (A)
@ 2023-03-13 17:16   ` Konstantin Meskhidze (A)
  2023-03-14 13:28     ` Mickaël Salaün
  1 sibling, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-13 17:16 UTC (permalink / raw)
  To: Günther Noack
  Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



2/24/2023 1:17 AM, Günther Noack пишет:
> Hello Konstantin!
> 
> Sorry for asking such fundamental questions again so late in the review.
> 
> After playing with patch V9 with the Go-Landlock library, I'm still
> having trouble understanding these questions -- they probably have
> good answers, but I also did not see them explained in the
> documentation. Maybe it would help to clarify it there?
> 
> * What is the use case for permitting processes to connect to a given
>    TCP port, but leaving unspecified what the IP address is?
> 
>    Example: If a Landlock ruleset permits connecting to TCP port 53,
>    that makes it possible to talk to any IP address on the internet (at
>    least if the process runs on a normal Linux desktop machine), and we
>    can't really control whether that is the system's proper (TCP-)DNS
>    server or whether that is an attacker-controlled service for
>    accepting leaked secrets from the process...?
> 
>    Is the plan that IP address support should be added in a follow-up
>    patch?  Will it become part of the landlock_net_service_attr struct?

      In the beginning I introduced the idea with IP address to
Mickaël but he suggested to use port-based granularity. So with ports 
it's worth using Landlock in containerized applications working within 
one IP address. Anyway it's possible to use netfilter to control 
incoming traffic. It's a good question - we should discuss it carefuly.
> 
> * Given the list of obscure network protocols listed in the socket(2)
>    man page, I find it slightly weird to have rules for the use of TCP,
>    but to leave less prominent protocols unrestricted.
> 
>    For example, a process with an enabled Landlock network ruleset may
>    connect only to certain TCP ports, but at the same time it can
>    happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?

      We also have started a discussion about UDP protocol, but it's 
more complicated since UDP sockets does not establish connections 
between each other. There is a performance problem on the first place here.

I'm not familiar with Bluetooth/CAN bus/DECnet/IPX but let's discuss it.
Any ideas here?

> 
>    I'm mentioning these more obscure protocols, because I doubt that
>    Landlock will grow more sophisticated support for them anytime soon,
>    so maybe the best option would be to just make it possible to
>    disable these?  Is that also part of the plan?
> 
>    (I think there would be a lot of value in restricting network
>    access, even when it's done very broadly.  There are many programs
>    that don't need network at all, and among those that do need
>    network, most only require IP networking.
> 
>    Btw, the argument for more broad disabling of network access was
>    already made at https://cr.yp.to/unix/disablenetwork.html in the
>    past.)
      Thanks for the link. I will read it.
> 
> * This one is more of an implementation question: I don't understand
>    why we are storing the networking rules in the same RB tree as the
>    file system rules. - It looks a bit like "YAGNI" to me...?

      Actually network rules are stored in a different RB tree.
      You can check it in struct landlock_ruleset (ruleset.h):
      - struct rb_root root_inodeis for fs rules

      - struct rb_root root_net_port is for network rules;
> 
>    Would it be more efficient to keep the file system rules in the
>    existing RB tree, and store the networking rules *separately* next
>    to it in a different RB tree, or even in a more optimized data
>    structure? In pseudocode:
> 
>      struct fast_lookup_int_set bind_tcp_ports;
>      struct fast_lookup_int_set connect_tcp_ports;
>      struct landlock_rb_tree fs_rules;
> 
>    It seems that there should be a data structure that supports this
>    well and which uses the fact that we only need to store small
>    integers?

      Thnaks for the question. From my point of view it depends on a 
real scenario - how many ports we want to allow by Landlock for a 
proccess - thousands, hundreds or less. If it's just 10 ports - do we 
really need some optimized data structure? Do we get some performance 
gain here?
What do you think?
> 
> Thanks,
> –Günther
> 
> P.S.: Apologies if some of it was discussed previously. I did my best
> to catch up on previous threads, but it's long, and it's possible that
> I missed parts of the discussion.
> 
> On Mon, Jan 16, 2023 at 04:58:06PM +0800, Konstantin Meskhidze wrote:
>> Hi,
>> This is a new V9 patch related to Landlock LSM network confinement.
>> It is based on the landlock's -next branch on top of v6.2-rc3 kernel version:
>> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next
>> 
>> It brings refactoring of previous patch version V8.
>> Mostly there are fixes of logic and typos, adding new tests.
>> 
>> All test were run in QEMU evironment and compiled with
>>  -static flag.
>>  1. network_test: 32/32 tests passed.
>>  2. base_test: 7/7 tests passed.
>>  3. fs_test: 78/78 tests passed.
>>  4. ptrace_test: 8/8 tests passed.
>> 
>> Previous versions:
>> v8: https://lore.kernel.org/linux-security-module/20221021152644.155136-1-konstantin.meskhidze@huawei.com/
>> v7: https://lore.kernel.org/linux-security-module/20220829170401.834298-1-konstantin.meskhidze@huawei.com/
>> v6: https://lore.kernel.org/linux-security-module/20220621082313.3330667-1-konstantin.meskhidze@huawei.com/
>> v5: https://lore.kernel.org/linux-security-module/20220516152038.39594-1-konstantin.meskhidze@huawei.com
>> v4: https://lore.kernel.org/linux-security-module/20220309134459.6448-1-konstantin.meskhidze@huawei.com/
>> v3: https://lore.kernel.org/linux-security-module/20220124080215.265538-1-konstantin.meskhidze@huawei.com/
>> v2: https://lore.kernel.org/linux-security-module/20211228115212.703084-1-konstantin.meskhidze@huawei.com/
>> v1: https://lore.kernel.org/linux-security-module/20211210072123.386713-1-konstantin.meskhidze@huawei.com/
>> 
>> Konstantin Meskhidze (11):
>>   landlock: Make ruleset's access masks more generic
>>   landlock: Refactor landlock_find_rule/insert_rule
>>   landlock: Refactor merge/inherit_ruleset functions
>>   landlock: Move and rename umask_layers() and init_layer_masks()
>>   landlock: Refactor _unmask_layers() and _init_layer_masks()
>>   landlock: Refactor landlock_add_rule() syscall
>>   landlock: Add network rules and TCP hooks support
>>   selftests/landlock: Share enforce_ruleset()
>>   selftests/landlock: Add 10 new test suites dedicated to network
>>   samples/landlock: Add network demo
>>   landlock: Document Landlock's network support
>> 
>> Mickaël Salaün (1):
>>   landlock: Allow filesystem layout changes for domains without such
>>     rule type
>> 
>>  Documentation/userspace-api/landlock.rst     |   72 +-
>>  include/uapi/linux/landlock.h                |   49 +
>>  samples/landlock/sandboxer.c                 |  131 +-
>>  security/landlock/Kconfig                    |    1 +
>>  security/landlock/Makefile                   |    2 +
>>  security/landlock/fs.c                       |  255 ++--
>>  security/landlock/limits.h                   |    7 +-
>>  security/landlock/net.c                      |  200 +++
>>  security/landlock/net.h                      |   26 +
>>  security/landlock/ruleset.c                  |  409 +++++--
>>  security/landlock/ruleset.h                  |  185 ++-
>>  security/landlock/setup.c                    |    2 +
>>  security/landlock/syscalls.c                 |  165 ++-
>>  tools/testing/selftests/landlock/base_test.c |    2 +-
>>  tools/testing/selftests/landlock/common.h    |   10 +
>>  tools/testing/selftests/landlock/config      |    4 +
>>  tools/testing/selftests/landlock/fs_test.c   |   75 +-
>>  tools/testing/selftests/landlock/net_test.c  | 1157 ++++++++++++++++++
>>  18 files changed, 2398 insertions(+), 354 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] 74+ messages in thread

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-03-13  9:33     ` Konstantin Meskhidze (A)
@ 2023-03-14 12:13       ` Mickaël Salaün
  2023-03-14 14:38         ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-03-14 12:13 UTC (permalink / raw)
  To: Konstantin Meskhidze (A)
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 13/03/2023 10:33, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/10/2023 8:39 PM, Mickaël Salaün пишет:
>>
>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>> This commit adds network rules support in the ruleset management
>>> helpers and the landlock_create_ruleset syscall.
>>> Refactor user space API to support network actions. Add new network
>>> access flags, network rule and network attributes. Increment Landlock
>>> ABI version. Expand access_masks_t to u32 to be sure network access
>>> rights can be stored. Implement socket_bind() and socket_connect()
>>> LSM hooks, which enable to restrict TCP socket binding and connection
>>> to specific ports.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v8:
>>> * Squashes commits.
>>> * Refactors commit message.
>>> * Changes UAPI port field to __be16.
>>> * Changes logic of bind/connect hooks with AF_UNSPEC families.
>>> * Adds address length checking.
>>> * Minor fixes.
>>>
>>> Changes since v7:
>>> * Squashes commits.
>>> * Increments ABI version to 4.
>>> * Refactors commit message.
>>> * Minor fixes.
>>>
>>> Changes since v6:
>>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>>     because it OR values.
>>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>>> * Refactors landlock_get_net_access_mask().
>>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>>     LANDLOCK_NUM_ACCESS_FS as value.
>>> * Updates access_masks_t to u32 to support network access actions.
>>> * Refactors landlock internal functions to support network actions with
>>>     landlock_key/key_type/id types.
>>>
>>> Changes since v5:
>>> * Gets rid of partial revert from landlock_add_rule
>>> syscall.
>>> * Formats code with clang-format-14.
>>>
>>> Changes since v4:
>>> * Refactors landlock_create_ruleset() - splits ruleset and
>>> masks checks.
>>> * Refactors landlock_create_ruleset() and landlock mask
>>> setters/getters to support two rule types.
>>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>>> function by factoring out get_ruleset_from_fd() and
>>> landlock_put_ruleset().
>>>
>>> Changes since v3:
>>> * Splits commit.
>>> * Adds network rule support for internal landlock functions.
>>> * Adds set_mask and get_mask for network.
>>> * Adds rb_root root_net_port.
>>
>> [...]
>>
>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>> +			       struct sockaddr *address, __be16 port,
>>> +			       access_mask_t access_request)
>>> +{
>>> +	bool allowed = false;
>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>> +	const struct landlock_rule *rule;
>>> +	access_mask_t handled_access;
>>> +	const struct landlock_id id = {
>>> +		.key.data = port,
>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>> +	};
>>> +
>>> +	if (WARN_ON_ONCE(!domain))
>>> +		return 0;
>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>> +		return -EACCES;
>>> +
>>> +	switch (address->sa_family) {
>>> +	case AF_UNSPEC:
>>> +		/*
>>> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
>>> +		 * association, which have the same effect as closing the
>>> +		 * connection while retaining the socket object (i.e., the file
>>> +		 * descriptor).  As for dropping privileges, closing
>>> +		 * connections is always allowed.
>>> +		 */
>>> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>> +			return 0;
>>> +
>>> +		/*
>>> +		 * For compatibility reason, accept AF_UNSPEC for bind
>>> +		 * accesses (mapped to AF_INET) only if the address is
>>> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
>>> +		 * required to not wrongfully return -EACCES instead of
>>> +		 * -EAFNOSUPPORT.
>>> +		 */
>>> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
>>> +			const struct sockaddr_in *const sockaddr =
>>> +				(struct sockaddr_in *)address;
>>> +
>>> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
>>> +				return -EAFNOSUPPORT;
>>> +		}
>>> +
>>> +		fallthrough;
>>> +	case AF_INET:
>>> +#if IS_ENABLED(CONFIG_IPV6)
>>> +	case AF_INET6:
>>> +#endif
>>> +		rule = landlock_find_rule(domain, id);
>>> +		handled_access = landlock_init_layer_masks(
>>> +			domain, access_request, &layer_masks,
>>> +			LANDLOCK_KEY_NET_PORT);
>>> +		allowed = landlock_unmask_layers(rule, handled_access,
>>> +						 &layer_masks,
>>> +						 ARRAY_SIZE(layer_masks));
>>> +
>>> +		fallthrough;
>>
>> You can remove this fallthrough.
>>
>>
>>> +	}
>>> +	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 sockaddr->sin_port;
>>> +	}
>>> +#if IS_ENABLED(CONFIG_IPV6)
>>> +	case AF_INET6: {
>>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>>> +			(struct sockaddr_in6 *)address;
>>> +		return sockaddr_ip6->sin6_port;
>>> +	}
>>> +#endif
>>> +	}
>>> +	WARN_ON_ONCE(1);
>>> +	return 0;
>>> +}
>>> +
>>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>>> +			    int addrlen)
>>> +{
>>> +	int ret;
>>> +	const struct landlock_ruleset *const dom =
>>> +		landlock_get_current_domain();
>>
>> landlock_get_current_domain() should only be called by a
>> get_current_net_domain() wrapper that checks if the current domain
>> handles network accesses. See get_current_fs_domain() in patch 2/12.
> 
>     Hi Mickaël.
>     I have question:
> 
>     static access_mask_t
> get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
> {
> 	access_mask_t access_dom = 0;
> 	size_t layer_level;
> 
> 	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> 		access_dom |=
> 			landlock_get_raw_fs_access_mask(domain, layer_level);
> 	return access_dom & LANDLOCK_MASK_ACCESS_FS;
> }
> 
> landlock_get_raw_fs_access_mask() function is already mask by
> LANDLOCK_MASK_ACCESS_FS. We could get rid of access_dom masking.
> What do you think?

Right, you can remove this extra mask.

While reading the code again I found that it would be better to rename 
ACCESS_FS_INITIALLY_DENIED to LANDLOCK_ACCESS_FS_INITIALLY_DENIED.

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

* Re: [PATCH v9 00/12] Network support for Landlock
  2023-03-13 17:16   ` Konstantin Meskhidze (A)
@ 2023-03-14 13:28     ` Mickaël Salaün
  2023-06-26 15:29       ` [PATCH v9 00/12] Network support for Landlock - allowed list of protocols Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-03-14 13:28 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin


On 13/03/2023 18:16, Konstantin Meskhidze (A) wrote:
> 
> 
> 2/24/2023 1:17 AM, Günther Noack пишет:
>> Hello Konstantin!
>>
>> Sorry for asking such fundamental questions again so late in the review.
>>
>> After playing with patch V9 with the Go-Landlock library, I'm still
>> having trouble understanding these questions -- they probably have
>> good answers, but I also did not see them explained in the
>> documentation. Maybe it would help to clarify it there?
>>
>> * What is the use case for permitting processes to connect to a given
>>     TCP port, but leaving unspecified what the IP address is?
>>
>>     Example: If a Landlock ruleset permits connecting to TCP port 53,
>>     that makes it possible to talk to any IP address on the internet (at
>>     least if the process runs on a normal Linux desktop machine), and we
>>     can't really control whether that is the system's proper (TCP-)DNS
>>     server or whether that is an attacker-controlled service for
>>     accepting leaked secrets from the process...?
>>
>>     Is the plan that IP address support should be added in a follow-up
>>     patch?  Will it become part of the landlock_net_service_attr struct?
> 
>        In the beginning I introduced the idea with IP address to
> Mickaël but he suggested to use port-based granularity. So with ports
> it's worth using Landlock in containerized applications working within
> one IP address. Anyway it's possible to use netfilter to control
> incoming traffic. It's a good question - we should discuss it carefuly.

Limiting access rule definition to TCP ports is because of two reasons:
- practical one: start with something small and improve it. All new 
features should be covered by tests, and it takes time to write and 
review them, especially to cover IPv4, IPv6 and any other type of 
address to identify a server. Being able to control TCP connect and bind 
is useful and brings the scaffolding for other non-kernel-object 
restrictions.
- semantic one: ports are tied to well-known (or configured) services, 
whatever the network where a process is (e.g. Internet, LAN, container's 
network namespace, VM). However, IP addresses are not well-known but 
(most of the time) tied to names/DNS, which is not handled by the kernel 
but user space. Moreover, I think it makes sense for app/service 
developers to think about reachable services, but much less about 
servers, which depend on the local network and a system-wide configuration.

For some use cases, there is definitely a need to restrict access to a 
set of servers though. I think a new dedicated attr struct would be 
easier to handle and it would make more sense to compose them (ANDing 
all network rule types to make a final decision). This new struct could 
define different kind of subnets (IPv4, IPv6, ethernet, bluetooth…). One 
of this type could be the local link, and especially if the server is 
local to the system or not (i.e. loopback interface), and if the server 
is in a specified network namespace (e.g. specific container/pod).

Anyway, this should indeed be documented.


>>
>> * Given the list of obscure network protocols listed in the socket(2)
>>     man page, I find it slightly weird to have rules for the use of TCP,
>>     but to leave less prominent protocols unrestricted.
>>
>>     For example, a process with an enabled Landlock network ruleset may
>>     connect only to certain TCP ports, but at the same time it can
>>     happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?
> 
>        We also have started a discussion about UDP protocol, but it's
> more complicated since UDP sockets does not establish connections
> between each other. There is a performance problem on the first place here.
> 
> I'm not familiar with Bluetooth/CAN bus/DECnet/IPX but let's discuss it.
> Any ideas here?

All these protocols should be handled one way or another someday. ;)


> 
>>
>>     I'm mentioning these more obscure protocols, because I doubt that
>>     Landlock will grow more sophisticated support for them anytime soon,
>>     so maybe the best option would be to just make it possible to
>>     disable these?  Is that also part of the plan?
>>
>>     (I think there would be a lot of value in restricting network
>>     access, even when it's done very broadly.  There are many programs
>>     that don't need network at all, and among those that do need
>>     network, most only require IP networking.

Indeed, protocols that nobody care to make Landlock supports them will 
probably not have fine-grained control. We could extend the ruleset 
attributes to disable the use (i.e. not only the creation of new related 
sockets/resources) of network protocol families, in a way that would 
make sandboxes simulate a kernel without such protocol support. In this 
case, this should be an allowed list of protocols, and everything not in 
that list should be denied. This approach could be used for other kernel 
features (unrelated to network).


>>
>>     Btw, the argument for more broad disabling of network access was
>>     already made at https://cr.yp.to/unix/disablenetwork.html in the
>>     past.)

This is interesting but scoped to a single use case. As specified at the 
beginning of this linked page, there must be exceptions, not only with 
AF_UNIX but also for (the newer) AF_VSOCK, and probably future ones. 
This is why I don't think a binary approach is a good one for Linux. 
Users should be able to specify what they need, and block the rest.


>        Thanks for the link. I will read it.
>>
>> * This one is more of an implementation question: I don't understand
>>     why we are storing the networking rules in the same RB tree as the
>>     file system rules. - It looks a bit like "YAGNI" to me...?
> 
>        Actually network rules are stored in a different RB tree.
>        You can check it in struct landlock_ruleset (ruleset.h):
>        - struct rb_root root_inodeis for fs rules
> 
>        - struct rb_root root_net_port is for network rules;
>>
>>     Would it be more efficient to keep the file system rules in the
>>     existing RB tree, and store the networking rules *separately* next
>>     to it in a different RB tree, or even in a more optimized data
>>     structure? In pseudocode:
>>
>>       struct fast_lookup_int_set bind_tcp_ports;
>>       struct fast_lookup_int_set connect_tcp_ports;
>>       struct landlock_rb_tree fs_rules;
>>
>>     It seems that there should be a data structure that supports this
>>     well and which uses the fact that we only need to store small
>>     integers?
> 
>        Thnaks for the question. From my point of view it depends on a
> real scenario - how many ports we want to allow by Landlock for a
> proccess - thousands, hundreds or less. If it's just 10 ports - do we
> really need some optimized data structure? Do we get some performance
> gain here?
> What do you think?

As Konstantin explained, there are two different red-black trees. This 
data structure may not be optimal but it is much easier to start with that.

Using one tree per right would increase the size, especially for each 
new access right, but it is worth thinking about a new data structure 
dealing with sets (and ranges) of numbers.

Talking about performance optimization, the first step would be to use a 
hash table for domain's inode identification.


>>
>> Thanks,
>> –Günther
>>
>> P.S.: Apologies if some of it was discussed previously. I did my best
>> to catch up on previous threads, but it's long, and it's possible that
>> I missed parts of the discussion.
>>
>> On Mon, Jan 16, 2023 at 04:58:06PM +0800, Konstantin Meskhidze wrote:
>>> Hi,
>>> This is a new V9 patch related to Landlock LSM network confinement.
>>> It is based on the landlock's -next branch on top of v6.2-rc3 kernel version:
>>> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next
>>>
>>> It brings refactoring of previous patch version V8.
>>> Mostly there are fixes of logic and typos, adding new tests.
>>>
>>> All test were run in QEMU evironment and compiled with
>>>   -static flag.
>>>   1. network_test: 32/32 tests passed.
>>>   2. base_test: 7/7 tests passed.
>>>   3. fs_test: 78/78 tests passed.
>>>   4. ptrace_test: 8/8 tests passed.
>>>
>>> Previous versions:
>>> v8: https://lore.kernel.org/linux-security-module/20221021152644.155136-1-konstantin.meskhidze@huawei.com/
>>> v7: https://lore.kernel.org/linux-security-module/20220829170401.834298-1-konstantin.meskhidze@huawei.com/
>>> v6: https://lore.kernel.org/linux-security-module/20220621082313.3330667-1-konstantin.meskhidze@huawei.com/
>>> v5: https://lore.kernel.org/linux-security-module/20220516152038.39594-1-konstantin.meskhidze@huawei.com
>>> v4: https://lore.kernel.org/linux-security-module/20220309134459.6448-1-konstantin.meskhidze@huawei.com/
>>> v3: https://lore.kernel.org/linux-security-module/20220124080215.265538-1-konstantin.meskhidze@huawei.com/
>>> v2: https://lore.kernel.org/linux-security-module/20211228115212.703084-1-konstantin.meskhidze@huawei.com/
>>> v1: https://lore.kernel.org/linux-security-module/20211210072123.386713-1-konstantin.meskhidze@huawei.com/
>>>
>>> Konstantin Meskhidze (11):
>>>    landlock: Make ruleset's access masks more generic
>>>    landlock: Refactor landlock_find_rule/insert_rule
>>>    landlock: Refactor merge/inherit_ruleset functions
>>>    landlock: Move and rename umask_layers() and init_layer_masks()
>>>    landlock: Refactor _unmask_layers() and _init_layer_masks()
>>>    landlock: Refactor landlock_add_rule() syscall
>>>    landlock: Add network rules and TCP hooks support
>>>    selftests/landlock: Share enforce_ruleset()
>>>    selftests/landlock: Add 10 new test suites dedicated to network
>>>    samples/landlock: Add network demo
>>>    landlock: Document Landlock's network support
>>>
>>> Mickaël Salaün (1):
>>>    landlock: Allow filesystem layout changes for domains without such
>>>      rule type
>>>
>>>   Documentation/userspace-api/landlock.rst     |   72 +-
>>>   include/uapi/linux/landlock.h                |   49 +
>>>   samples/landlock/sandboxer.c                 |  131 +-
>>>   security/landlock/Kconfig                    |    1 +
>>>   security/landlock/Makefile                   |    2 +
>>>   security/landlock/fs.c                       |  255 ++--
>>>   security/landlock/limits.h                   |    7 +-
>>>   security/landlock/net.c                      |  200 +++
>>>   security/landlock/net.h                      |   26 +
>>>   security/landlock/ruleset.c                  |  409 +++++--
>>>   security/landlock/ruleset.h                  |  185 ++-
>>>   security/landlock/setup.c                    |    2 +
>>>   security/landlock/syscalls.c                 |  165 ++-
>>>   tools/testing/selftests/landlock/base_test.c |    2 +-
>>>   tools/testing/selftests/landlock/common.h    |   10 +
>>>   tools/testing/selftests/landlock/config      |    4 +
>>>   tools/testing/selftests/landlock/fs_test.c   |   75 +-
>>>   tools/testing/selftests/landlock/net_test.c  | 1157 ++++++++++++++++++
>>>   18 files changed, 2398 insertions(+), 354 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] 74+ messages in thread

* Re: [PATCH v9 08/12] landlock: Add network rules and TCP hooks support
  2023-03-14 12:13       ` Mickaël Salaün
@ 2023-03-14 14:38         ` Konstantin Meskhidze (A)
  0 siblings, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-03-14 14:38 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin



3/14/2023 3:13 PM, Mickaël Salaün пишет:
> 
> On 13/03/2023 10:33, Konstantin Meskhidze (A) wrote:
>> 
>> 
>> 2/10/2023 8:39 PM, Mickaël Salaün пишет:
>>>
>>> On 16/01/2023 09:58, Konstantin Meskhidze wrote:
>>>> This commit adds network rules support in the ruleset management
>>>> helpers and the landlock_create_ruleset syscall.
>>>> Refactor user space API to support network actions. Add new network
>>>> access flags, network rule and network attributes. Increment Landlock
>>>> ABI version. Expand access_masks_t to u32 to be sure network access
>>>> rights can be stored. Implement socket_bind() and socket_connect()
>>>> LSM hooks, which enable to restrict TCP socket binding and connection
>>>> to specific ports.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v8:
>>>> * Squashes commits.
>>>> * Refactors commit message.
>>>> * Changes UAPI port field to __be16.
>>>> * Changes logic of bind/connect hooks with AF_UNSPEC families.
>>>> * Adds address length checking.
>>>> * Minor fixes.
>>>>
>>>> Changes since v7:
>>>> * Squashes commits.
>>>> * Increments ABI version to 4.
>>>> * Refactors commit message.
>>>> * Minor fixes.
>>>>
>>>> Changes since v6:
>>>> * Renames landlock_set_net_access_mask() to landlock_add_net_access_mask()
>>>>     because it OR values.
>>>> * Makes landlock_add_net_access_mask() more resilient incorrect values.
>>>> * Refactors landlock_get_net_access_mask().
>>>> * Renames LANDLOCK_MASK_SHIFT_NET to LANDLOCK_SHIFT_ACCESS_NET and use
>>>>     LANDLOCK_NUM_ACCESS_FS as value.
>>>> * Updates access_masks_t to u32 to support network access actions.
>>>> * Refactors landlock internal functions to support network actions with
>>>>     landlock_key/key_type/id types.
>>>>
>>>> Changes since v5:
>>>> * Gets rid of partial revert from landlock_add_rule
>>>> syscall.
>>>> * Formats code with clang-format-14.
>>>>
>>>> Changes since v4:
>>>> * Refactors landlock_create_ruleset() - splits ruleset and
>>>> masks checks.
>>>> * Refactors landlock_create_ruleset() and landlock mask
>>>> setters/getters to support two rule types.
>>>> * Refactors landlock_add_rule syscall add_rule_path_beneath
>>>> function by factoring out get_ruleset_from_fd() and
>>>> landlock_put_ruleset().
>>>>
>>>> Changes since v3:
>>>> * Splits commit.
>>>> * Adds network rule support for internal landlock functions.
>>>> * Adds set_mask and get_mask for network.
>>>> * Adds rb_root root_net_port.
>>>
>>> [...]
>>>
>>>> +static int check_socket_access(const struct landlock_ruleset *const domain,
>>>> +			       struct sockaddr *address, __be16 port,
>>>> +			       access_mask_t access_request)
>>>> +{
>>>> +	bool allowed = false;
>>>> +	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>>>> +	const struct landlock_rule *rule;
>>>> +	access_mask_t handled_access;
>>>> +	const struct landlock_id id = {
>>>> +		.key.data = port,
>>>> +		.type = LANDLOCK_KEY_NET_PORT,
>>>> +	};
>>>> +
>>>> +	if (WARN_ON_ONCE(!domain))
>>>> +		return 0;
>>>> +	if (WARN_ON_ONCE(domain->num_layers < 1))
>>>> +		return -EACCES;
>>>> +
>>>> +	switch (address->sa_family) {
>>>> +	case AF_UNSPEC:
>>>> +		/*
>>>> +		 * Connecting to an address with AF_UNSPEC dissolves the TCP
>>>> +		 * association, which have the same effect as closing the
>>>> +		 * connection while retaining the socket object (i.e., the file
>>>> +		 * descriptor).  As for dropping privileges, closing
>>>> +		 * connections is always allowed.
>>>> +		 */
>>>> +		if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>>> +			return 0;
>>>> +
>>>> +		/*
>>>> +		 * For compatibility reason, accept AF_UNSPEC for bind
>>>> +		 * accesses (mapped to AF_INET) only if the address is
>>>> +		 * INADDR_ANY (cf. __inet_bind).  Checking the address is
>>>> +		 * required to not wrongfully return -EACCES instead of
>>>> +		 * -EAFNOSUPPORT.
>>>> +		 */
>>>> +		if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
>>>> +			const struct sockaddr_in *const sockaddr =
>>>> +				(struct sockaddr_in *)address;
>>>> +
>>>> +			if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
>>>> +				return -EAFNOSUPPORT;
>>>> +		}
>>>> +
>>>> +		fallthrough;
>>>> +	case AF_INET:
>>>> +#if IS_ENABLED(CONFIG_IPV6)
>>>> +	case AF_INET6:
>>>> +#endif
>>>> +		rule = landlock_find_rule(domain, id);
>>>> +		handled_access = landlock_init_layer_masks(
>>>> +			domain, access_request, &layer_masks,
>>>> +			LANDLOCK_KEY_NET_PORT);
>>>> +		allowed = landlock_unmask_layers(rule, handled_access,
>>>> +						 &layer_masks,
>>>> +						 ARRAY_SIZE(layer_masks));
>>>> +
>>>> +		fallthrough;
>>>
>>> You can remove this fallthrough.
>>>
>>>
>>>> +	}
>>>> +	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 sockaddr->sin_port;
>>>> +	}
>>>> +#if IS_ENABLED(CONFIG_IPV6)
>>>> +	case AF_INET6: {
>>>> +		const struct sockaddr_in6 *const sockaddr_ip6 =
>>>> +			(struct sockaddr_in6 *)address;
>>>> +		return sockaddr_ip6->sin6_port;
>>>> +	}
>>>> +#endif
>>>> +	}
>>>> +	WARN_ON_ONCE(1);
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int hook_socket_bind(struct socket *sock, struct sockaddr *address,
>>>> +			    int addrlen)
>>>> +{
>>>> +	int ret;
>>>> +	const struct landlock_ruleset *const dom =
>>>> +		landlock_get_current_domain();
>>>
>>> landlock_get_current_domain() should only be called by a
>>> get_current_net_domain() wrapper that checks if the current domain
>>> handles network accesses. See get_current_fs_domain() in patch 2/12.
>> 
>>     Hi Mickaël.
>>     I have question:
>> 
>>     static access_mask_t
>> get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
>> {
>> 	access_mask_t access_dom = 0;
>> 	size_t layer_level;
>> 
>> 	for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
>> 		access_dom |=
>> 			landlock_get_raw_fs_access_mask(domain, layer_level);
>> 	return access_dom & LANDLOCK_MASK_ACCESS_FS;
>> }
>> 
>> landlock_get_raw_fs_access_mask() function is already mask by
>> LANDLOCK_MASK_ACCESS_FS. We could get rid of access_dom masking.
>> What do you think?
> 
> Right, you can remove this extra mask.
> 
> While reading the code again I found that it would be better to rename
> ACCESS_FS_INITIALLY_DENIED to LANDLOCK_ACCESS_FS_INITIALLY_DENIED.

   Ok. Will be renamed..
> .

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-03-14 13:28     ` Mickaël Salaün
@ 2023-06-26 15:29       ` Mickaël Salaün
  2023-06-28  2:33         ` Jeff Xu
  2023-06-28  8:44         ` Günther Noack
  0 siblings, 2 replies; 74+ messages in thread
From: Mickaël Salaün @ 2023-06-26 15:29 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), Günther Noack
  Cc: willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

Reviving Günther's suggestion to deny a set of network protocols:

On 14/03/2023 14:28, Mickaël Salaün wrote:
> 
> On 13/03/2023 18:16, Konstantin Meskhidze (A) wrote:
>>
>>
>> 2/24/2023 1:17 AM, Günther Noack пишет:

[...]

>>>
>>> * Given the list of obscure network protocols listed in the socket(2)
>>>      man page, I find it slightly weird to have rules for the use of TCP,
>>>      but to leave less prominent protocols unrestricted.
>>>
>>>      For example, a process with an enabled Landlock network ruleset may
>>>      connect only to certain TCP ports, but at the same time it can
>>>      happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?
>>
>>         We also have started a discussion about UDP protocol, but it's
>> more complicated since UDP sockets does not establish connections
>> between each other. There is a performance problem on the first place here.
>>
>> I'm not familiar with Bluetooth/CAN bus/DECnet/IPX but let's discuss it.
>> Any ideas here?
> 
> All these protocols should be handled one way or another someday. ;)
> 
> 
>>
>>>
>>>      I'm mentioning these more obscure protocols, because I doubt that
>>>      Landlock will grow more sophisticated support for them anytime soon,
>>>      so maybe the best option would be to just make it possible to
>>>      disable these?  Is that also part of the plan?
>>>
>>>      (I think there would be a lot of value in restricting network
>>>      access, even when it's done very broadly.  There are many programs
>>>      that don't need network at all, and among those that do need
>>>      network, most only require IP networking.
> 
> Indeed, protocols that nobody care to make Landlock supports them will
> probably not have fine-grained control. We could extend the ruleset
> attributes to disable the use (i.e. not only the creation of new related
> sockets/resources) of network protocol families, in a way that would
> make sandboxes simulate a kernel without such protocol support. In this
> case, this should be an allowed list of protocols, and everything not in
> that list should be denied. This approach could be used for other kernel
> features (unrelated to network).
> 
> 
>>>
>>>      Btw, the argument for more broad disabling of network access was
>>>      already made at https://cr.yp.to/unix/disablenetwork.html in the
>>>      past.)
> 
> This is interesting but scoped to a single use case. As specified at the
> beginning of this linked page, there must be exceptions, not only with
> AF_UNIX but also for (the newer) AF_VSOCK, and probably future ones.
> This is why I don't think a binary approach is a good one for Linux.
> Users should be able to specify what they need, and block the rest.

Here is a design to be able to only allow a set of network protocols and 
deny everything else. This would be complementary to Konstantin's patch 
series which addresses fine-grained access control.

First, I want to remind that Landlock follows an allowed list approach 
with a set of (growing) supported actions (for compatibility reasons), 
which is kind of an allow-list-on-a-deny-list. But with this proposal, 
we want to be able to deny everything, which means: supported, not 
supported, known and unknown protocols.

We could add a new "handled_access_socket" field to the landlock_ruleset 
struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.

If this field is set, users could add a new type of rules:
struct landlock_socket_attr {
     __u64 allowed_access;
     int domain; // see socket(2)
     int type; // see socket(2)
}

The allowed_access field would only contain 
LANDLOCK_ACCESS_SOCKET_CREATE at first, but it could grow with other 
actions (which cannot be handled with seccomp):
- use: walk through all opened FDs and mark them as allowed or denied
- receive: hook on received FDs
- send: hook on sent FDs

We might also use the same approach for non-socket objects that can be 
identified with some meaningful properties.

What do you think?

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-26 15:29       ` [PATCH v9 00/12] Network support for Landlock - allowed list of protocols Mickaël Salaün
@ 2023-06-28  2:33         ` Jeff Xu
  2023-06-28 19:03           ` Mickaël Salaün
  2023-06-28  8:44         ` Günther Noack
  1 sibling, 1 reply; 74+ messages in thread
From: Jeff Xu @ 2023-06-28  2:33 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

On Mon, Jun 26, 2023 at 8:29 AM Mickaël Salaün <mic@digikod.net> wrote:
>
> Reviving Günther's suggestion to deny a set of network protocols:
>
> On 14/03/2023 14:28, Mickaël Salaün wrote:
> >
> > On 13/03/2023 18:16, Konstantin Meskhidze (A) wrote:
> >>
> >>
> >> 2/24/2023 1:17 AM, Günther Noack пишет:
>
> [...]
>
> >>>
> >>> * Given the list of obscure network protocols listed in the socket(2)
> >>>      man page, I find it slightly weird to have rules for the use of TCP,
> >>>      but to leave less prominent protocols unrestricted.
> >>>
> >>>      For example, a process with an enabled Landlock network ruleset may
> >>>      connect only to certain TCP ports, but at the same time it can
> >>>      happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?
> >>
> >>         We also have started a discussion about UDP protocol, but it's
> >> more complicated since UDP sockets does not establish connections
> >> between each other. There is a performance problem on the first place here.
> >>
> >> I'm not familiar with Bluetooth/CAN bus/DECnet/IPX but let's discuss it.
> >> Any ideas here?
> >
> > All these protocols should be handled one way or another someday. ;)
> >
> >
> >>
> >>>
> >>>      I'm mentioning these more obscure protocols, because I doubt that
> >>>      Landlock will grow more sophisticated support for them anytime soon,
> >>>      so maybe the best option would be to just make it possible to
> >>>      disable these?  Is that also part of the plan?
> >>>
> >>>      (I think there would be a lot of value in restricting network
> >>>      access, even when it's done very broadly.  There are many programs
> >>>      that don't need network at all, and among those that do need
> >>>      network, most only require IP networking.
> >
> > Indeed, protocols that nobody care to make Landlock supports them will
> > probably not have fine-grained control. We could extend the ruleset
> > attributes to disable the use (i.e. not only the creation of new related
> > sockets/resources) of network protocol families, in a way that would
> > make sandboxes simulate a kernel without such protocol support. In this
> > case, this should be an allowed list of protocols, and everything not in
> > that list should be denied. This approach could be used for other kernel
> > features (unrelated to network).
> >
> >
> >>>
> >>>      Btw, the argument for more broad disabling of network access was
> >>>      already made at https://cr.yp.to/unix/disablenetwork.html in the
> >>>      past.)
> >
> > This is interesting but scoped to a single use case. As specified at the
> > beginning of this linked page, there must be exceptions, not only with
> > AF_UNIX but also for (the newer) AF_VSOCK, and probably future ones.
> > This is why I don't think a binary approach is a good one for Linux.
> > Users should be able to specify what they need, and block the rest.
>
> Here is a design to be able to only allow a set of network protocols and
> deny everything else. This would be complementary to Konstantin's patch
> series which addresses fine-grained access control.
>
> First, I want to remind that Landlock follows an allowed list approach
> with a set of (growing) supported actions (for compatibility reasons),
> which is kind of an allow-list-on-a-deny-list. But with this proposal,
> we want to be able to deny everything, which means: supported, not
> supported, known and unknown protocols.
>
I think this makes sense.  ChomeOS can use it at the process level:
disable network, allow VSOCK only, allow TCP only, etc.

> We could add a new "handled_access_socket" field to the landlock_ruleset
> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>
> If this field is set, users could add a new type of rules:
> struct landlock_socket_attr {
>      __u64 allowed_access;
>      int domain; // see socket(2)
>      int type; // see socket(2)
> }
>
Do you want to add "int protocol" ? which is the third parameter of socket(2)
According to protocols(5), the protocols are defined in:
https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml

It is part of IPv4/IPV6 header:
https://www.rfc-editor.org/rfc/rfc791.html#section-3.1
https://www.rfc-editor.org/rfc/rfc8200.html#section-3

> The allowed_access field would only contain
> LANDLOCK_ACCESS_SOCKET_CREATE at first, but it could grow with other
> actions (which cannot be handled with seccomp):
> - use: walk through all opened FDs and mark them as allowed or denied
> - receive: hook on received FDs
> - send: hook on sent FDs
>
also bind, connect, accept.

> We might also use the same approach for non-socket objects that can be
> identified with some meaningful properties.
>
> What do you think?

-Jeff

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-26 15:29       ` [PATCH v9 00/12] Network support for Landlock - allowed list of protocols Mickaël Salaün
  2023-06-28  2:33         ` Jeff Xu
@ 2023-06-28  8:44         ` Günther Noack
  2023-06-28 17:03           ` Jeff Xu
  2023-06-28 19:07           ` Mickaël Salaün
  1 sibling, 2 replies; 74+ messages in thread
From: Günther Noack @ 2023-06-28  8:44 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

Hello!

On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
> Here is a design to be able to only allow a set of network protocols and
> deny everything else. This would be complementary to Konstantin's patch
> series which addresses fine-grained access control.
> 
> First, I want to remind that Landlock follows an allowed list approach with
> a set of (growing) supported actions (for compatibility reasons), which is
> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
> able to deny everything, which means: supported, not supported, known and
> unknown protocols.
> 
> We could add a new "handled_access_socket" field to the landlock_ruleset
> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
> 
> If this field is set, users could add a new type of rules:
> struct landlock_socket_attr {
>     __u64 allowed_access;
>     int domain; // see socket(2)
>     int type; // see socket(2)
> }
> 
> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
> first, but it could grow with other actions (which cannot be handled with
> seccomp):
> - use: walk through all opened FDs and mark them as allowed or denied
> - receive: hook on received FDs
> - send: hook on sent FDs
> 
> We might also use the same approach for non-socket objects that can be
> identified with some meaningful properties.
> 
> What do you think?

This sounds like a good plan to me - it would make it possible to restrict new
socket creation using protocols that were not intended to be used, and I also
think it would fit the Landlock model nicely.

Small remark on the side: The security_socket_create() hook does not only get
invoked as a result of socket(2), but also as a part of accept(2) - so this
approach might already prevent new connections very effectively.

Spelling out some scenarios, so that we are sure that we are on the same page:

A)

A program that does not need networking could specify a ruleset where
LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.

B)

A program that runs a TCP server could specify a ruleset where
LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:

  /* From Konstantin's patch set */
  struct landlock_net_service_attr bind_attr = {
    .allowed_access = LANDLOCK_NET_BIND_TCP,
    .port = 8080,
  };

  /* From Mickaël's proposal */
  struct landlock_socket_attr sock_inet_attr = {
    .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
    .domain = AF_INET,
    .type = SOCK_STREAM,
  }

  struct landlock_socket_attr sock_inet6_attr = {
    .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
    .domain = AF_INET6,
     .type = SOCK_STREAM,
  }

That should then be enough to bind and listen on ports, whereas outgoing
connections with TCP and anything using other network protocols would not be
permitted.

(Alternatively, it could bind() the socket early, *then enable Landlock* and
leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
IPv6, so that listen() and accept() work on the already-bound socket.)

Overall, this sounds like an excellent approach to me. 👍

—Günther

-- 
Sent using Mutt 🐕 Woof Woof

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-28  8:44         ` Günther Noack
@ 2023-06-28 17:03           ` Jeff Xu
  2023-06-28 19:29             ` Mickaël Salaün
  2023-06-28 19:07           ` Mickaël Salaün
  1 sibling, 1 reply; 74+ messages in thread
From: Jeff Xu @ 2023-06-28 17:03 UTC (permalink / raw)
  To: Günther Noack
  Cc: Mickaël Salaün, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

Hello,

Thanks for writing up the example for an incoming TCP connection ! It
helps with the context.

Since I'm late to this thread, one thing I want to ask:  all the APIs
proposed so far are at the process level, we don't have any API that
applies restriction to socket fd itself, right ? this is what I
thought, but I would like to get confirmation.

On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
>
> Hello!
>
> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
> > Here is a design to be able to only allow a set of network protocols and
> > deny everything else. This would be complementary to Konstantin's patch
> > series which addresses fine-grained access control.
> >
> > First, I want to remind that Landlock follows an allowed list approach with
> > a set of (growing) supported actions (for compatibility reasons), which is
> > kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
> > able to deny everything, which means: supported, not supported, known and
> > unknown protocols.
> >
> > We could add a new "handled_access_socket" field to the landlock_ruleset
> > struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
> >
> > If this field is set, users could add a new type of rules:
> > struct landlock_socket_attr {
> >     __u64 allowed_access;
> >     int domain; // see socket(2)
> >     int type; // see socket(2)
> > }
> >
> > The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
> > first, but it could grow with other actions (which cannot be handled with
> > seccomp):
> > - use: walk through all opened FDs and mark them as allowed or denied
> > - receive: hook on received FDs
> > - send: hook on sent FDs
> >
> > We might also use the same approach for non-socket objects that can be
> > identified with some meaningful properties.
> >
> > What do you think?
>
> This sounds like a good plan to me - it would make it possible to restrict new
> socket creation using protocols that were not intended to be used, and I also
> think it would fit the Landlock model nicely.
>
> Small remark on the side: The security_socket_create() hook does not only get
> invoked as a result of socket(2), but also as a part of accept(2) - so this
> approach might already prevent new connections very effectively.
>
That is an interesting aspect that might be worth discussing more.
seccomp is per syscall, landlock doesn't necessarily follow the same,
another design is to add more logic in Landlock, e.g.
LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
calls (socket/bind/listen/accept/connect). App dev might feel it is
easier to use.

> Spelling out some scenarios, so that we are sure that we are on the same page:
>
> A)
>
> A program that does not need networking could specify a ruleset where
> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
>
> B)
>
> A program that runs a TCP server could specify a ruleset where
> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
>
>   /* From Konstantin's patch set */
>   struct landlock_net_service_attr bind_attr = {
>     .allowed_access = LANDLOCK_NET_BIND_TCP,
>     .port = 8080,
>   };
>
>   /* From Mickaël's proposal */
>   struct landlock_socket_attr sock_inet_attr = {
>     .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>     .domain = AF_INET,
>     .type = SOCK_STREAM,
>   }
>
>   struct landlock_socket_attr sock_inet6_attr = {
>     .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>     .domain = AF_INET6,
>      .type = SOCK_STREAM,
>   }
>
> That should then be enough to bind and listen on ports, whereas outgoing
> connections with TCP and anything using other network protocols would not be
> permitted.
>
TCP server is an interesting case. From a security perspective, a
process cares if it is acting as a server or client in TCP, a server
might only want to accept an incoming TCP connection, never initiate
an outgoing TCP connection, and a client is the opposite.

Processes can restrict outgoing/incoming TCP connection by seccomp for
accept(2) or connect(2),  though I feel Landlock can do this more
naturally for app dev, and at per-protocol level.  seccomp doesn't
provide per-protocol granularity.

For bind(2), iirc, it can be used for a server to assign dst port of
incoming TCP connection, also by a client to assign a src port of an
outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
cases, right ? this might not be a problem, just something to keep
note.

> (Alternatively, it could bind() the socket early, *then enable Landlock* and
> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
> IPv6, so that listen() and accept() work on the already-bound socket.)
>
For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
so dev is fully aware it is not just applied to socket create.

> Overall, this sounds like an excellent approach to me. 👍
>
> —Günther
>
> --
> Sent using Mutt 🐕 Woof Woof

-Jeff

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-28  2:33         ` Jeff Xu
@ 2023-06-28 19:03           ` Mickaël Salaün
  2023-06-28 21:56             ` Jeff Xu
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-06-28 19:03 UTC (permalink / raw)
  To: Jeff Xu
  Cc: Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov


On 28/06/2023 04:33, Jeff Xu wrote:
> On Mon, Jun 26, 2023 at 8:29 AM Mickaël Salaün <mic@digikod.net> wrote:
>>
>> Reviving Günther's suggestion to deny a set of network protocols:
>>
>> On 14/03/2023 14:28, Mickaël Salaün wrote:
>>>
>>> On 13/03/2023 18:16, Konstantin Meskhidze (A) wrote:
>>>>
>>>>
>>>> 2/24/2023 1:17 AM, Günther Noack пишет:
>>
>> [...]
>>
>>>>>
>>>>> * Given the list of obscure network protocols listed in the socket(2)
>>>>>       man page, I find it slightly weird to have rules for the use of TCP,
>>>>>       but to leave less prominent protocols unrestricted.
>>>>>
>>>>>       For example, a process with an enabled Landlock network ruleset may
>>>>>       connect only to certain TCP ports, but at the same time it can
>>>>>       happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?
>>>>
>>>>          We also have started a discussion about UDP protocol, but it's
>>>> more complicated since UDP sockets does not establish connections
>>>> between each other. There is a performance problem on the first place here.
>>>>
>>>> I'm not familiar with Bluetooth/CAN bus/DECnet/IPX but let's discuss it.
>>>> Any ideas here?
>>>
>>> All these protocols should be handled one way or another someday. ;)
>>>
>>>
>>>>
>>>>>
>>>>>       I'm mentioning these more obscure protocols, because I doubt that
>>>>>       Landlock will grow more sophisticated support for them anytime soon,
>>>>>       so maybe the best option would be to just make it possible to
>>>>>       disable these?  Is that also part of the plan?
>>>>>
>>>>>       (I think there would be a lot of value in restricting network
>>>>>       access, even when it's done very broadly.  There are many programs
>>>>>       that don't need network at all, and among those that do need
>>>>>       network, most only require IP networking.
>>>
>>> Indeed, protocols that nobody care to make Landlock supports them will
>>> probably not have fine-grained control. We could extend the ruleset
>>> attributes to disable the use (i.e. not only the creation of new related
>>> sockets/resources) of network protocol families, in a way that would
>>> make sandboxes simulate a kernel without such protocol support. In this
>>> case, this should be an allowed list of protocols, and everything not in
>>> that list should be denied. This approach could be used for other kernel
>>> features (unrelated to network).
>>>
>>>
>>>>>
>>>>>       Btw, the argument for more broad disabling of network access was
>>>>>       already made at https://cr.yp.to/unix/disablenetwork.html in the
>>>>>       past.)
>>>
>>> This is interesting but scoped to a single use case. As specified at the
>>> beginning of this linked page, there must be exceptions, not only with
>>> AF_UNIX but also for (the newer) AF_VSOCK, and probably future ones.
>>> This is why I don't think a binary approach is a good one for Linux.
>>> Users should be able to specify what they need, and block the rest.
>>
>> Here is a design to be able to only allow a set of network protocols and
>> deny everything else. This would be complementary to Konstantin's patch
>> series which addresses fine-grained access control.
>>
>> First, I want to remind that Landlock follows an allowed list approach
>> with a set of (growing) supported actions (for compatibility reasons),
>> which is kind of an allow-list-on-a-deny-list. But with this proposal,
>> we want to be able to deny everything, which means: supported, not
>> supported, known and unknown protocols.
>>
> I think this makes sense.  ChomeOS can use it at the process level:
> disable network, allow VSOCK only, allow TCP only, etc.
> 
>> We could add a new "handled_access_socket" field to the landlock_ruleset
>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>>
>> If this field is set, users could add a new type of rules:
>> struct landlock_socket_attr {
>>       __u64 allowed_access;
>>       int domain; // see socket(2)

I guess "family" would also make sense. It's the name used in the 
kernel, the "AF" prefixes, and address_families(7). I'm not sure why 
"domain" was chosen for socket(2).


>>       int type; // see socket(2)
>> }
>>
> Do you want to add "int protocol" ? which is the third parameter of socket(2)
> According to protocols(5), the protocols are defined in:
> https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
> 
> It is part of IPv4/IPV6 header:
> https://www.rfc-editor.org/rfc/rfc791.html#section-3.1
> https://www.rfc-editor.org/rfc/rfc8200.html#section-3

I understand the rationale but I'm not sure if this would be useful. Do 
you have use cases?


> 
>> The allowed_access field would only contain
>> LANDLOCK_ACCESS_SOCKET_CREATE at first, but it could grow with other
>> actions (which cannot be handled with seccomp):
>> - use: walk through all opened FDs and mark them as allowed or denied
>> - receive: hook on received FDs
>> - send: hook on sent FDs
>>
> also bind, connect, accept.

I don't think "accept" would be useful, and I'm not sure if "bind" and 
"connect" would not be redundant with LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP
Bind and connect for a datagram socket is optional, so this might lead 
to a false sense of security. If we want to support protocols other than 
TCP to restrict bind/connect, then they deserve to be controlled 
according to a port (or similar).

> 
>> We might also use the same approach for non-socket objects that can be
>> identified with some meaningful properties.
>>
>> What do you think?
> 
> -Jeff

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-28  8:44         ` Günther Noack
  2023-06-28 17:03           ` Jeff Xu
@ 2023-06-28 19:07           ` Mickaël Salaün
  1 sibling, 0 replies; 74+ messages in thread
From: Mickaël Salaün @ 2023-06-28 19:07 UTC (permalink / raw)
  To: Günther Noack
  Cc: Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov


On 28/06/2023 10:44, Günther Noack wrote:
> Hello!
> 
> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
>> Here is a design to be able to only allow a set of network protocols and
>> deny everything else. This would be complementary to Konstantin's patch
>> series which addresses fine-grained access control.
>>
>> First, I want to remind that Landlock follows an allowed list approach with
>> a set of (growing) supported actions (for compatibility reasons), which is
>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
>> able to deny everything, which means: supported, not supported, known and
>> unknown protocols.
>>
>> We could add a new "handled_access_socket" field to the landlock_ruleset
>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>>
>> If this field is set, users could add a new type of rules:
>> struct landlock_socket_attr {
>>      __u64 allowed_access;
>>      int domain; // see socket(2)
>>      int type; // see socket(2)
>> }
>>
>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
>> first, but it could grow with other actions (which cannot be handled with
>> seccomp):
>> - use: walk through all opened FDs and mark them as allowed or denied
>> - receive: hook on received FDs
>> - send: hook on sent FDs
>>
>> We might also use the same approach for non-socket objects that can be
>> identified with some meaningful properties.
>>
>> What do you think?
> 
> This sounds like a good plan to me - it would make it possible to restrict new
> socket creation using protocols that were not intended to be used, and I also
> think it would fit the Landlock model nicely.
> 
> Small remark on the side: The security_socket_create() hook does not only get
> invoked as a result of socket(2), but also as a part of accept(2) - so this
> approach might already prevent new connections very effectively.

Indeed. We could also differentiate socket(2) from accept(2) with a 
dedicated LANDLOCK_ACCESS_SOCKET_ACCEPT right. This would enable to 
create a bind socket, sandbox the process and deny new socket(2) calls, 
but still allows to call accept(2) and receive new connections.

BTW, unix socket path opening should be considered too.

> 
> Spelling out some scenarios, so that we are sure that we are on the same page:
> 
> A)
> 
> A program that does not need networking could specify a ruleset where
> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.

This is correct, except if the process receive a socket FD or open a 
unix socket path.


> 
> B)
> 
> A program that runs a TCP server could specify a ruleset where
> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and

s/LANDLOCK_NET_CONNECT_TCP/LANDLOCK_ACCESS_NET_CONNECT_TCP/

> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
> 
>    /* From Konstantin's patch set */
>    struct landlock_net_service_attr bind_attr = {
>      .allowed_access = LANDLOCK_NET_BIND_TCP,
>      .port = 8080,
>    };
> 
>    /* From Mickaël's proposal */
>    struct landlock_socket_attr sock_inet_attr = {
>      .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>      .domain = AF_INET,
>      .type = SOCK_STREAM,
>    }
> 
>    struct landlock_socket_attr sock_inet6_attr = {
>      .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>      .domain = AF_INET6,
>       .type = SOCK_STREAM,
>    }
> 
> That should then be enough to bind and listen on ports, whereas outgoing
> connections with TCP and anything using other network protocols would not be
> permitted.
> 
> (Alternatively, it could bind() the socket early, *then enable Landlock* and
> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
> IPv6, so that listen() and accept() work on the already-bound socket.)

correct

> 
> Overall, this sounds like an excellent approach to me. 👍
> 
> —Günther
> 

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-28 17:03           ` Jeff Xu
@ 2023-06-28 19:29             ` Mickaël Salaün
  2023-06-29  3:18               ` Jeff Xu
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-06-28 19:29 UTC (permalink / raw)
  To: Jeff Xu, Günther Noack
  Cc: Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov


On 28/06/2023 19:03, Jeff Xu wrote:
> Hello,
> 
> Thanks for writing up the example for an incoming TCP connection ! It
> helps with the context.
> 
> Since I'm late to this thread, one thing I want to ask:  all the APIs
> proposed so far are at the process level, we don't have any API that
> applies restriction to socket fd itself, right ? this is what I
> thought, but I would like to get confirmation.

Restriction are applied to actions, not to already existing/opened FDs. 
We could add a way to restrict opened FDs, but I don't think this is the 
right approach because sandboxing is a deliberate action from a process, 
and it should already take care of its FDs.


> 
> On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
>>
>> Hello!
>>
>> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
>>> Here is a design to be able to only allow a set of network protocols and
>>> deny everything else. This would be complementary to Konstantin's patch
>>> series which addresses fine-grained access control.
>>>
>>> First, I want to remind that Landlock follows an allowed list approach with
>>> a set of (growing) supported actions (for compatibility reasons), which is
>>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
>>> able to deny everything, which means: supported, not supported, known and
>>> unknown protocols.
>>>
>>> We could add a new "handled_access_socket" field to the landlock_ruleset
>>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>>>
>>> If this field is set, users could add a new type of rules:
>>> struct landlock_socket_attr {
>>>      __u64 allowed_access;
>>>      int domain; // see socket(2)
>>>      int type; // see socket(2)
>>> }
>>>
>>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
>>> first, but it could grow with other actions (which cannot be handled with
>>> seccomp):
>>> - use: walk through all opened FDs and mark them as allowed or denied
>>> - receive: hook on received FDs
>>> - send: hook on sent FDs
>>>
>>> We might also use the same approach for non-socket objects that can be
>>> identified with some meaningful properties.
>>>
>>> What do you think?
>>
>> This sounds like a good plan to me - it would make it possible to restrict new
>> socket creation using protocols that were not intended to be used, and I also
>> think it would fit the Landlock model nicely.
>>
>> Small remark on the side: The security_socket_create() hook does not only get
>> invoked as a result of socket(2), but also as a part of accept(2) - so this
>> approach might already prevent new connections very effectively.
>>
> That is an interesting aspect that might be worth discussing more.
> seccomp is per syscall, landlock doesn't necessarily follow the same,
> another design is to add more logic in Landlock, e.g.
> LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
> calls (socket/bind/listen/accept/connect). App dev might feel it is
> easier to use.

seccomp restricts the use of the syscall interface, whereas Landlock 
restricts the use of kernel objects (i.e. the semantic).

We need to find a good tradeoff between a lot of access rights and a few 
grouping different actions. This should make sense from a developer 
point of view according to its knowledge and use of the kernel 
interfaces (potential wrapped with high level libraries), but also to 
the semantic of the sandbox and the security guarantees we want to provide.

We should also keep in mind that high level Landlock libraries can take 
care of potential coarse-grained use of restrictions.


> 
>> Spelling out some scenarios, so that we are sure that we are on the same page:
>>
>> A)
>>
>> A program that does not need networking could specify a ruleset where
>> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
>>
>> B)
>>
>> A program that runs a TCP server could specify a ruleset where
>> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
>> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
>>
>>    /* From Konstantin's patch set */
>>    struct landlock_net_service_attr bind_attr = {
>>      .allowed_access = LANDLOCK_NET_BIND_TCP,
>>      .port = 8080,
>>    };
>>
>>    /* From Mickaël's proposal */
>>    struct landlock_socket_attr sock_inet_attr = {
>>      .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>      .domain = AF_INET,
>>      .type = SOCK_STREAM,
>>    }
>>
>>    struct landlock_socket_attr sock_inet6_attr = {
>>      .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>      .domain = AF_INET6,
>>       .type = SOCK_STREAM,
>>    }
>>
>> That should then be enough to bind and listen on ports, whereas outgoing
>> connections with TCP and anything using other network protocols would not be
>> permitted.
>>
> TCP server is an interesting case. From a security perspective, a
> process cares if it is acting as a server or client in TCP, a server
> might only want to accept an incoming TCP connection, never initiate
> an outgoing TCP connection, and a client is the opposite.
> 
> Processes can restrict outgoing/incoming TCP connection by seccomp for
> accept(2) or connect(2),  though I feel Landlock can do this more
> naturally for app dev, and at per-protocol level.  seccomp doesn't
> provide per-protocol granularity.

Right, seccomp cannot filter TCP ports.

> 
> For bind(2), iirc, it can be used for a server to assign dst port of
> incoming TCP connection, also by a client to assign a src port of an
> outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
> cases, right ? this might not be a problem, just something to keep
> note.

Good point. I think it is in line with the rule definition: to allow to 
bind on a specific port. However, if clients want to set the source port 
to a (legitimate) value, then that would be an issue because we cannot 
allow a whole range of ports (e.g., >= 1024). I'm not sure if this 
practice would be deemed "legitimate" though. Do you know client 
applications using bind?

Konstantin, we should have a test for this case anyway.


> 
>> (Alternatively, it could bind() the socket early, *then enable Landlock* and
>> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
>> IPv6, so that listen() and accept() work on the already-bound socket.)
>>
> For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
> so dev is fully aware it is not just applied to socket create.

I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does 
PROTOCOL mean?

> 
>> Overall, this sounds like an excellent approach to me. 👍
>>
>> —Günther
>>
>> --
>> Sent using Mutt 🐕 Woof Woof
> 
> -Jeff

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-28 19:03           ` Mickaël Salaün
@ 2023-06-28 21:56             ` Jeff Xu
  0 siblings, 0 replies; 74+ messages in thread
From: Jeff Xu @ 2023-06-28 21:56 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

On Wed, Jun 28, 2023 at 12:03 PM Mickaël Salaün <mic@digikod.net> wrote:
>
>
> On 28/06/2023 04:33, Jeff Xu wrote:
> > On Mon, Jun 26, 2023 at 8:29 AM Mickaël Salaün <mic@digikod.net> wrote:
> >>
> >> Reviving Günther's suggestion to deny a set of network protocols:
> >>
> >> On 14/03/2023 14:28, Mickaël Salaün wrote:
> >>>
> >>> On 13/03/2023 18:16, Konstantin Meskhidze (A) wrote:
> >>>>
> >>>>
> >>>> 2/24/2023 1:17 AM, Günther Noack пишет:
> >>
> >> [...]
> >>
> >>>>>
> >>>>> * Given the list of obscure network protocols listed in the socket(2)
> >>>>>       man page, I find it slightly weird to have rules for the use of TCP,
> >>>>>       but to leave less prominent protocols unrestricted.
> >>>>>
> >>>>>       For example, a process with an enabled Landlock network ruleset may
> >>>>>       connect only to certain TCP ports, but at the same time it can
> >>>>>       happily use Bluetooth/CAN bus/DECnet/IPX or other protocols?
> >>>>
> >>>>          We also have started a discussion about UDP protocol, but it's
> >>>> more complicated since UDP sockets does not establish connections
> >>>> between each other. There is a performance problem on the first place here.
> >>>>
> >>>> I'm not familiar with Bluetooth/CAN bus/DECnet/IPX but let's discuss it.
> >>>> Any ideas here?
> >>>
> >>> All these protocols should be handled one way or another someday. ;)
> >>>
> >>>
> >>>>
> >>>>>
> >>>>>       I'm mentioning these more obscure protocols, because I doubt that
> >>>>>       Landlock will grow more sophisticated support for them anytime soon,
> >>>>>       so maybe the best option would be to just make it possible to
> >>>>>       disable these?  Is that also part of the plan?
> >>>>>
> >>>>>       (I think there would be a lot of value in restricting network
> >>>>>       access, even when it's done very broadly.  There are many programs
> >>>>>       that don't need network at all, and among those that do need
> >>>>>       network, most only require IP networking.
> >>>
> >>> Indeed, protocols that nobody care to make Landlock supports them will
> >>> probably not have fine-grained control. We could extend the ruleset
> >>> attributes to disable the use (i.e. not only the creation of new related
> >>> sockets/resources) of network protocol families, in a way that would
> >>> make sandboxes simulate a kernel without such protocol support. In this
> >>> case, this should be an allowed list of protocols, and everything not in
> >>> that list should be denied. This approach could be used for other kernel
> >>> features (unrelated to network).
> >>>
> >>>
> >>>>>
> >>>>>       Btw, the argument for more broad disabling of network access was
> >>>>>       already made at https://cr.yp.to/unix/disablenetwork.html in the
> >>>>>       past.)
> >>>
> >>> This is interesting but scoped to a single use case. As specified at the
> >>> beginning of this linked page, there must be exceptions, not only with
> >>> AF_UNIX but also for (the newer) AF_VSOCK, and probably future ones.
> >>> This is why I don't think a binary approach is a good one for Linux.
> >>> Users should be able to specify what they need, and block the rest.
> >>
> >> Here is a design to be able to only allow a set of network protocols and
> >> deny everything else. This would be complementary to Konstantin's patch
> >> series which addresses fine-grained access control.
> >>
> >> First, I want to remind that Landlock follows an allowed list approach
> >> with a set of (growing) supported actions (for compatibility reasons),
> >> which is kind of an allow-list-on-a-deny-list. But with this proposal,
> >> we want to be able to deny everything, which means: supported, not
> >> supported, known and unknown protocols.
> >>
> > I think this makes sense.  ChomeOS can use it at the process level:
> > disable network, allow VSOCK only, allow TCP only, etc.
> >
> >> We could add a new "handled_access_socket" field to the landlock_ruleset
> >> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
> >>
> >> If this field is set, users could add a new type of rules:
> >> struct landlock_socket_attr {
> >>       __u64 allowed_access;
> >>       int domain; // see socket(2)
>
> I guess "family" would also make sense. It's the name used in the
> kernel, the "AF" prefixes, and address_families(7). I'm not sure why
> "domain" was chosen for socket(2).
>
Agree also.

>
> >>       int type; // see socket(2)
> >> }
> >>
> > Do you want to add "int protocol" ? which is the third parameter of socket(2)
> > According to protocols(5), the protocols are defined in:
> > https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
> >
> > It is part of IPv4/IPV6 header:
> > https://www.rfc-editor.org/rfc/rfc791.html#section-3.1
> > https://www.rfc-editor.org/rfc/rfc8200.html#section-3
>
> I understand the rationale but I'm not sure if this would be useful. Do
> you have use cases?
>
I agree this field is not commonly used, so might not be that useful.
In most cases, the protocol field will just be 0.

One case I thought of previously is building an icmp or DHCP packet
with raw socket,  but now I'm not sure what kind of support/enforce
the kernel has for the protocol field with raw socket.

We can drop this for now, if there is a clearer requirement in future,
it is easy to add a new rule.

>
> >
> >> The allowed_access field would only contain
> >> LANDLOCK_ACCESS_SOCKET_CREATE at first, but it could grow with other
> >> actions (which cannot be handled with seccomp):
> >> - use: walk through all opened FDs and mark them as allowed or denied
> >> - receive: hook on received FDs
> >> - send: hook on sent FDs
> >>
> > also bind, connect, accept.
>
> I don't think "accept" would be useful, and I'm not sure if "bind" and
> "connect" would not be redundant with LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP
> Bind and connect for a datagram socket is optional, so this might lead
> to a false sense of security. If we want to support protocols other than
> TCP to restrict bind/connect, then they deserve to be controlled
> according to a port (or similar).
>
> >
> >> We might also use the same approach for non-socket objects that can be
> >> identified with some meaningful properties.
> >>
> >> What do you think?
> >
> > -Jeff

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-28 19:29             ` Mickaël Salaün
@ 2023-06-29  3:18               ` Jeff Xu
  2023-06-29 11:07                 ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Jeff Xu @ 2023-06-29  3:18 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Günther Noack, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

resend.

On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>
>
> On 28/06/2023 19:03, Jeff Xu wrote:
> > Hello,
> >
> > Thanks for writing up the example for an incoming TCP connection ! It
> > helps with the context.
> >
> > Since I'm late to this thread, one thing I want to ask:  all the APIs
> > proposed so far are at the process level, we don't have any API that
> > applies restriction to socket fd itself, right ? this is what I
> > thought, but I would like to get confirmation.
>
> Restriction are applied to actions, not to already existing/opened FDs.
> We could add a way to restrict opened FDs, but I don't think this is the
> right approach because sandboxing is a deliberate action from a process,
> and it should already take care of its FDs.
>
>
> >
> > On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
> >>
> >> Hello!
> >>
> >> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
> >>> Here is a design to be able to only allow a set of network protocols and
> >>> deny everything else. This would be complementary to Konstantin's patch
> >>> series which addresses fine-grained access control.
> >>>
> >>> First, I want to remind that Landlock follows an allowed list approach with
> >>> a set of (growing) supported actions (for compatibility reasons), which is
> >>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
> >>> able to deny everything, which means: supported, not supported, known and
> >>> unknown protocols.
> >>>
> >>> We could add a new "handled_access_socket" field to the landlock_ruleset
> >>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
> >>>
> >>> If this field is set, users could add a new type of rules:
> >>> struct landlock_socket_attr {
> >>>      __u64 allowed_access;
> >>>      int domain; // see socket(2)
> >>>      int type; // see socket(2)
> >>> }
> >>>
> >>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
> >>> first, but it could grow with other actions (which cannot be handled with
> >>> seccomp):
> >>> - use: walk through all opened FDs and mark them as allowed or denied
> >>> - receive: hook on received FDs
> >>> - send: hook on sent FDs
> >>>
> >>> We might also use the same approach for non-socket objects that can be
> >>> identified with some meaningful properties.
> >>>
> >>> What do you think?
> >>
> >> This sounds like a good plan to me - it would make it possible to restrict new
> >> socket creation using protocols that were not intended to be used, and I also
> >> think it would fit the Landlock model nicely.
> >>
> >> Small remark on the side: The security_socket_create() hook does not only get
> >> invoked as a result of socket(2), but also as a part of accept(2) - so this
> >> approach might already prevent new connections very effectively.
> >>
> > That is an interesting aspect that might be worth discussing more.
> > seccomp is per syscall, landlock doesn't necessarily follow the same,
> > another design is to add more logic in Landlock, e.g.
> > LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
> > calls (socket/bind/listen/accept/connect). App dev might feel it is
> > easier to use.
>
> seccomp restricts the use of the syscall interface, whereas Landlock
> restricts the use of kernel objects (i.e. the semantic).
>
> We need to find a good tradeoff between a lot of access rights and a few
> grouping different actions. This should make sense from a developer
> point of view according to its knowledge and use of the kernel
> interfaces (potential wrapped with high level libraries), but also to
> the semantic of the sandbox and the security guarantees we want to provide.
>
> We should also keep in mind that high level Landlock libraries can take
> care of potential coarse-grained use of restrictions.
>
>
> >
> >> Spelling out some scenarios, so that we are sure that we are on the same page:
> >>
> >> A)
> >>
> >> A program that does not need networking could specify a ruleset where
> >> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
> >>
> >> B)
> >>
> >> A program that runs a TCP server could specify a ruleset where
> >> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
> >> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
> >>
> >>    /* From Konstantin's patch set */
> >>    struct landlock_net_service_attr bind_attr = {
> >>      .allowed_access = LANDLOCK_NET_BIND_TCP,
> >>      .port = 8080,
> >>    };
> >>
> >>    /* From Mickaël's proposal */
> >>    struct landlock_socket_attr sock_inet_attr = {
> >>      .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> >>      .domain = AF_INET,
> >>      .type = SOCK_STREAM,
> >>    }
> >>
> >>    struct landlock_socket_attr sock_inet6_attr = {
> >>      .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> >>      .domain = AF_INET6,
> >>       .type = SOCK_STREAM,
> >>    }
> >>
> >> That should then be enough to bind and listen on ports, whereas outgoing
> >> connections with TCP and anything using other network protocols would not be
> >> permitted.
> >>
> > TCP server is an interesting case. From a security perspective, a
> > process cares if it is acting as a server or client in TCP, a server
> > might only want to accept an incoming TCP connection, never initiate
> > an outgoing TCP connection, and a client is the opposite.
> >
> > Processes can restrict outgoing/incoming TCP connection by seccomp for
> > accept(2) or connect(2),  though I feel Landlock can do this more
> > naturally for app dev, and at per-protocol level.  seccomp doesn't
> > provide per-protocol granularity.
>
> Right, seccomp cannot filter TCP ports.
>
> >
> > For bind(2), iirc, it can be used for a server to assign dst port of
> > incoming TCP connection, also by a client to assign a src port of an
> > outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
> > cases, right ? this might not be a problem, just something to keep
> > note.
>
> Good point. I think it is in line with the rule definition: to allow to
> bind on a specific port. However, if clients want to set the source port
> to a (legitimate) value, then that would be an issue because we cannot
> allow a whole range of ports (e.g., >= 1024). I'm not sure if this
> practice would be deemed "legitimate" though. Do you know client
> applications using bind?
>
> Konstantin, we should have a test for this case anyway.
>
>
> >
> >> (Alternatively, it could bind() the socket early, *then enable Landlock* and
> >> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
> >> IPv6, so that listen() and accept() work on the already-bound socket.)
> >>
> > For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
> > so dev is fully aware it is not just applied to socket create.
>
> I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does
> PROTOCOL mean?
>
I meant checking family + type of socket, and apply to all of
socket(2),bind(2),accept(2),connect(2),listen(2), maybe
send(2)/recv(2) too.

s/LANDLOCK_ACCESS_SOCKET_CREATE/LANDLOCK_ACCESS_SOCKET_TYPE.

This implies the kernel will check on socket fd's property (family +
type) at those calls, this applies to
a - the socket fd is created within the process, after landlock is applied.
b - created in process prior to landlock is applied.
c - created out of process then passed into this process,

> >
> >> Overall, this sounds like an excellent approach to me. 👍
> >>
> >> —Günther
> >>
> >> --
> >> Sent using Mutt 🐕 Woof Woof
> >
> > -Jeff

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-29  3:18               ` Jeff Xu
@ 2023-06-29 11:07                 ` Mickaël Salaün
  2023-06-30  4:18                   ` Jeff Xu
  2023-07-13 11:44                   ` Konstantin Meskhidze (A)
  0 siblings, 2 replies; 74+ messages in thread
From: Mickaël Salaün @ 2023-06-29 11:07 UTC (permalink / raw)
  To: Jeff Xu
  Cc: Günther Noack, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov


On 29/06/2023 05:18, Jeff Xu wrote:
> resend.
> 
> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>>
>>
>> On 28/06/2023 19:03, Jeff Xu wrote:
>>> Hello,
>>>
>>> Thanks for writing up the example for an incoming TCP connection ! It
>>> helps with the context.
>>>
>>> Since I'm late to this thread, one thing I want to ask:  all the APIs
>>> proposed so far are at the process level, we don't have any API that
>>> applies restriction to socket fd itself, right ? this is what I
>>> thought, but I would like to get confirmation.
>>
>> Restriction are applied to actions, not to already existing/opened FDs.
>> We could add a way to restrict opened FDs, but I don't think this is the
>> right approach because sandboxing is a deliberate action from a process,
>> and it should already take care of its FDs.
>>
>>
>>>
>>> On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
>>>>
>>>> Hello!
>>>>
>>>> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
>>>>> Here is a design to be able to only allow a set of network protocols and
>>>>> deny everything else. This would be complementary to Konstantin's patch
>>>>> series which addresses fine-grained access control.
>>>>>
>>>>> First, I want to remind that Landlock follows an allowed list approach with
>>>>> a set of (growing) supported actions (for compatibility reasons), which is
>>>>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
>>>>> able to deny everything, which means: supported, not supported, known and
>>>>> unknown protocols.
>>>>>
>>>>> We could add a new "handled_access_socket" field to the landlock_ruleset
>>>>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>>>>>
>>>>> If this field is set, users could add a new type of rules:
>>>>> struct landlock_socket_attr {
>>>>>       __u64 allowed_access;
>>>>>       int domain; // see socket(2)
>>>>>       int type; // see socket(2)
>>>>> }
>>>>>
>>>>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
>>>>> first, but it could grow with other actions (which cannot be handled with
>>>>> seccomp):
>>>>> - use: walk through all opened FDs and mark them as allowed or denied
>>>>> - receive: hook on received FDs
>>>>> - send: hook on sent FDs
>>>>>
>>>>> We might also use the same approach for non-socket objects that can be
>>>>> identified with some meaningful properties.
>>>>>
>>>>> What do you think?
>>>>
>>>> This sounds like a good plan to me - it would make it possible to restrict new
>>>> socket creation using protocols that were not intended to be used, and I also
>>>> think it would fit the Landlock model nicely.
>>>>
>>>> Small remark on the side: The security_socket_create() hook does not only get
>>>> invoked as a result of socket(2), but also as a part of accept(2) - so this
>>>> approach might already prevent new connections very effectively.
>>>>
>>> That is an interesting aspect that might be worth discussing more.
>>> seccomp is per syscall, landlock doesn't necessarily follow the same,
>>> another design is to add more logic in Landlock, e.g.
>>> LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
>>> calls (socket/bind/listen/accept/connect). App dev might feel it is
>>> easier to use.
>>
>> seccomp restricts the use of the syscall interface, whereas Landlock
>> restricts the use of kernel objects (i.e. the semantic).
>>
>> We need to find a good tradeoff between a lot of access rights and a few
>> grouping different actions. This should make sense from a developer
>> point of view according to its knowledge and use of the kernel
>> interfaces (potential wrapped with high level libraries), but also to
>> the semantic of the sandbox and the security guarantees we want to provide.
>>
>> We should also keep in mind that high level Landlock libraries can take
>> care of potential coarse-grained use of restrictions.
>>
>>
>>>
>>>> Spelling out some scenarios, so that we are sure that we are on the same page:
>>>>
>>>> A)
>>>>
>>>> A program that does not need networking could specify a ruleset where
>>>> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
>>>>
>>>> B)
>>>>
>>>> A program that runs a TCP server could specify a ruleset where
>>>> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
>>>> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
>>>>
>>>>     /* From Konstantin's patch set */
>>>>     struct landlock_net_service_attr bind_attr = {
>>>>       .allowed_access = LANDLOCK_NET_BIND_TCP,
>>>>       .port = 8080,
>>>>     };
>>>>
>>>>     /* From Mickaël's proposal */
>>>>     struct landlock_socket_attr sock_inet_attr = {
>>>>       .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>>>       .domain = AF_INET,
>>>>       .type = SOCK_STREAM,
>>>>     }
>>>>
>>>>     struct landlock_socket_attr sock_inet6_attr = {
>>>>       .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>>>       .domain = AF_INET6,
>>>>        .type = SOCK_STREAM,
>>>>     }
>>>>
>>>> That should then be enough to bind and listen on ports, whereas outgoing
>>>> connections with TCP and anything using other network protocols would not be
>>>> permitted.
>>>>
>>> TCP server is an interesting case. From a security perspective, a
>>> process cares if it is acting as a server or client in TCP, a server
>>> might only want to accept an incoming TCP connection, never initiate
>>> an outgoing TCP connection, and a client is the opposite.
>>>
>>> Processes can restrict outgoing/incoming TCP connection by seccomp for
>>> accept(2) or connect(2),  though I feel Landlock can do this more
>>> naturally for app dev, and at per-protocol level.  seccomp doesn't
>>> provide per-protocol granularity.
>>
>> Right, seccomp cannot filter TCP ports.
>>
>>>
>>> For bind(2), iirc, it can be used for a server to assign dst port of
>>> incoming TCP connection, also by a client to assign a src port of an
>>> outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
>>> cases, right ? this might not be a problem, just something to keep
>>> note.
>>
>> Good point. I think it is in line with the rule definition: to allow to
>> bind on a specific port. However, if clients want to set the source port
>> to a (legitimate) value, then that would be an issue because we cannot
>> allow a whole range of ports (e.g., >= 1024). I'm not sure if this
>> practice would be deemed "legitimate" though. Do you know client
>> applications using bind?
>>
>> Konstantin, we should have a test for this case anyway.

Thinking more about TCP clients binding sockets, a 
LANDLOCK_ACCESS_NET_LISTEN_TCP would be more useful than 
LANDLOCK_ACCESS_NET_BIND_TCP, but being able to limit the scope of 
"bindable" ports is also valuable to forbid a malicious sandboxed 
process to impersonate a legitimate server process. This also means that 
it might be interesting to be able to handle port ranges.

We already have a LANDLOCK_ACCESS_NET_BIND_TCP implementation and 
related tests, so I think we should proceed with that. The next 
network-related patch series should implement this 
LANDLOCK_ACCESS_NET_LISTEN_TCP access right though, which should not be 
difficult thanks to the framework implemented with current patch series.

Konstantin, would you like to develop the TCP listening access control 
once this patch series land?


>>>> (Alternatively, it could bind() the socket early, *then enable Landlock* and
>>>> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
>>>> IPv6, so that listen() and accept() work on the already-bound socket.)
>>>>
>>> For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
>>> so dev is fully aware it is not just applied to socket create.
>>
>> I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does
>> PROTOCOL mean?
>>
> I meant checking family + type of socket, and apply to all of
> socket(2),bind(2),accept(2),connect(2),listen(2), maybe
> send(2)/recv(2) too.

OK, that would be kind of similar to the LANDLOCK_ACCESS_SOCKET_USE 
description. However, I think this kind of global approach has several 
issues:
- This covers a lot of different aspects and would increase the cost of 
development/testing/review.
- Whereas it wraps different actions, it will not let user space have a 
fine-grained access control on these, which could be useful for some use 
cases.
- I don't see the point of restricting accept(2) if we can already 
restrict bind(2) and listen(2). accept(2) could be useful to identify 
the remote peer but I'm not convinced this would make sense, and if it 
would, then this can be postponed until we have a way to identify peers.
- For performance reasons, we should avoid restricting 
send/recv/read/write but instead only restrict the control plane: object 
creation and configuration.

I'm not convinced that being able to control all kind of socket bind, 
listen and connect actions might be worth implementing instead of a 
fine-grained access control for the main protocols (TCP, UDP, unix and 
vsock maybe), with the related tests and guarantees.

However, this landlock_socket_attr struct could have an allowed_access 
field that could contain LANDLOCK_ACCESS_NET_{CONNECT,LISTEN,BIND}_TCP 
rights (which would just not be constrained by any port, except if a 
landlock_net_port_attr rule matches). It would then make sense to rename 
LANDLOCK_ACCESS_SOCKET_CREATE to LANDLOCK_ACCESS_NET_CREATE_SOCKET. This 
right would not be accepted in a landlock_net_port_attr.allowed_access 
though.

> 
> s/LANDLOCK_ACCESS_SOCKET_CREATE/LANDLOCK_ACCESS_SOCKET_TYPE.
> 
> This implies the kernel will check on socket fd's property (family +
> type) at those calls, this applies to
> a - the socket fd is created within the process, after landlock is applied.
> b - created in process prior to landlock is applied.
> c - created out of process then passed into this process,

OK, these are the same rules as for LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-29 11:07                 ` Mickaël Salaün
@ 2023-06-30  4:18                   ` Jeff Xu
  2023-06-30 18:23                     ` Mickaël Salaün
  2023-07-13 11:44                   ` Konstantin Meskhidze (A)
  1 sibling, 1 reply; 74+ messages in thread
From: Jeff Xu @ 2023-06-30  4:18 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Günther Noack, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

On Thu, Jun 29, 2023 at 4:07 AM Mickaël Salaün <mic@digikod.net> wrote:
>
>
> On 29/06/2023 05:18, Jeff Xu wrote:
> > resend.
> >
> > On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
> >>
> >>
> >> On 28/06/2023 19:03, Jeff Xu wrote:
> >>> Hello,
> >>>
> >>> Thanks for writing up the example for an incoming TCP connection ! It
> >>> helps with the context.
> >>>
> >>> Since I'm late to this thread, one thing I want to ask:  all the APIs
> >>> proposed so far are at the process level, we don't have any API that
> >>> applies restriction to socket fd itself, right ? this is what I
> >>> thought, but I would like to get confirmation.
> >>
> >> Restriction are applied to actions, not to already existing/opened FDs.
> >> We could add a way to restrict opened FDs, but I don't think this is the
> >> right approach because sandboxing is a deliberate action from a process,
> >> and it should already take care of its FDs.
> >>
> >>
> >>>
> >>> On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
> >>>>
> >>>> Hello!
> >>>>
> >>>> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
> >>>>> Here is a design to be able to only allow a set of network protocols and
> >>>>> deny everything else. This would be complementary to Konstantin's patch
> >>>>> series which addresses fine-grained access control.
> >>>>>
> >>>>> First, I want to remind that Landlock follows an allowed list approach with
> >>>>> a set of (growing) supported actions (for compatibility reasons), which is
> >>>>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
> >>>>> able to deny everything, which means: supported, not supported, known and
> >>>>> unknown protocols.
> >>>>>
> >>>>> We could add a new "handled_access_socket" field to the landlock_ruleset
> >>>>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
> >>>>>
> >>>>> If this field is set, users could add a new type of rules:
> >>>>> struct landlock_socket_attr {
> >>>>>       __u64 allowed_access;
> >>>>>       int domain; // see socket(2)
> >>>>>       int type; // see socket(2)
> >>>>> }
> >>>>>
> >>>>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
> >>>>> first, but it could grow with other actions (which cannot be handled with
> >>>>> seccomp):
> >>>>> - use: walk through all opened FDs and mark them as allowed or denied
> >>>>> - receive: hook on received FDs
> >>>>> - send: hook on sent FDs
> >>>>>
> >>>>> We might also use the same approach for non-socket objects that can be
> >>>>> identified with some meaningful properties.
> >>>>>
> >>>>> What do you think?
> >>>>
> >>>> This sounds like a good plan to me - it would make it possible to restrict new
> >>>> socket creation using protocols that were not intended to be used, and I also
> >>>> think it would fit the Landlock model nicely.
> >>>>
> >>>> Small remark on the side: The security_socket_create() hook does not only get
> >>>> invoked as a result of socket(2), but also as a part of accept(2) - so this
> >>>> approach might already prevent new connections very effectively.
> >>>>
> >>> That is an interesting aspect that might be worth discussing more.
> >>> seccomp is per syscall, landlock doesn't necessarily follow the same,
> >>> another design is to add more logic in Landlock, e.g.
> >>> LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
> >>> calls (socket/bind/listen/accept/connect). App dev might feel it is
> >>> easier to use.
> >>
> >> seccomp restricts the use of the syscall interface, whereas Landlock
> >> restricts the use of kernel objects (i.e. the semantic).
> >>
> >> We need to find a good tradeoff between a lot of access rights and a few
> >> grouping different actions. This should make sense from a developer
> >> point of view according to its knowledge and use of the kernel
> >> interfaces (potential wrapped with high level libraries), but also to
> >> the semantic of the sandbox and the security guarantees we want to provide.
> >>
> >> We should also keep in mind that high level Landlock libraries can take
> >> care of potential coarse-grained use of restrictions.
> >>
> >>
> >>>
> >>>> Spelling out some scenarios, so that we are sure that we are on the same page:
> >>>>
> >>>> A)
> >>>>
> >>>> A program that does not need networking could specify a ruleset where
> >>>> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
> >>>>
> >>>> B)
> >>>>
> >>>> A program that runs a TCP server could specify a ruleset where
> >>>> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
> >>>> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
> >>>>
> >>>>     /* From Konstantin's patch set */
> >>>>     struct landlock_net_service_attr bind_attr = {
> >>>>       .allowed_access = LANDLOCK_NET_BIND_TCP,
> >>>>       .port = 8080,
> >>>>     };
> >>>>
> >>>>     /* From Mickaël's proposal */
> >>>>     struct landlock_socket_attr sock_inet_attr = {
> >>>>       .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> >>>>       .domain = AF_INET,
> >>>>       .type = SOCK_STREAM,
> >>>>     }
> >>>>
> >>>>     struct landlock_socket_attr sock_inet6_attr = {
> >>>>       .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> >>>>       .domain = AF_INET6,
> >>>>        .type = SOCK_STREAM,
> >>>>     }
> >>>>
> >>>> That should then be enough to bind and listen on ports, whereas outgoing
> >>>> connections with TCP and anything using other network protocols would not be
> >>>> permitted.
> >>>>
> >>> TCP server is an interesting case. From a security perspective, a
> >>> process cares if it is acting as a server or client in TCP, a server
> >>> might only want to accept an incoming TCP connection, never initiate
> >>> an outgoing TCP connection, and a client is the opposite.
> >>>
> >>> Processes can restrict outgoing/incoming TCP connection by seccomp for
> >>> accept(2) or connect(2),  though I feel Landlock can do this more
> >>> naturally for app dev, and at per-protocol level.  seccomp doesn't
> >>> provide per-protocol granularity.
> >>
> >> Right, seccomp cannot filter TCP ports.
> >>
> >>>
> >>> For bind(2), iirc, it can be used for a server to assign dst port of
> >>> incoming TCP connection, also by a client to assign a src port of an
> >>> outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
> >>> cases, right ? this might not be a problem, just something to keep
> >>> note.
> >>
> >> Good point. I think it is in line with the rule definition: to allow to
> >> bind on a specific port. However, if clients want to set the source port
> >> to a (legitimate) value, then that would be an issue because we cannot
> >> allow a whole range of ports (e.g., >= 1024). I'm not sure if this
> >> practice would be deemed "legitimate" though. Do you know client
> >> applications using bind?
> >>
> >> Konstantin, we should have a test for this case anyway.
>
> Thinking more about TCP clients binding sockets, a
> LANDLOCK_ACCESS_NET_LISTEN_TCP would be more useful than
> LANDLOCK_ACCESS_NET_BIND_TCP, but being able to limit the scope of
> "bindable" ports is also valuable to forbid a malicious sandboxed
> process to impersonate a legitimate server process. This also means that
> it might be interesting to be able to handle port ranges.
>
> We already have a LANDLOCK_ACCESS_NET_BIND_TCP implementation and
> related tests, so I think we should proceed with that. The next
> network-related patch series should implement this
> LANDLOCK_ACCESS_NET_LISTEN_TCP access right though, which should not be
> difficult thanks to the framework implemented with current patch series.
>
> Konstantin, would you like to develop the TCP listening access control
> once this patch series land?
>
>
> >>>> (Alternatively, it could bind() the socket early, *then enable Landlock* and
> >>>> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
> >>>> IPv6, so that listen() and accept() work on the already-bound socket.)
> >>>>
> >>> For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
> >>> so dev is fully aware it is not just applied to socket create.
> >>
> >> I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does
> >> PROTOCOL mean?
> >>
> > I meant checking family + type of socket, and apply to all of
> > socket(2),bind(2),accept(2),connect(2),listen(2), maybe
> > send(2)/recv(2) too.
>
> OK, that would be kind of similar to the LANDLOCK_ACCESS_SOCKET_USE
> description. However, I think this kind of global approach has several
> issues:
> - This covers a lot of different aspects and would increase the cost of
> development/testing/review.
True.

> - Whereas it wraps different actions, it will not let user space have a
> fine-grained access control on these, which could be useful for some use
> cases.
Make sense.

> - I don't see the point of restricting accept(2) if we can already
> restrict bind(2) and listen(2). accept(2) could be useful to identify
> the remote peer but I'm not convinced this would make sense, and if it
> would, then this can be postponed until we have a way to identify peers.

I was thinking about a case that the socket was created/bind/listen in
another process, then passed into the current process,

For example:
Process A has :
LANDLOCK_ACCESS_SOCKET_CREATE (family = f1, type = t1)
socket s1 is created in process A with family = f1, type = t1, and
bind/listen to port p1.

socket s1 is passed to process B
Process B has:
LANDLOCK_ACCESS_SOCKET_CREATE (family =f1, type = t2) (note the type
is different than A)
LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP (port = p2)

However, those rules in B don't restrict process B from using
accept(s1), s1 is another type.

In accept(2), struct sockaddr contains sa_family_t (AF_xx)  but no
type, which is strange to me, the API should either include both, or
none (accept whatever it is already in socket fd, which is set during
creation time).

looking into accept(2) implementation: it calls: sock->ops->accept
iiuc, sock->ops is set during socket(2), allowing each protocol to
have its own implementation.

When we consider a> our intention to restrict family + type of socket,
with b> socket can be passed between processes,
there can be a need to harden the check (family + type) for all of
bind/listen/accept/connect. Otherwise, there is still a possibility
that the process to accept a socket of different type unintentionally.

This means:
LANDLOCK_ACCESS_SOCKET_ATTR_CREATE (family =f1, type = t2)
LANDLOCK_ACCESS_SOCKET_ATTR_BIND (family =f1, type = t2)
LANDLOCK_ACCESS_SOCKET_ATTR_ACCEPT (family =f1, type = t2)
LANDLOCK_ACCESS_SOCKET_ATTR_ LISTEN (family =f1, type = t2)
LANDLOCK_ACCESS_SOCKET_ATTR_CONNECT (family =f1, type = t2)
Note: this checks family+type only, not port.
The check is applied to all protocols, so not specific to TCP/UDP

> - For performance reasons, we should avoid restricting
> send/recv/read/write but instead only restrict the control plane: object
> creation and configuration.
>
Performance is a valid concern.

As example of server, usually the main process listens/accepts incoming
connections, and forked processes do send/recv, the main process can
be viewed as a control plane, and send/recv can be viewed as a data
plane. It makes sense that we start with the control plane.

We might like to keep a note that by not restricting send/recv, a
socket can be created OOP, then passed into current process and call
send/recv, so the network is not fully disabled by landlock alone
(still need seccomp)

Things might get more complicated, say: a forked process is intended
to send/recv UDP, but was confused and got a TCP socket from
OOP, etc. This is not different than accept(2) case above. There might
be an opportunity for Landlock to harden this when we design for
data-plane.

> I'm not convinced that being able to control all kind of socket bind,
> listen and connect actions might be worth implementing instead of a
> fine-grained access control for the main protocols (TCP, UDP, unix and
> vsock maybe), with the related tests and guarantees.
>
> However, this landlock_socket_attr struct could have an allowed_access
> field that could contain LANDLOCK_ACCESS_NET_{CONNECT,LISTEN,BIND}_TCP
> rights (which would just not be constrained by any port, except if a
> landlock_net_port_attr rule matches). It would then make sense to rename
> LANDLOCK_ACCESS_SOCKET_CREATE to LANDLOCK_ACCESS_NET_CREATE_SOCKET. This
> right would not be accepted in a landlock_net_port_attr.allowed_access
> though.
>
I'm not sure if my view is fully explained. I don't mean to control
all kinds of socket bind/listen/connect actions.
My view is:
1> have a rule to check family + type, to make sure the process is
using the socket type they intend to use, such as
LANDLOCK_ACCESS_SOCKET_ATTR_{CREATE|CONNECT|BIND|ACCEPT|LISTEN}, as
discussed in accept(2) case.
2> have protocol specific rules, such as LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.
So bind(2) will be checked by both 1 and 2.

As example of TCP server, the process will use:
LANDLOCK_ACCESS_SOCKET_ATTR_{CREATE|BIND|ACCEPT|LISTEN}
LANDLOCK_ACCESS_NET_{BIND}_TCP

> >
> > s/LANDLOCK_ACCESS_SOCKET_CREATE/LANDLOCK_ACCESS_SOCKET_TYPE.
> >
> > This implies the kernel will check on socket fd's property (family +
> > type) at those calls, this applies to
> > a - the socket fd is created within the process, after landlock is applied.
> > b - created in process prior to landlock is applied.
> > c - created out of process then passed into this process,
>
> OK, these are the same rules as for LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.

I don't mean this to be _TCP specific, this is still the family + type
discussion above.

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-30  4:18                   ` Jeff Xu
@ 2023-06-30 18:23                     ` Mickaël Salaün
  2023-07-05 15:00                       ` Jeff Xu
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-06-30 18:23 UTC (permalink / raw)
  To: Jeff Xu
  Cc: Günther Noack, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin, Jeff Xu,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov


On 30/06/2023 06:18, Jeff Xu wrote:
> On Thu, Jun 29, 2023 at 4:07 AM Mickaël Salaün <mic@digikod.net> wrote:
>>
>>
>> On 29/06/2023 05:18, Jeff Xu wrote:
>>> resend.
>>>
>>> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>>>>
>>>>
>>>> On 28/06/2023 19:03, Jeff Xu wrote:
>>>>> Hello,
>>>>>
>>>>> Thanks for writing up the example for an incoming TCP connection ! It
>>>>> helps with the context.
>>>>>
>>>>> Since I'm late to this thread, one thing I want to ask:  all the APIs
>>>>> proposed so far are at the process level, we don't have any API that
>>>>> applies restriction to socket fd itself, right ? this is what I
>>>>> thought, but I would like to get confirmation.
>>>>
>>>> Restriction are applied to actions, not to already existing/opened FDs.
>>>> We could add a way to restrict opened FDs, but I don't think this is the
>>>> right approach because sandboxing is a deliberate action from a process,
>>>> and it should already take care of its FDs.
>>>>
>>>>
>>>>>
>>>>> On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
>>>>>>
>>>>>> Hello!
>>>>>>
>>>>>> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
>>>>>>> Here is a design to be able to only allow a set of network protocols and
>>>>>>> deny everything else. This would be complementary to Konstantin's patch
>>>>>>> series which addresses fine-grained access control.
>>>>>>>
>>>>>>> First, I want to remind that Landlock follows an allowed list approach with
>>>>>>> a set of (growing) supported actions (for compatibility reasons), which is
>>>>>>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
>>>>>>> able to deny everything, which means: supported, not supported, known and
>>>>>>> unknown protocols.
>>>>>>>
>>>>>>> We could add a new "handled_access_socket" field to the landlock_ruleset
>>>>>>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>>>>>>>
>>>>>>> If this field is set, users could add a new type of rules:
>>>>>>> struct landlock_socket_attr {
>>>>>>>        __u64 allowed_access;
>>>>>>>        int domain; // see socket(2)
>>>>>>>        int type; // see socket(2)
>>>>>>> }
>>>>>>>
>>>>>>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
>>>>>>> first, but it could grow with other actions (which cannot be handled with
>>>>>>> seccomp):
>>>>>>> - use: walk through all opened FDs and mark them as allowed or denied
>>>>>>> - receive: hook on received FDs
>>>>>>> - send: hook on sent FDs
>>>>>>>
>>>>>>> We might also use the same approach for non-socket objects that can be
>>>>>>> identified with some meaningful properties.
>>>>>>>
>>>>>>> What do you think?
>>>>>>
>>>>>> This sounds like a good plan to me - it would make it possible to restrict new
>>>>>> socket creation using protocols that were not intended to be used, and I also
>>>>>> think it would fit the Landlock model nicely.
>>>>>>
>>>>>> Small remark on the side: The security_socket_create() hook does not only get
>>>>>> invoked as a result of socket(2), but also as a part of accept(2) - so this
>>>>>> approach might already prevent new connections very effectively.
>>>>>>
>>>>> That is an interesting aspect that might be worth discussing more.
>>>>> seccomp is per syscall, landlock doesn't necessarily follow the same,
>>>>> another design is to add more logic in Landlock, e.g.
>>>>> LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
>>>>> calls (socket/bind/listen/accept/connect). App dev might feel it is
>>>>> easier to use.
>>>>
>>>> seccomp restricts the use of the syscall interface, whereas Landlock
>>>> restricts the use of kernel objects (i.e. the semantic).
>>>>
>>>> We need to find a good tradeoff between a lot of access rights and a few
>>>> grouping different actions. This should make sense from a developer
>>>> point of view according to its knowledge and use of the kernel
>>>> interfaces (potential wrapped with high level libraries), but also to
>>>> the semantic of the sandbox and the security guarantees we want to provide.
>>>>
>>>> We should also keep in mind that high level Landlock libraries can take
>>>> care of potential coarse-grained use of restrictions.
>>>>
>>>>
>>>>>
>>>>>> Spelling out some scenarios, so that we are sure that we are on the same page:
>>>>>>
>>>>>> A)
>>>>>>
>>>>>> A program that does not need networking could specify a ruleset where
>>>>>> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
>>>>>>
>>>>>> B)
>>>>>>
>>>>>> A program that runs a TCP server could specify a ruleset where
>>>>>> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
>>>>>> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
>>>>>>
>>>>>>      /* From Konstantin's patch set */
>>>>>>      struct landlock_net_service_attr bind_attr = {
>>>>>>        .allowed_access = LANDLOCK_NET_BIND_TCP,
>>>>>>        .port = 8080,
>>>>>>      };
>>>>>>
>>>>>>      /* From Mickaël's proposal */
>>>>>>      struct landlock_socket_attr sock_inet_attr = {
>>>>>>        .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>>>>>        .domain = AF_INET,
>>>>>>        .type = SOCK_STREAM,
>>>>>>      }
>>>>>>
>>>>>>      struct landlock_socket_attr sock_inet6_attr = {
>>>>>>        .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>>>>>        .domain = AF_INET6,
>>>>>>         .type = SOCK_STREAM,
>>>>>>      }
>>>>>>
>>>>>> That should then be enough to bind and listen on ports, whereas outgoing
>>>>>> connections with TCP and anything using other network protocols would not be
>>>>>> permitted.
>>>>>>
>>>>> TCP server is an interesting case. From a security perspective, a
>>>>> process cares if it is acting as a server or client in TCP, a server
>>>>> might only want to accept an incoming TCP connection, never initiate
>>>>> an outgoing TCP connection, and a client is the opposite.
>>>>>
>>>>> Processes can restrict outgoing/incoming TCP connection by seccomp for
>>>>> accept(2) or connect(2),  though I feel Landlock can do this more
>>>>> naturally for app dev, and at per-protocol level.  seccomp doesn't
>>>>> provide per-protocol granularity.
>>>>
>>>> Right, seccomp cannot filter TCP ports.
>>>>
>>>>>
>>>>> For bind(2), iirc, it can be used for a server to assign dst port of
>>>>> incoming TCP connection, also by a client to assign a src port of an
>>>>> outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
>>>>> cases, right ? this might not be a problem, just something to keep
>>>>> note.
>>>>
>>>> Good point. I think it is in line with the rule definition: to allow to
>>>> bind on a specific port. However, if clients want to set the source port
>>>> to a (legitimate) value, then that would be an issue because we cannot
>>>> allow a whole range of ports (e.g., >= 1024). I'm not sure if this
>>>> practice would be deemed "legitimate" though. Do you know client
>>>> applications using bind?
>>>>
>>>> Konstantin, we should have a test for this case anyway.
>>
>> Thinking more about TCP clients binding sockets, a
>> LANDLOCK_ACCESS_NET_LISTEN_TCP would be more useful than
>> LANDLOCK_ACCESS_NET_BIND_TCP, but being able to limit the scope of
>> "bindable" ports is also valuable to forbid a malicious sandboxed
>> process to impersonate a legitimate server process. This also means that
>> it might be interesting to be able to handle port ranges.
>>
>> We already have a LANDLOCK_ACCESS_NET_BIND_TCP implementation and
>> related tests, so I think we should proceed with that. The next
>> network-related patch series should implement this
>> LANDLOCK_ACCESS_NET_LISTEN_TCP access right though, which should not be
>> difficult thanks to the framework implemented with current patch series.
>>
>> Konstantin, would you like to develop the TCP listening access control
>> once this patch series land?
>>
>>
>>>>>> (Alternatively, it could bind() the socket early, *then enable Landlock* and
>>>>>> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
>>>>>> IPv6, so that listen() and accept() work on the already-bound socket.)
>>>>>>
>>>>> For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
>>>>> so dev is fully aware it is not just applied to socket create.
>>>>
>>>> I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does
>>>> PROTOCOL mean?
>>>>
>>> I meant checking family + type of socket, and apply to all of
>>> socket(2),bind(2),accept(2),connect(2),listen(2), maybe
>>> send(2)/recv(2) too.
>>
>> OK, that would be kind of similar to the LANDLOCK_ACCESS_SOCKET_USE
>> description. However, I think this kind of global approach has several
>> issues:
>> - This covers a lot of different aspects and would increase the cost of
>> development/testing/review.
> True.
> 
>> - Whereas it wraps different actions, it will not let user space have a
>> fine-grained access control on these, which could be useful for some use
>> cases.
> Make sense.
> 
>> - I don't see the point of restricting accept(2) if we can already
>> restrict bind(2) and listen(2). accept(2) could be useful to identify
>> the remote peer but I'm not convinced this would make sense, and if it
>> would, then this can be postponed until we have a way to identify peers.
> 
> I was thinking about a case that the socket was created/bind/listen in
> another process, then passed into the current process,
> 
> For example:
> Process A has :
> LANDLOCK_ACCESS_SOCKET_CREATE (family = f1, type = t1)
> socket s1 is created in process A with family = f1, type = t1, and
> bind/listen to port p1.
> 
> socket s1 is passed to process B
> Process B has:
> LANDLOCK_ACCESS_SOCKET_CREATE (family =f1, type = t2) (note the type
> is different than A)
> LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP (port = p2)
> 
> However, those rules in B don't restrict process B from using
> accept(s1), s1 is another type.

Indeed, but why process A would pass this FD to B? Do you have real use 
cases in mind?

In case of confuse deputy attack, I'm convinced there is much more 
chance for B to just ask A to do nasty thing, no need to receive an FD, 
just to write data to the socket/IPC.

> 
> In accept(2), struct sockaddr contains sa_family_t (AF_xx)  but no
> type, which is strange to me, the API should either include both, or
> none (accept whatever it is already in socket fd, which is set during
> creation time).

I think sockaddr defines the minimal requirement to deal with 
accept/bind/connect. The sin_family is require to define the type of 
address and port according, but the type is not.

> 
> looking into accept(2) implementation: it calls: sock->ops->accept
> iiuc, sock->ops is set during socket(2), allowing each protocol to
> have its own implementation.
> 
> When we consider a> our intention to restrict family + type of socket,
> with b> socket can be passed between processes,
> there can be a need to harden the check (family + type) for all of
> bind/listen/accept/connect. Otherwise, there is still a possibility
> that the process to accept a socket of different type unintentionally.
> 
> This means:
> LANDLOCK_ACCESS_SOCKET_ATTR_CREATE (family =f1, type = t2)
> LANDLOCK_ACCESS_SOCKET_ATTR_BIND (family =f1, type = t2)
> LANDLOCK_ACCESS_SOCKET_ATTR_ACCEPT (family =f1, type = t2)
> LANDLOCK_ACCESS_SOCKET_ATTR_ LISTEN (family =f1, type = t2)
> LANDLOCK_ACCESS_SOCKET_ATTR_CONNECT (family =f1, type = t2)
> Note: this checks family+type only, not port.
> The check is applied to all protocols, so not specific to TCP/UDP

The sandboxing/Landlock threat model is to restrict a process when it is 
sandboxed, but this sandboxing is a request from the same process (or 
one of its parent) that happen when it is more trustworthy (or at least 
has more privileges) than after it sandbox itself.

The process sandboxing itself can use several kernel features, and one 
of it is Landlock. In any case, it should take care of closing file 
descriptors that should not be passed to the sandboxed process.

The limits of sandboxing are the communication channels from and to 
outside the sandbox. The peers talking with sandboxed processes should 
then not be subject to confused deputy attacks, which means they must 
not enable to bypass the user-defined security policy (from which the 
Landlock policy is only a part). Receiving file descriptors should then 
not be more important than controlling the communication channels. If a 
not-sandboxed process is willing to give more right to a sandboxed 
process, by passing FDs or just receiving commands, then this 
not-sandboxed process need to be fixed.

This is the rationale to not care about received nor sent file 
descriptors. The communication channels and the remote peers must be 
trusted to not give more privileges to the sandboxed processes.

If a peer is malicious, it doesn't need to pass a file descriptor to the 
sandboxed process, it can just read (data) commands and apply them to 
its file descriptors. I think the ability to pass file descriptors 
should be seen as a way to improve performance by avoiding a user space 
process to act as a proxy receiving read/write commands and managing 
file descriptors itself. On the other hand, file descriptors could be 
used as real capabilities/tokens to manage access, but senders still 
need to be careful to only pass the required ones.

All this to say that being able to restrict actions on file descriptors 
would be useful for senders/services to send a subset of the file 
descriptor capabilities (cf. Capsicum), but not the other way around.


> 
>> - For performance reasons, we should avoid restricting
>> send/recv/read/write but instead only restrict the control plane: object
>> creation and configuration.
>>
> Performance is a valid concern.
> 
> As example of server, usually the main process listens/accepts incoming
> connections, and forked processes do send/recv, the main process can
> be viewed as a control plane, and send/recv can be viewed as a data
> plane. It makes sense that we start with the control plane.
> 
> We might like to keep a note that by not restricting send/recv, a
> socket can be created OOP, then passed into current process and call
> send/recv, so the network is not fully disabled by landlock alone
> (still need seccomp)

Right, the kernel (and then Landlock) is not enough to sandbox a 
complete environment, user space needs to be aware and be configured for 
that too.

I understand the desire to restrict as much as possible, but this 
require to add more code and then it increase the risk of bugs, whereas 
it might not be a big deal for attackers. I don't think the cost is 
worth it and I don't want to give a false sense of security that could 
let users think their application cannot communicate with the network if 
it can communicate with local processes connected to the network.


> 
> Things might get more complicated, say: a forked process is intended
> to send/recv UDP, but was confused and got a TCP socket from
> OOP, etc. This is not different than accept(2) case above. There might
> be an opportunity for Landlock to harden this when we design for
> data-plane.
> 
>> I'm not convinced that being able to control all kind of socket bind,
>> listen and connect actions might be worth implementing instead of a
>> fine-grained access control for the main protocols (TCP, UDP, unix and
>> vsock maybe), with the related tests and guarantees.
>>
>> However, this landlock_socket_attr struct could have an allowed_access
>> field that could contain LANDLOCK_ACCESS_NET_{CONNECT,LISTEN,BIND}_TCP
>> rights (which would just not be constrained by any port, except if a
>> landlock_net_port_attr rule matches). It would then make sense to rename
>> LANDLOCK_ACCESS_SOCKET_CREATE to LANDLOCK_ACCESS_NET_CREATE_SOCKET. This
>> right would not be accepted in a landlock_net_port_attr.allowed_access
>> though.
>>
> I'm not sure if my view is fully explained. I don't mean to control
> all kinds of socket bind/listen/connect actions.
> My view is:
> 1> have a rule to check family + type, to make sure the process is
> using the socket type they intend to use, such as
> LANDLOCK_ACCESS_SOCKET_ATTR_{CREATE|CONNECT|BIND|ACCEPT|LISTEN}, as
> discussed in accept(2) case.
> 2> have protocol specific rules, such as LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.
> So bind(2) will be checked by both 1 and 2.

Right, I understand your point.

> 
> As example of TCP server, the process will use:
> LANDLOCK_ACCESS_SOCKET_ATTR_{CREATE|BIND|ACCEPT|LISTEN}
> LANDLOCK_ACCESS_NET_{BIND}_TCP
> 
>>>
>>> s/LANDLOCK_ACCESS_SOCKET_CREATE/LANDLOCK_ACCESS_SOCKET_TYPE.
>>>
>>> This implies the kernel will check on socket fd's property (family +
>>> type) at those calls, this applies to
>>> a - the socket fd is created within the process, after landlock is applied.
>>> b - created in process prior to landlock is applied.
>>> c - created out of process then passed into this process,
>>
>> OK, these are the same rules as for LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.
> 
> I don't mean this to be _TCP specific, this is still the family + type
> discussion above.

Yes, I meant that your a/b/c rules would apply for the current 
LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP types as well.

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-30 18:23                     ` Mickaël Salaün
@ 2023-07-05 15:00                       ` Jeff Xu
  2023-07-12 11:30                         ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Jeff Xu @ 2023-07-05 15:00 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Jeff Xu, Günther Noack, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov

On Fri, Jun 30, 2023 at 11:23 AM Mickaël Salaün <mic@digikod.net> wrote:
>
>
> On 30/06/2023 06:18, Jeff Xu wrote:
> > On Thu, Jun 29, 2023 at 4:07 AM Mickaël Salaün <mic@digikod.net> wrote:
> >>
> >>
> >> On 29/06/2023 05:18, Jeff Xu wrote:
> >>> resend.
> >>>
> >>> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
> >>>>
> >>>>
> >>>> On 28/06/2023 19:03, Jeff Xu wrote:
> >>>>> Hello,
> >>>>>
> >>>>> Thanks for writing up the example for an incoming TCP connection ! It
> >>>>> helps with the context.
> >>>>>
> >>>>> Since I'm late to this thread, one thing I want to ask:  all the APIs
> >>>>> proposed so far are at the process level, we don't have any API that
> >>>>> applies restriction to socket fd itself, right ? this is what I
> >>>>> thought, but I would like to get confirmation.
> >>>>
> >>>> Restriction are applied to actions, not to already existing/opened FDs.
> >>>> We could add a way to restrict opened FDs, but I don't think this is the
> >>>> right approach because sandboxing is a deliberate action from a process,
> >>>> and it should already take care of its FDs.
> >>>>
> >>>>
> >>>>>
> >>>>> On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
> >>>>>>
> >>>>>> Hello!
> >>>>>>
> >>>>>> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
> >>>>>>> Here is a design to be able to only allow a set of network protocols and
> >>>>>>> deny everything else. This would be complementary to Konstantin's patch
> >>>>>>> series which addresses fine-grained access control.
> >>>>>>>
> >>>>>>> First, I want to remind that Landlock follows an allowed list approach with
> >>>>>>> a set of (growing) supported actions (for compatibility reasons), which is
> >>>>>>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
> >>>>>>> able to deny everything, which means: supported, not supported, known and
> >>>>>>> unknown protocols.
> >>>>>>>
> >>>>>>> We could add a new "handled_access_socket" field to the landlock_ruleset
> >>>>>>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
> >>>>>>>
> >>>>>>> If this field is set, users could add a new type of rules:
> >>>>>>> struct landlock_socket_attr {
> >>>>>>>        __u64 allowed_access;
> >>>>>>>        int domain; // see socket(2)
> >>>>>>>        int type; // see socket(2)
> >>>>>>> }
> >>>>>>>
> >>>>>>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
> >>>>>>> first, but it could grow with other actions (which cannot be handled with
> >>>>>>> seccomp):
> >>>>>>> - use: walk through all opened FDs and mark them as allowed or denied
> >>>>>>> - receive: hook on received FDs
> >>>>>>> - send: hook on sent FDs
> >>>>>>>
> >>>>>>> We might also use the same approach for non-socket objects that can be
> >>>>>>> identified with some meaningful properties.
> >>>>>>>
> >>>>>>> What do you think?
> >>>>>>
> >>>>>> This sounds like a good plan to me - it would make it possible to restrict new
> >>>>>> socket creation using protocols that were not intended to be used, and I also
> >>>>>> think it would fit the Landlock model nicely.
> >>>>>>
> >>>>>> Small remark on the side: The security_socket_create() hook does not only get
> >>>>>> invoked as a result of socket(2), but also as a part of accept(2) - so this
> >>>>>> approach might already prevent new connections very effectively.
> >>>>>>
> >>>>> That is an interesting aspect that might be worth discussing more.
> >>>>> seccomp is per syscall, landlock doesn't necessarily follow the same,
> >>>>> another design is to add more logic in Landlock, e.g.
> >>>>> LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
> >>>>> calls (socket/bind/listen/accept/connect). App dev might feel it is
> >>>>> easier to use.
> >>>>
> >>>> seccomp restricts the use of the syscall interface, whereas Landlock
> >>>> restricts the use of kernel objects (i.e. the semantic).
> >>>>
> >>>> We need to find a good tradeoff between a lot of access rights and a few
> >>>> grouping different actions. This should make sense from a developer
> >>>> point of view according to its knowledge and use of the kernel
> >>>> interfaces (potential wrapped with high level libraries), but also to
> >>>> the semantic of the sandbox and the security guarantees we want to provide.
> >>>>
> >>>> We should also keep in mind that high level Landlock libraries can take
> >>>> care of potential coarse-grained use of restrictions.
> >>>>
> >>>>
> >>>>>
> >>>>>> Spelling out some scenarios, so that we are sure that we are on the same page:
> >>>>>>
> >>>>>> A)
> >>>>>>
> >>>>>> A program that does not need networking could specify a ruleset where
> >>>>>> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
> >>>>>>
> >>>>>> B)
> >>>>>>
> >>>>>> A program that runs a TCP server could specify a ruleset where
> >>>>>> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
> >>>>>> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
> >>>>>>
> >>>>>>      /* From Konstantin's patch set */
> >>>>>>      struct landlock_net_service_attr bind_attr = {
> >>>>>>        .allowed_access = LANDLOCK_NET_BIND_TCP,
> >>>>>>        .port = 8080,
> >>>>>>      };
> >>>>>>
> >>>>>>      /* From Mickaël's proposal */
> >>>>>>      struct landlock_socket_attr sock_inet_attr = {
> >>>>>>        .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> >>>>>>        .domain = AF_INET,
> >>>>>>        .type = SOCK_STREAM,
> >>>>>>      }
> >>>>>>
> >>>>>>      struct landlock_socket_attr sock_inet6_attr = {
> >>>>>>        .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
> >>>>>>        .domain = AF_INET6,
> >>>>>>         .type = SOCK_STREAM,
> >>>>>>      }
> >>>>>>
> >>>>>> That should then be enough to bind and listen on ports, whereas outgoing
> >>>>>> connections with TCP and anything using other network protocols would not be
> >>>>>> permitted.
> >>>>>>
> >>>>> TCP server is an interesting case. From a security perspective, a
> >>>>> process cares if it is acting as a server or client in TCP, a server
> >>>>> might only want to accept an incoming TCP connection, never initiate
> >>>>> an outgoing TCP connection, and a client is the opposite.
> >>>>>
> >>>>> Processes can restrict outgoing/incoming TCP connection by seccomp for
> >>>>> accept(2) or connect(2),  though I feel Landlock can do this more
> >>>>> naturally for app dev, and at per-protocol level.  seccomp doesn't
> >>>>> provide per-protocol granularity.
> >>>>
> >>>> Right, seccomp cannot filter TCP ports.
> >>>>
> >>>>>
> >>>>> For bind(2), iirc, it can be used for a server to assign dst port of
> >>>>> incoming TCP connection, also by a client to assign a src port of an
> >>>>> outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
> >>>>> cases, right ? this might not be a problem, just something to keep
> >>>>> note.
> >>>>
> >>>> Good point. I think it is in line with the rule definition: to allow to
> >>>> bind on a specific port. However, if clients want to set the source port
> >>>> to a (legitimate) value, then that would be an issue because we cannot
> >>>> allow a whole range of ports (e.g., >= 1024). I'm not sure if this
> >>>> practice would be deemed "legitimate" though. Do you know client
> >>>> applications using bind?

My understanding is that the higher protocol might negotiate and
assign both ports for a new connection (I think SIP does this for
RTP, but that is UDP. I don't know any case for TCP).

> >>>>
> >>>> Konstantin, we should have a test for this case anyway.
> >>
> >> Thinking more about TCP clients binding sockets, a
> >> LANDLOCK_ACCESS_NET_LISTEN_TCP would be more useful than
> >> LANDLOCK_ACCESS_NET_BIND_TCP, but being able to limit the scope of
> >> "bindable" ports is also valuable to forbid a malicious sandboxed
> >> process to impersonate a legitimate server process. This also means that
> >> it might be interesting to be able to handle port ranges.
> >>
> >> We already have a LANDLOCK_ACCESS_NET_BIND_TCP implementation and
> >> related tests, so I think we should proceed with that. The next
> >> network-related patch series should implement this
> >> LANDLOCK_ACCESS_NET_LISTEN_TCP access right though, which should not be
> >> difficult thanks to the framework implemented with current patch series.
> >>
> >> Konstantin, would you like to develop the TCP listening access control
> >> once this patch series land?
> >>
> >>
> >>>>>> (Alternatively, it could bind() the socket early, *then enable Landlock* and
> >>>>>> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
> >>>>>> IPv6, so that listen() and accept() work on the already-bound socket.)
> >>>>>>
> >>>>> For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
> >>>>> so dev is fully aware it is not just applied to socket create.
> >>>>
> >>>> I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does
> >>>> PROTOCOL mean?
> >>>>
> >>> I meant checking family + type of socket, and apply to all of
> >>> socket(2),bind(2),accept(2),connect(2),listen(2), maybe
> >>> send(2)/recv(2) too.
> >>
> >> OK, that would be kind of similar to the LANDLOCK_ACCESS_SOCKET_USE
> >> description. However, I think this kind of global approach has several
> >> issues:
> >> - This covers a lot of different aspects and would increase the cost of
> >> development/testing/review.
> > True.
> >
> >> - Whereas it wraps different actions, it will not let user space have a
> >> fine-grained access control on these, which could be useful for some use
> >> cases.
> > Make sense.
> >
> >> - I don't see the point of restricting accept(2) if we can already
> >> restrict bind(2) and listen(2). accept(2) could be useful to identify
> >> the remote peer but I'm not convinced this would make sense, and if it
> >> would, then this can be postponed until we have a way to identify peers.
> >
> > I was thinking about a case that the socket was created/bind/listen in
> > another process, then passed into the current process,
> >
> > For example:
> > Process A has :
> > LANDLOCK_ACCESS_SOCKET_CREATE (family = f1, type = t1)
> > socket s1 is created in process A with family = f1, type = t1, and
> > bind/listen to port p1.
> >
> > socket s1 is passed to process B
> > Process B has:
> > LANDLOCK_ACCESS_SOCKET_CREATE (family =f1, type = t2) (note the type
> > is different than A)
> > LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP (port = p2)
> >
> > However, those rules in B don't restrict process B from using
> > accept(s1), s1 is another type.
>
> Indeed, but why process A would pass this FD to B? Do you have real use
> cases in mind?
>
> In case of confuse deputy attack, I'm convinced there is much more
> chance for B to just ask A to do nasty thing, no need to receive an FD,
> just to write data to the socket/IPC.
>
Yes. It is the confuse deputy attack I was referring to.
Though, it is more of a design options type of questions than a real
world attack case.

> >
> > In accept(2), struct sockaddr contains sa_family_t (AF_xx)  but no
> > type, which is strange to me, the API should either include both, or
> > none (accept whatever it is already in socket fd, which is set during
> > creation time).
>
> I think sockaddr defines the minimal requirement to deal with
> accept/bind/connect. The sin_family is require to define the type of
> address and port according, but the type is not.
>
> >
> > looking into accept(2) implementation: it calls: sock->ops->accept
> > iiuc, sock->ops is set during socket(2), allowing each protocol to
> > have its own implementation.
> >
> > When we consider a> our intention to restrict family + type of socket,
> > with b> socket can be passed between processes,
> > there can be a need to harden the check (family + type) for all of
> > bind/listen/accept/connect. Otherwise, there is still a possibility
> > that the process to accept a socket of different type unintentionally.
> >
> > This means:
> > LANDLOCK_ACCESS_SOCKET_ATTR_CREATE (family =f1, type = t2)
> > LANDLOCK_ACCESS_SOCKET_ATTR_BIND (family =f1, type = t2)
> > LANDLOCK_ACCESS_SOCKET_ATTR_ACCEPT (family =f1, type = t2)
> > LANDLOCK_ACCESS_SOCKET_ATTR_ LISTEN (family =f1, type = t2)
> > LANDLOCK_ACCESS_SOCKET_ATTR_CONNECT (family =f1, type = t2)
> > Note: this checks family+type only, not port.
> > The check is applied to all protocols, so not specific to TCP/UDP
>
> The sandboxing/Landlock threat model is to restrict a process when it is
> sandboxed, but this sandboxing is a request from the same process (or
> one of its parent) that happen when it is more trustworthy (or at least
> has more privileges) than after it sandbox itself.
>
> The process sandboxing itself can use several kernel features, and one
> of it is Landlock. In any case, it should take care of closing file
> descriptors that should not be passed to the sandboxed process.
>
Agree.

> The limits of sandboxing are the communication channels from and to
> outside the sandbox. The peers talking with sandboxed processes should
> then not be subject to confused deputy attacks, which means they must
> not enable to bypass the user-defined security policy (from which the
> Landlock policy is only a part). Receiving file descriptors should then
> not be more important than controlling the communication channels. If a
> not-sandboxed process is willing to give more right to a sandboxed
> process, by passing FDs or just receiving commands, then this
> not-sandboxed process need to be fixed.
>
> This is the rationale to not care about received nor sent file
> descriptors. The communication channels and the remote peers must be
> trusted to not give more privileges to the sandboxed processes.
>
> If a peer is malicious, it doesn't need to pass a file descriptor to the
> sandboxed process, it can just read (data) commands and apply them to
> its file descriptors.

I see the reasoning. i.e. sandboxing the process is not more
important than securing communication channels, or securing the peer.

So in a system that let a peer process to pass a socket into a
higher privileged process, when the communication channel or the peer
process is compromised,  e.g. swapping the fd/socket into a different
one that the attacker controls, confuse deputy attack can happen. The
recommendation here is to secure peer and communication.
I agree with this approach in general.  I need to think about how it
applies to specific cases.

> I think the ability to pass file descriptors
> should be seen as a way to improve performance by avoiding a user space
> process to act as a proxy receiving read/write commands and managing
> file descriptors itself. On the other hand, file descriptors could be
> used as real capabilities/tokens to manage access, but senders still
> need to be careful to only pass the required ones.
>
> All this to say that being able to restrict actions on file descriptors
> would be useful for senders/services to send a subset of the file
> descriptor capabilities (cf. Capsicum), but not the other way around.
>
In the Landlock kernel doc:
Similarly to file access modes (e.g. O_RDWR), Landlock access rights
attached to file descriptors are retained even if they are passed
between processes (e.g. through a Unix domain socket). Such access
rights will then be enforced even if the receiving process is not
sandboxed by Landlock. Indeed, this is required to keep a consistent
access control over the whole system, and this avoids unattended
bypasses through file descriptor passing (i.e. confused deputy
attack).

iiuc, the design for file and socket in landlock is different. For
socket, the access rules are applied only to the current process (more
like seccomp), while for file restriction, the rules can be passed
into another un-landlocked process.

>
> >
> >> - For performance reasons, we should avoid restricting
> >> send/recv/read/write but instead only restrict the control plane: object
> >> creation and configuration.
> >>
> > Performance is a valid concern.
> >
> > As example of server, usually the main process listens/accepts incoming
> > connections, and forked processes do send/recv, the main process can
> > be viewed as a control plane, and send/recv can be viewed as a data
> > plane. It makes sense that we start with the control plane.
> >
> > We might like to keep a note that by not restricting send/recv, a
> > socket can be created OOP, then passed into current process and call
> > send/recv, so the network is not fully disabled by landlock alone
> > (still need seccomp)
>
> Right, the kernel (and then Landlock) is not enough to sandbox a
> complete environment, user space needs to be aware and be configured for
> that too.
>
> I understand the desire to restrict as much as possible, but this
> require to add more code and then it increase the risk of bugs, whereas
> it might not be a big deal for attackers. I don't think the cost is
> worth it and I don't want to give a false sense of security that could
> let users think their application cannot communicate with the network if
> it can communicate with local processes connected to the network.
>
Agree with the cost/benefit concern.

I think the current design is already good enough for the
decompression program case  in
https://cr.yp.to/unix/disablenetwork.html, as mentioned in Günther's
response.

> >
> > Things might get more complicated, say: a forked process is intended
> > to send/recv UDP, but was confused and got a TCP socket from
> > OOP, etc. This is not different than accept(2) case above. There might
> > be an opportunity for Landlock to harden this when we design for
> > data-plane.
> >
> >> I'm not convinced that being able to control all kind of socket bind,
> >> listen and connect actions might be worth implementing instead of a
> >> fine-grained access control for the main protocols (TCP, UDP, unix and
> >> vsock maybe), with the related tests and guarantees.
> >>
> >> However, this landlock_socket_attr struct could have an allowed_access
> >> field that could contain LANDLOCK_ACCESS_NET_{CONNECT,LISTEN,BIND}_TCP
> >> rights (which would just not be constrained by any port, except if a
> >> landlock_net_port_attr rule matches). It would then make sense to rename
> >> LANDLOCK_ACCESS_SOCKET_CREATE to LANDLOCK_ACCESS_NET_CREATE_SOCKET. This
> >> right would not be accepted in a landlock_net_port_attr.allowed_access
> >> though.
> >>
> > I'm not sure if my view is fully explained. I don't mean to control
> > all kinds of socket bind/listen/connect actions.
> > My view is:
> > 1> have a rule to check family + type, to make sure the process is
> > using the socket type they intend to use, such as
> > LANDLOCK_ACCESS_SOCKET_ATTR_{CREATE|CONNECT|BIND|ACCEPT|LISTEN}, as
> > discussed in accept(2) case.
> > 2> have protocol specific rules, such as LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.
> > So bind(2) will be checked by both 1 and 2.
>
> Right, I understand your point.
>
Great ! Thanks a lot for the discussion !
















> >
> > As example of TCP server, the process will use:
> > LANDLOCK_ACCESS_SOCKET_ATTR_{CREATE|BIND|ACCEPT|LISTEN}
> > LANDLOCK_ACCESS_NET_{BIND}_TCP
> >
> >>>
> >>> s/LANDLOCK_ACCESS_SOCKET_CREATE/LANDLOCK_ACCESS_SOCKET_TYPE.
> >>>
> >>> This implies the kernel will check on socket fd's property (family +
> >>> type) at those calls, this applies to
> >>> a - the socket fd is created within the process, after landlock is applied.
> >>> b - created in process prior to landlock is applied.
> >>> c - created out of process then passed into this process,
> >>
> >> OK, these are the same rules as for LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.
> >
> > I don't mean this to be _TCP specific, this is still the family + type
> > discussion above.
>
> Yes, I meant that your a/b/c rules would apply for the current
> LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP types as well.

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-07-05 15:00                       ` Jeff Xu
@ 2023-07-12 11:30                         ` Mickaël Salaün
  2023-07-13 13:20                           ` Konstantin Meskhidze (A)
  0 siblings, 1 reply; 74+ messages in thread
From: Mickaël Salaün @ 2023-07-12 11:30 UTC (permalink / raw)
  To: Jeff Xu
  Cc: Jeff Xu, Günther Noack, Konstantin Meskhidze (A),
	Günther Noack, willemdebruijn.kernel, linux-security-module,
	netdev, netfilter-devel, yusongping, artem.kuzin,
	Jorge Lucangeli Obes, Allen Webb, Dmitry Torokhov


On 05/07/2023 17:00, Jeff Xu wrote:
> On Fri, Jun 30, 2023 at 11:23 AM Mickaël Salaün <mic@digikod.net> wrote:
>>
>>
>> On 30/06/2023 06:18, Jeff Xu wrote:
>>> On Thu, Jun 29, 2023 at 4:07 AM Mickaël Salaün <mic@digikod.net> wrote:
>>>>
>>>>
>>>> On 29/06/2023 05:18, Jeff Xu wrote:
>>>>> resend.
>>>>>
>>>>> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>>>>>>
>>>>>>
>>>>>> On 28/06/2023 19:03, Jeff Xu wrote:

[...]

>> The sandboxing/Landlock threat model is to restrict a process when it is
>> sandboxed, but this sandboxing is a request from the same process (or
>> one of its parent) that happen when it is more trustworthy (or at least
>> has more privileges) than after it sandbox itself.
>>
>> The process sandboxing itself can use several kernel features, and one
>> of it is Landlock. In any case, it should take care of closing file
>> descriptors that should not be passed to the sandboxed process.
>>
> Agree.
> 
>> The limits of sandboxing are the communication channels from and to
>> outside the sandbox. The peers talking with sandboxed processes should
>> then not be subject to confused deputy attacks, which means they must
>> not enable to bypass the user-defined security policy (from which the
>> Landlock policy is only a part). Receiving file descriptors should then
>> not be more important than controlling the communication channels. If a
>> not-sandboxed process is willing to give more right to a sandboxed
>> process, by passing FDs or just receiving commands, then this
>> not-sandboxed process need to be fixed.
>>
>> This is the rationale to not care about received nor sent file
>> descriptors. The communication channels and the remote peers must be
>> trusted to not give more privileges to the sandboxed processes.
>>
>> If a peer is malicious, it doesn't need to pass a file descriptor to the
>> sandboxed process, it can just read (data) commands and apply them to
>> its file descriptors.
> 
> I see the reasoning. i.e. sandboxing the process is not more
> important than securing communication channels, or securing the peer.
> 
> So in a system that let a peer process to pass a socket into a
> higher privileged process, when the communication channel or the peer
> process is compromised,  e.g. swapping the fd/socket into a different
> one that the attacker controls, confuse deputy attack can happen. The
> recommendation here is to secure peer and communication.
> I agree with this approach in general.  I need to think about how it
> applies to specific cases.
> 
>> I think the ability to pass file descriptors
>> should be seen as a way to improve performance by avoiding a user space
>> process to act as a proxy receiving read/write commands and managing
>> file descriptors itself. On the other hand, file descriptors could be
>> used as real capabilities/tokens to manage access, but senders still
>> need to be careful to only pass the required ones.
>>
>> All this to say that being able to restrict actions on file descriptors
>> would be useful for senders/services to send a subset of the file
>> descriptor capabilities (cf. Capsicum), but not the other way around.
>>
> In the Landlock kernel doc:
> Similarly to file access modes (e.g. O_RDWR), Landlock access rights
> attached to file descriptors are retained even if they are passed
> between processes (e.g. through a Unix domain socket). Such access
> rights will then be enforced even if the receiving process is not
> sandboxed by Landlock. Indeed, this is required to keep a consistent
> access control over the whole system, and this avoids unattended
> bypasses through file descriptor passing (i.e. confused deputy
> attack).
> 
> iiuc, the design for file and socket in landlock is different. For
> socket, the access rules are applied only to the current process (more
> like seccomp), while for file restriction, the rules can be passed
> into another un-landlocked process.

The O_RDWR restrictions are enforced by the basic kernel access control, 
not Landlock. However, for file truncation, Landlock complements the 
basic kernel access rights and behave the same.

There is indeed slight differences between file system and socket 
restrictions. For the file system, a file descriptor is a direct access 
to a file/data. For the network, we cannot identify for which data/peer 
a newly created socket will give access to, we need to wait for a 
connect or bind request to identify the use case for this socket. We 
could tie the access rights (related to ports) to an opened socket, but 
this would not align with the way Landlock access control works for the 
file system. Indeed, a directory file descriptor may enable to open 
another file (i.e. a new data item), but this opening is restricted by 
Landlock. A newly created socket gives access to the network (or a 
subset of it), but binding or connecting to a peer (i.e. accessing new 
data) is restricted by Landlock. Accesses tied to FDs are those that 
enable to get access to the underlying data (e.g. read, write, 
truncate). A newly created socket is harmless until it is connected to a 
peer, similarly to a memfd file descriptor. A directory opened by a 
sandboxed process can be passed to a process outside this sandbox and it 
might be allowed to open a relative path/file, which might not be the 
case for the sandboxed process.

I think it might be summarize by the difference between underlying FD 
data in the case of a regular file (i.e. tied access rights), and 
relative new data in the case of a directory or a socket (i.e. 
sandboxing policy scope).

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-06-29 11:07                 ` Mickaël Salaün
  2023-06-30  4:18                   ` Jeff Xu
@ 2023-07-13 11:44                   ` Konstantin Meskhidze (A)
  1 sibling, 0 replies; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-07-13 11:44 UTC (permalink / raw)
  To: Mickaël Salaün, Jeff Xu
  Cc: Günther Noack, Günther Noack, willemdebruijn.kernel,
	linux-security-module, netdev, netfilter-devel, yusongping,
	artem.kuzin, Jeff Xu, Jorge Lucangeli Obes, Allen Webb,
	Dmitry Torokhov



6/29/2023 2:07 PM, Mickaël Salaün пишет:
> 
> On 29/06/2023 05:18, Jeff Xu wrote:
>> resend.
>> 
>> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>>>
>>>
>>> On 28/06/2023 19:03, Jeff Xu wrote:
>>>> Hello,
>>>>
>>>> Thanks for writing up the example for an incoming TCP connection ! It
>>>> helps with the context.
>>>>
>>>> Since I'm late to this thread, one thing I want to ask:  all the APIs
>>>> proposed so far are at the process level, we don't have any API that
>>>> applies restriction to socket fd itself, right ? this is what I
>>>> thought, but I would like to get confirmation.
>>>
>>> Restriction are applied to actions, not to already existing/opened FDs.
>>> We could add a way to restrict opened FDs, but I don't think this is the
>>> right approach because sandboxing is a deliberate action from a process,
>>> and it should already take care of its FDs.
>>>
>>>
>>>>
>>>> On Wed, Jun 28, 2023 at 2:09 AM Günther Noack <gnoack@google.com> wrote:
>>>>>
>>>>> Hello!
>>>>>
>>>>> On Mon, Jun 26, 2023 at 05:29:34PM +0200, Mickaël Salaün wrote:
>>>>>> Here is a design to be able to only allow a set of network protocols and
>>>>>> deny everything else. This would be complementary to Konstantin's patch
>>>>>> series which addresses fine-grained access control.
>>>>>>
>>>>>> First, I want to remind that Landlock follows an allowed list approach with
>>>>>> a set of (growing) supported actions (for compatibility reasons), which is
>>>>>> kind of an allow-list-on-a-deny-list. But with this proposal, we want to be
>>>>>> able to deny everything, which means: supported, not supported, known and
>>>>>> unknown protocols.
>>>>>>
>>>>>> We could add a new "handled_access_socket" field to the landlock_ruleset
>>>>>> struct, which could contain a LANDLOCK_ACCESS_SOCKET_CREATE flag.
>>>>>>
>>>>>> If this field is set, users could add a new type of rules:
>>>>>> struct landlock_socket_attr {
>>>>>>       __u64 allowed_access;
>>>>>>       int domain; // see socket(2)
>>>>>>       int type; // see socket(2)
>>>>>> }
>>>>>>
>>>>>> The allowed_access field would only contain LANDLOCK_ACCESS_SOCKET_CREATE at
>>>>>> first, but it could grow with other actions (which cannot be handled with
>>>>>> seccomp):
>>>>>> - use: walk through all opened FDs and mark them as allowed or denied
>>>>>> - receive: hook on received FDs
>>>>>> - send: hook on sent FDs
>>>>>>
>>>>>> We might also use the same approach for non-socket objects that can be
>>>>>> identified with some meaningful properties.
>>>>>>
>>>>>> What do you think?
>>>>>
>>>>> This sounds like a good plan to me - it would make it possible to restrict new
>>>>> socket creation using protocols that were not intended to be used, and I also
>>>>> think it would fit the Landlock model nicely.
>>>>>
>>>>> Small remark on the side: The security_socket_create() hook does not only get
>>>>> invoked as a result of socket(2), but also as a part of accept(2) - so this
>>>>> approach might already prevent new connections very effectively.
>>>>>
>>>> That is an interesting aspect that might be worth discussing more.
>>>> seccomp is per syscall, landlock doesn't necessarily follow the same,
>>>> another design is to add more logic in Landlock, e.g.
>>>> LANDLOCK_ACCESS_SOCKET_PROTOCOL which will apply to all of the socket
>>>> calls (socket/bind/listen/accept/connect). App dev might feel it is
>>>> easier to use.
>>>
>>> seccomp restricts the use of the syscall interface, whereas Landlock
>>> restricts the use of kernel objects (i.e. the semantic).
>>>
>>> We need to find a good tradeoff between a lot of access rights and a few
>>> grouping different actions. This should make sense from a developer
>>> point of view according to its knowledge and use of the kernel
>>> interfaces (potential wrapped with high level libraries), but also to
>>> the semantic of the sandbox and the security guarantees we want to provide.
>>>
>>> We should also keep in mind that high level Landlock libraries can take
>>> care of potential coarse-grained use of restrictions.
>>>
>>>
>>>>
>>>>> Spelling out some scenarios, so that we are sure that we are on the same page:
>>>>>
>>>>> A)
>>>>>
>>>>> A program that does not need networking could specify a ruleset where
>>>>> LANDLOCK_ACCESS_SOCKET_CREATE is handled, and simply not permit anything.
>>>>>
>>>>> B)
>>>>>
>>>>> A program that runs a TCP server could specify a ruleset where
>>>>> LANDLOCK_NET_BIND_TCP, LANDLOCK_NET_CONNECT_TCP and
>>>>> LANDLOCK_ACCESS_SOCKET_CREATE are handled, and where the following rules are added:
>>>>>
>>>>>     /* From Konstantin's patch set */
>>>>>     struct landlock_net_service_attr bind_attr = {
>>>>>       .allowed_access = LANDLOCK_NET_BIND_TCP,
>>>>>       .port = 8080,
>>>>>     };
>>>>>
>>>>>     /* From Mickaël's proposal */
>>>>>     struct landlock_socket_attr sock_inet_attr = {
>>>>>       .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>>>>       .domain = AF_INET,
>>>>>       .type = SOCK_STREAM,
>>>>>     }
>>>>>
>>>>>     struct landlock_socket_attr sock_inet6_attr = {
>>>>>       .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE,
>>>>>       .domain = AF_INET6,
>>>>>        .type = SOCK_STREAM,
>>>>>     }
>>>>>
>>>>> That should then be enough to bind and listen on ports, whereas outgoing
>>>>> connections with TCP and anything using other network protocols would not be
>>>>> permitted.
>>>>>
>>>> TCP server is an interesting case. From a security perspective, a
>>>> process cares if it is acting as a server or client in TCP, a server
>>>> might only want to accept an incoming TCP connection, never initiate
>>>> an outgoing TCP connection, and a client is the opposite.
>>>>
>>>> Processes can restrict outgoing/incoming TCP connection by seccomp for
>>>> accept(2) or connect(2),  though I feel Landlock can do this more
>>>> naturally for app dev, and at per-protocol level.  seccomp doesn't
>>>> provide per-protocol granularity.
>>>
>>> Right, seccomp cannot filter TCP ports.
>>>
>>>>
>>>> For bind(2), iirc, it can be used for a server to assign dst port of
>>>> incoming TCP connection, also by a client to assign a src port of an
>>>> outgoing TCP connection. LANDLOCK_NET_BIND_TCP will apply to both
>>>> cases, right ? this might not be a problem, just something to keep
>>>> note.
>>>
>>> Good point. I think it is in line with the rule definition: to allow to
>>> bind on a specific port. However, if clients want to set the source port
>>> to a (legitimate) value, then that would be an issue because we cannot
>>> allow a whole range of ports (e.g., >= 1024). I'm not sure if this
>>> practice would be deemed "legitimate" though. Do you know client
>>> applications using bind?
>>>
>>> Konstantin, we should have a test for this case anyway.
> 
> Thinking more about TCP clients binding sockets, a
> LANDLOCK_ACCESS_NET_LISTEN_TCP would be more useful than
> LANDLOCK_ACCESS_NET_BIND_TCP, but being able to limit the scope of
> "bindable" ports is also valuable to forbid a malicious sandboxed
> process to impersonate a legitimate server process. This also means that
> it might be interesting to be able to handle port ranges.
> 
> We already have a LANDLOCK_ACCESS_NET_BIND_TCP implementation and
> related tests, so I think we should proceed with that. The next
> network-related patch series should implement this
> LANDLOCK_ACCESS_NET_LISTEN_TCP access right though, which should not be
> difficult thanks to the framework implemented with current patch series.
> 
> Konstantin, would you like to develop the TCP listening access control
> once this patch series land?

  Hi all,
  Sorry for the late reply. I think this access control would be useful.
  I would like to add LANDLOCK_ACCESS_NET_LISTEN_TCP access right in 
future patches.




> 
> 
>>>>> (Alternatively, it could bind() the socket early, *then enable Landlock* and
>>>>> leave out the rule for BIND_TCP, only permitting SOCKET_CREATE for IPv4 and
>>>>> IPv6, so that listen() and accept() work on the already-bound socket.)
>>>>>
>>>> For this approach, LANDLOCK_ACCESS_SOCKET_PROTOCOL is a better name,
>>>> so dev is fully aware it is not just applied to socket create.
>>>
>>> I don't get the semantic of LANDLOCK_ACCESS_SOCKET_PROTOCOL. What does
>>> PROTOCOL mean?
>>>
>> I meant checking family + type of socket, and apply to all of
>> socket(2),bind(2),accept(2),connect(2),listen(2), maybe
>> send(2)/recv(2) too.
> 
> OK, that would be kind of similar to the LANDLOCK_ACCESS_SOCKET_USE
> description. However, I think this kind of global approach has several
> issues:
> - This covers a lot of different aspects and would increase the cost of
> development/testing/review.
> - Whereas it wraps different actions, it will not let user space have a
> fine-grained access control on these, which could be useful for some use
> cases.
> - I don't see the point of restricting accept(2) if we can already
> restrict bind(2) and listen(2). accept(2) could be useful to identify
> the remote peer but I'm not convinced this would make sense, and if it
> would, then this can be postponed until we have a way to identify peers.
> - For performance reasons, we should avoid restricting
> send/recv/read/write but instead only restrict the control plane: object
> creation and configuration.

   I agree. I'm not sure about restricting the data plane here. We have 
to restrict connection making, not data transfering when connection has 
been established.
> 
> I'm not convinced that being able to control all kind of socket bind,
> listen and connect actions might be worth implementing instead of a
> fine-grained access control for the main protocols (TCP, UDP, unix and
> vsock maybe), with the related tests and guarantees.
> 
> However, this landlock_socket_attr struct could have an allowed_access
> field that could contain LANDLOCK_ACCESS_NET_{CONNECT,LISTEN,BIND}_TCP
> rights (which would just not be constrained by any port, except if a
> landlock_net_port_attr rule matches). It would then make sense to rename
> LANDLOCK_ACCESS_SOCKET_CREATE to LANDLOCK_ACCESS_NET_CREATE_SOCKET. This
> right would not be accepted in a landlock_net_port_attr.allowed_access
> though.
> 
>> 
>> s/LANDLOCK_ACCESS_SOCKET_CREATE/LANDLOCK_ACCESS_SOCKET_TYPE.
>> 
>> This implies the kernel will check on socket fd's property (family +
>> type) at those calls, this applies to
>> a - the socket fd is created within the process, after landlock is applied.
>> b - created in process prior to landlock is applied.
>> c - created out of process then passed into this process,
> 
> OK, these are the same rules as for LANDLOCK_ACCESS_NET_{CONNECT,BIND}_TCP.
> .

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-07-12 11:30                         ` Mickaël Salaün
@ 2023-07-13 13:20                           ` Konstantin Meskhidze (A)
  2023-07-13 14:52                             ` Mickaël Salaün
  0 siblings, 1 reply; 74+ messages in thread
From: Konstantin Meskhidze (A) @ 2023-07-13 13:20 UTC (permalink / raw)
  To: Mickaël Salaün, Jeff Xu
  Cc: Jeff Xu, Günther Noack, Günther Noack,
	willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin, Jorge Lucangeli Obes,
	Allen Webb, Dmitry Torokhov



7/12/2023 2:30 PM, Mickaël Salaün пишет:
> 
> On 05/07/2023 17:00, Jeff Xu wrote:
>> On Fri, Jun 30, 2023 at 11:23 AM Mickaël Salaün <mic@digikod.net> wrote:
>>>
>>>
>>> On 30/06/2023 06:18, Jeff Xu wrote:
>>>> On Thu, Jun 29, 2023 at 4:07 AM Mickaël Salaün <mic@digikod.net> wrote:
>>>>>
>>>>>
>>>>> On 29/06/2023 05:18, Jeff Xu wrote:
>>>>>> resend.
>>>>>>
>>>>>> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>>>>>>>
>>>>>>>
>>>>>>> On 28/06/2023 19:03, Jeff Xu wrote:
> 
> [...]
> 
>>> The sandboxing/Landlock threat model is to restrict a process when it is
>>> sandboxed, but this sandboxing is a request from the same process (or
>>> one of its parent) that happen when it is more trustworthy (or at least
>>> has more privileges) than after it sandbox itself.
>>>
>>> The process sandboxing itself can use several kernel features, and one
>>> of it is Landlock. In any case, it should take care of closing file
>>> descriptors that should not be passed to the sandboxed process.
>>>
>> Agree.
>> 
>>> The limits of sandboxing are the communication channels from and to
>>> outside the sandbox. The peers talking with sandboxed processes should
>>> then not be subject to confused deputy attacks, which means they must
>>> not enable to bypass the user-defined security policy (from which the
>>> Landlock policy is only a part). Receiving file descriptors should then
>>> not be more important than controlling the communication channels. If a
>>> not-sandboxed process is willing to give more right to a sandboxed
>>> process, by passing FDs or just receiving commands, then this
>>> not-sandboxed process need to be fixed.
>>>
>>> This is the rationale to not care about received nor sent file
>>> descriptors. The communication channels and the remote peers must be
>>> trusted to not give more privileges to the sandboxed processes.
>>>
>>> If a peer is malicious, it doesn't need to pass a file descriptor to the
>>> sandboxed process, it can just read (data) commands and apply them to
>>> its file descriptors.
>> 
>> I see the reasoning. i.e. sandboxing the process is not more
>> important than securing communication channels, or securing the peer.
>> 
>> So in a system that let a peer process to pass a socket into a
>> higher privileged process, when the communication channel or the peer
>> process is compromised,  e.g. swapping the fd/socket into a different
>> one that the attacker controls, confuse deputy attack can happen. The
>> recommendation here is to secure peer and communication.
>> I agree with this approach in general.  I need to think about how it
>> applies to specific cases.
>> 
>>> I think the ability to pass file descriptors
>>> should be seen as a way to improve performance by avoiding a user space
>>> process to act as a proxy receiving read/write commands and managing
>>> file descriptors itself. On the other hand, file descriptors could be
>>> used as real capabilities/tokens to manage access, but senders still
>>> need to be careful to only pass the required ones.
>>>
>>> All this to say that being able to restrict actions on file descriptors
>>> would be useful for senders/services to send a subset of the file
>>> descriptor capabilities (cf. Capsicum), but not the other way around.
>>>
>> In the Landlock kernel doc:
>> Similarly to file access modes (e.g. O_RDWR), Landlock access rights
>> attached to file descriptors are retained even if they are passed
>> between processes (e.g. through a Unix domain socket). Such access
>> rights will then be enforced even if the receiving process is not
>> sandboxed by Landlock. Indeed, this is required to keep a consistent
>> access control over the whole system, and this avoids unattended
>> bypasses through file descriptor passing (i.e. confused deputy
>> attack).
>> 
>> iiuc, the design for file and socket in landlock is different. For
>> socket, the access rules are applied only to the current process (more
>> like seccomp), while for file restriction, the rules can be passed
>> into another un-landlocked process.
> 
> The O_RDWR restrictions are enforced by the basic kernel access control,
> not Landlock. However, for file truncation, Landlock complements the
> basic kernel access rights and behave the same.
> 
> There is indeed slight differences between file system and socket
> restrictions. For the file system, a file descriptor is a direct access
> to a file/data. For the network, we cannot identify for which data/peer
> a newly created socket will give access to, we need to wait for a
> connect or bind request to identify the use case for this socket. We
> could tie the access rights (related to ports) to an opened socket, but
> this would not align with the way Landlock access control works for the
> file system. Indeed, a directory file descriptor may enable to open
> another file (i.e. a new data item), but this opening is restricted by
> Landlock. A newly created socket gives access to the network (or a
> subset of it), but binding or connecting to a peer (i.e. accessing new
> data) is restricted by Landlock. Accesses tied to FDs are those that
> enable to get access to the underlying data (e.g. read, write,
> truncate). A newly created socket is harmless until it is connected to a
> peer, similarly to a memfd file descriptor. A directory opened by a
> sandboxed process can be passed to a process outside this sandbox and it
> might be allowed to open a relative path/file, which might not be the
> case for the sandboxed process.

   I would like to mention that in case of files a Landlock rule is tied 
to undreliying file's inode ( already existing at the moment of creating
    a landlock's rule), and it's impossible to tie a new landlock rule 
to a socket before it's creating. Thats why all network access rules 
work with "port objects", representing network connections.

I was thinking about sendind socket's FD to another process.
If one process creates a socket and binds it to some port N. Then it 
sends socket's FD to a landlocked process with rule restricting to bind
to port N. Is this situation theoretically possible???


> 
> I think it might be summarize by the difference between underlying FD
> data in the case of a regular file (i.e. tied access rights), and
> relative new data in the case of a directory or a socket (i.e.
> sandboxing policy scope).
> .

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

* Re: [PATCH v9 00/12] Network support for Landlock - allowed list of protocols
  2023-07-13 13:20                           ` Konstantin Meskhidze (A)
@ 2023-07-13 14:52                             ` Mickaël Salaün
  0 siblings, 0 replies; 74+ messages in thread
From: Mickaël Salaün @ 2023-07-13 14:52 UTC (permalink / raw)
  To: Konstantin Meskhidze (A), Jeff Xu
  Cc: Jeff Xu, Günther Noack, Günther Noack,
	willemdebruijn.kernel, linux-security-module, netdev,
	netfilter-devel, yusongping, artem.kuzin, Jorge Lucangeli Obes,
	Allen Webb, Dmitry Torokhov


On 13/07/2023 15:20, Konstantin Meskhidze (A) wrote:
> 
> 
> 7/12/2023 2:30 PM, Mickaël Salaün пишет:
>>
>> On 05/07/2023 17:00, Jeff Xu wrote:
>>> On Fri, Jun 30, 2023 at 11:23 AM Mickaël Salaün <mic@digikod.net> wrote:
>>>>
>>>>
>>>> On 30/06/2023 06:18, Jeff Xu wrote:
>>>>> On Thu, Jun 29, 2023 at 4:07 AM Mickaël Salaün <mic@digikod.net> wrote:
>>>>>>
>>>>>>
>>>>>> On 29/06/2023 05:18, Jeff Xu wrote:
>>>>>>> resend.
>>>>>>>
>>>>>>> On Wed, Jun 28, 2023 at 12:29 PM Mickaël Salaün <mic@digikod.net> wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>> On 28/06/2023 19:03, Jeff Xu wrote:
>>
>> [...]
>>
>>>> The sandboxing/Landlock threat model is to restrict a process when it is
>>>> sandboxed, but this sandboxing is a request from the same process (or
>>>> one of its parent) that happen when it is more trustworthy (or at least
>>>> has more privileges) than after it sandbox itself.
>>>>
>>>> The process sandboxing itself can use several kernel features, and one
>>>> of it is Landlock. In any case, it should take care of closing file
>>>> descriptors that should not be passed to the sandboxed process.
>>>>
>>> Agree.
>>>
>>>> The limits of sandboxing are the communication channels from and to
>>>> outside the sandbox. The peers talking with sandboxed processes should
>>>> then not be subject to confused deputy attacks, which means they must
>>>> not enable to bypass the user-defined security policy (from which the
>>>> Landlock policy is only a part). Receiving file descriptors should then
>>>> not be more important than controlling the communication channels. If a
>>>> not-sandboxed process is willing to give more right to a sandboxed
>>>> process, by passing FDs or just receiving commands, then this
>>>> not-sandboxed process need to be fixed.
>>>>
>>>> This is the rationale to not care about received nor sent file
>>>> descriptors. The communication channels and the remote peers must be
>>>> trusted to not give more privileges to the sandboxed processes.
>>>>
>>>> If a peer is malicious, it doesn't need to pass a file descriptor to the
>>>> sandboxed process, it can just read (data) commands and apply them to
>>>> its file descriptors.
>>>
>>> I see the reasoning. i.e. sandboxing the process is not more
>>> important than securing communication channels, or securing the peer.
>>>
>>> So in a system that let a peer process to pass a socket into a
>>> higher privileged process, when the communication channel or the peer
>>> process is compromised,  e.g. swapping the fd/socket into a different
>>> one that the attacker controls, confuse deputy attack can happen. The
>>> recommendation here is to secure peer and communication.
>>> I agree with this approach in general.  I need to think about how it
>>> applies to specific cases.
>>>
>>>> I think the ability to pass file descriptors
>>>> should be seen as a way to improve performance by avoiding a user space
>>>> process to act as a proxy receiving read/write commands and managing
>>>> file descriptors itself. On the other hand, file descriptors could be
>>>> used as real capabilities/tokens to manage access, but senders still
>>>> need to be careful to only pass the required ones.
>>>>
>>>> All this to say that being able to restrict actions on file descriptors
>>>> would be useful for senders/services to send a subset of the file
>>>> descriptor capabilities (cf. Capsicum), but not the other way around.
>>>>
>>> In the Landlock kernel doc:
>>> Similarly to file access modes (e.g. O_RDWR), Landlock access rights
>>> attached to file descriptors are retained even if they are passed
>>> between processes (e.g. through a Unix domain socket). Such access
>>> rights will then be enforced even if the receiving process is not
>>> sandboxed by Landlock. Indeed, this is required to keep a consistent
>>> access control over the whole system, and this avoids unattended
>>> bypasses through file descriptor passing (i.e. confused deputy
>>> attack).
>>>
>>> iiuc, the design for file and socket in landlock is different. For
>>> socket, the access rules are applied only to the current process (more
>>> like seccomp), while for file restriction, the rules can be passed
>>> into another un-landlocked process.
>>
>> The O_RDWR restrictions are enforced by the basic kernel access control,
>> not Landlock. However, for file truncation, Landlock complements the
>> basic kernel access rights and behave the same.
>>
>> There is indeed slight differences between file system and socket
>> restrictions. For the file system, a file descriptor is a direct access
>> to a file/data. For the network, we cannot identify for which data/peer
>> a newly created socket will give access to, we need to wait for a
>> connect or bind request to identify the use case for this socket. We
>> could tie the access rights (related to ports) to an opened socket, but
>> this would not align with the way Landlock access control works for the
>> file system. Indeed, a directory file descriptor may enable to open
>> another file (i.e. a new data item), but this opening is restricted by
>> Landlock. A newly created socket gives access to the network (or a
>> subset of it), but binding or connecting to a peer (i.e. accessing new
>> data) is restricted by Landlock. Accesses tied to FDs are those that
>> enable to get access to the underlying data (e.g. read, write,
>> truncate). A newly created socket is harmless until it is connected to a
>> peer, similarly to a memfd file descriptor. A directory opened by a
>> sandboxed process can be passed to a process outside this sandbox and it
>> might be allowed to open a relative path/file, which might not be the
>> case for the sandboxed process.
> 
>     I would like to mention that in case of files a Landlock rule is tied
> to undreliying file's inode ( already existing at the moment of creating
>      a landlock's rule), and it's impossible to tie a new landlock rule
> to a socket before it's creating. Thats why all network access rules
> work with "port objects", representing network connections.

Correct, even if a port is not a *kernel* object.

> 
> I was thinking about sendind socket's FD to another process.
> If one process creates a socket and binds it to some port N. Then it
> sends socket's FD to a landlocked process with rule restricting to bind
> to port N. Is this situation theoretically possible???

Yes, it's possible an it's OK because the bind action was performed by 
an unsandboxed process. If this unsandboxed process is not trusted or if 
it can be fooled by a malicious client, the system should be designed to 
make it not able to talk to the sandboxed process.

> 
> 
>>
>> I think it might be summarize by the difference between underlying FD
>> data in the case of a regular file (i.e. tied access rights), and
>> relative new data in the case of a directory or a socket (i.e.
>> sandboxing policy scope).
>> .

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

end of thread, other threads:[~2023-07-13 14:53 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-16  8:58 [PATCH v9 00/12] Network support for Landlock Konstantin Meskhidze
2023-01-16  8:58 ` [PATCH v9 01/12] landlock: Make ruleset's access masks more generic Konstantin Meskhidze
2023-01-16  8:58 ` [PATCH v9 02/12] landlock: Allow filesystem layout changes for domains without such rule type Konstantin Meskhidze
2023-02-10 17:34   ` Mickaël Salaün
2023-02-14  8:51     ` Konstantin Meskhidze (A)
2023-02-14 12:07       ` Mickaël Salaün
2023-02-14 12:57         ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 03/12] landlock: Refactor landlock_find_rule/insert_rule Konstantin Meskhidze
2023-02-10 17:36   ` Mickaël Salaün
2023-02-14 10:15     ` Konstantin Meskhidze (A)
2023-02-14 12:09       ` Mickaël Salaün
2023-02-14 13:28         ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 04/12] landlock: Refactor merge/inherit_ruleset functions Konstantin Meskhidze
2023-01-16  8:58 ` [PATCH v9 05/12] landlock: Move and rename umask_layers() and init_layer_masks() Konstantin Meskhidze
2023-02-10 17:37   ` Mickaël Salaün
2023-02-14 10:15     ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 06/12] landlock: Refactor _unmask_layers() and _init_layer_masks() Konstantin Meskhidze
2023-02-10 17:38   ` Mickaël Salaün
2023-02-14 10:16     ` Konstantin Meskhidze (A)
2023-02-21 18:07   ` Mickaël Salaün
2023-03-06  7:52     ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 07/12] landlock: Refactor landlock_add_rule() syscall Konstantin Meskhidze
2023-02-10 17:38   ` Mickaël Salaün
2023-02-14 10:18     ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 08/12] landlock: Add network rules and TCP hooks support Konstantin Meskhidze
2023-02-10 17:39   ` Mickaël Salaün
2023-02-14 10:19     ` Konstantin Meskhidze (A)
2023-03-13  9:33     ` Konstantin Meskhidze (A)
2023-03-14 12:13       ` Mickaël Salaün
2023-03-14 14:38         ` Konstantin Meskhidze (A)
2023-02-21 18:04   ` Mickaël Salaün
2023-03-06 10:18     ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 09/12] selftests/landlock: Share enforce_ruleset() Konstantin Meskhidze
2023-01-16  8:58 ` [PATCH v9 10/12] selftests/landlock: Add 10 new test suites dedicated to network Konstantin Meskhidze
2023-02-10 17:40   ` Mickaël Salaün
2023-02-14 10:36     ` Konstantin Meskhidze (A)
2023-02-14 12:13       ` Mickaël Salaün
2023-02-14 13:28         ` Konstantin Meskhidze (A)
2023-02-21 18:05   ` Mickaël Salaün
2023-03-06 12:03     ` Konstantin Meskhidze (A)
2023-03-06 16:00       ` Mickaël Salaün
2023-03-06 18:13         ` Konstantin Meskhidze (A)
2023-01-16  8:58 ` [PATCH v9 11/12] samples/landlock: Add network demo Konstantin Meskhidze
2023-01-16  8:58 ` [PATCH v9 12/12] landlock: Document Landlock's network support Konstantin Meskhidze
2023-01-21 23:07   ` Günther Noack
2023-01-23  9:38     ` Konstantin Meskhidze (A)
2023-01-27 18:22       ` Mickaël Salaün
2023-01-30 10:03         ` Konstantin Meskhidze (A)
2023-02-21 16:16           ` Mickaël Salaün
2023-03-06 13:43             ` Konstantin Meskhidze (A)
2023-03-06 16:09               ` Mickaël Salaün
2023-03-06 17:55                 ` Konstantin Meskhidze (A)
2023-01-30 12:26         ` Konstantin Meskhidze (A)
2023-02-23 22:17 ` [PATCH v9 00/12] Network support for Landlock Günther Noack
2023-03-06  7:45   ` Konstantin Meskhidze (A)
2023-03-13 17:16   ` Konstantin Meskhidze (A)
2023-03-14 13:28     ` Mickaël Salaün
2023-06-26 15:29       ` [PATCH v9 00/12] Network support for Landlock - allowed list of protocols Mickaël Salaün
2023-06-28  2:33         ` Jeff Xu
2023-06-28 19:03           ` Mickaël Salaün
2023-06-28 21:56             ` Jeff Xu
2023-06-28  8:44         ` Günther Noack
2023-06-28 17:03           ` Jeff Xu
2023-06-28 19:29             ` Mickaël Salaün
2023-06-29  3:18               ` Jeff Xu
2023-06-29 11:07                 ` Mickaël Salaün
2023-06-30  4:18                   ` Jeff Xu
2023-06-30 18:23                     ` Mickaël Salaün
2023-07-05 15:00                       ` Jeff Xu
2023-07-12 11:30                         ` Mickaël Salaün
2023-07-13 13:20                           ` Konstantin Meskhidze (A)
2023-07-13 14:52                             ` Mickaël Salaün
2023-07-13 11:44                   ` Konstantin Meskhidze (A)
2023-06-28 19:07           ` Mickaël Salaün

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