From patchwork Mon Aug 29 17:03:44 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958237 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C7544C6FA82 for ; Mon, 29 Aug 2022 17:04:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231232AbiH2REN (ORCPT ); Mon, 29 Aug 2022 13:04:13 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37474 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229939AbiH2REN (ORCPT ); Mon, 29 Aug 2022 13:04:13 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D33B49BB52; Mon, 29 Aug 2022 10:04:11 -0700 (PDT) Received: from fraeml737-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGK2R6Nz67x0N; Tue, 30 Aug 2022 01:03:37 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml737-chm.china.huawei.com (10.206.15.218) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:10 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:09 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 01/18] landlock: rename access mask Date: Tue, 30 Aug 2022 01:03:44 +0800 Message-ID: <20220829170401.834298-2-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: 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 --- 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 | 7 ++++--- security/landlock/limits.h | 1 + security/landlock/ruleset.c | 17 +++++++++-------- security/landlock/ruleset.h | 37 ++++++++++++++++++++++++++++++++---- security/landlock/syscalls.c | 7 ++++--- 5 files changed, 51 insertions(+), 18 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index c57f581a9cd5..e2d1cc28729a 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -168,7 +168,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, return -EINVAL; /* Transforms relative access rights to absolute ones. */ - access_rights |= LANDLOCK_MASK_ACCESS_FS & ~ruleset->fs_access_masks[0]; + access_rights |= LANDLOCK_MASK_ACCESS_FS & + ~landlock_get_fs_access_mask(ruleset, 0); object = get_inode_object(d_backing_inode(path->dentry)); if (IS_ERR(object)) return PTR_ERR(object); @@ -287,7 +288,7 @@ get_handled_accesses(const struct landlock_ruleset *const domain) for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { - if (domain->fs_access_masks[layer_level] & + if (landlock_get_fs_access_mask(domain, layer_level) & BIT_ULL(access_bit)) { access_dom |= BIT_ULL(access_bit); break; @@ -317,7 +318,7 @@ init_layer_masks(const struct landlock_ruleset *const domain, for_each_set_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks)) { - if (domain->fs_access_masks[layer_level] & + if (landlock_get_fs_access_mask(domain, layer_level) & BIT_ULL(access_bit)) { (*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..647d44284080 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,28 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) refcount_inc(&ruleset->usage); } +/* A helper function to set a filesystem mask. */ +static inline void +landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset, + const access_mask_t fs_access_mask, + const u16 layer_level) +{ + access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS; + + /* Should already be checked in sys_landlock_create_ruleset(). */ + WARN_ON_ONCE(fs_access_mask != fs_mask); + // TODO: Add tests to check "|=" and not "=" + ruleset->access_masks[layer_level] |= + (fs_mask << LANDLOCK_SHIFT_ACCESS_FS); +} + +/* A helper function to get a filesystem mask. */ +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 f4d6fc7ed17f..6593381466e0 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; } From patchwork Mon Aug 29 17:03:45 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958238 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 655FBC6FA69 for ; Mon, 29 Aug 2022 17:04:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231245AbiH2REQ (ORCPT ); Mon, 29 Aug 2022 13:04:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37504 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229939AbiH2REP (ORCPT ); Mon, 29 Aug 2022 13:04:15 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C9DCC98A7B; Mon, 29 Aug 2022 10:04:13 -0700 (PDT) Received: from fraeml735-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcBm4s3Fz6842f; Tue, 30 Aug 2022 01:00:32 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml735-chm.china.huawei.com (10.206.15.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:11 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:11 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 02/18] landlock: refactor landlock_find_rule/insert_rule Date: Tue, 30 Aug 2022 01:03:45 +0800 Message-ID: <20220829170401.834298-3-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds a new landlock_key union and landlock_id structure to support a socket port rule type. Refactors landlock_insert_rule() and landlock_find_rule() to support coming network modifications. This patch also adds is_object_pointer() and get_root() helpers. Now adding or searching a rule in a ruleset depends on a landlock id argument provided in refactored functions mentioned above. Signed-off-by: Konstantin Meskhidze --- Changes since v6: * Adds union landlock_key, enum landlock_key_type, and struct landlock_id. * Refactors ruleset functions and improves switch/cases: create_rule(), insert_rule(), get_root(), is_object_pointer(), free_rule(), landlock_find_rule(). * Refactors landlock_append_fs_rule() functions to support new landlock_id type. Changes since v5: * Formats code with clang-format-14. Changes since v4: * Refactors insert_rule() and create_rule() functions by deleting rule_type from their arguments list, it helps to reduce useless code. Changes since v3: * Splits commit. * Refactors landlock_insert_rule and landlock_find_rule functions. * Rename new_ruleset->root_inode. --- security/landlock/fs.c | 21 ++++-- security/landlock/ruleset.c | 146 +++++++++++++++++++++++++----------- security/landlock/ruleset.h | 51 ++++++++++--- 3 files changed, 156 insertions(+), 62 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index e2d1cc28729a..cca87fcd222d 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; } @@ -197,6 +199,9 @@ find_rule(const struct landlock_ruleset *const domain, { const struct landlock_rule *rule; const struct inode *inode; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; /* Ignores nonexistent leafs. */ if (d_is_negative(dentry)) @@ -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; } diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 1f3188b4e313..41de17d1869e 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -35,7 +35,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); mutex_init(&new_ruleset->lock); - new_ruleset->root = RB_ROOT; + new_ruleset->root_inode = RB_ROOT; new_ruleset->num_layers = num_layers; /* * hierarchy = NULL @@ -68,8 +68,18 @@ static void build_check_rule(void) BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS); } +static inline bool is_object_pointer(const enum landlock_key_type key_type) +{ + switch (key_type) { + case LANDLOCK_KEY_INODE: + return true; + } + WARN_ON_ONCE(1); + return false; +} + static struct landlock_rule * -create_rule(struct landlock_object *const object, +create_rule(const struct landlock_id id, const struct landlock_layer (*const layers)[], const u32 num_layers, const struct landlock_layer *const new_layer) { @@ -90,8 +100,13 @@ create_rule(struct landlock_object *const object, if (!new_rule) return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); - landlock_get_object(object); - new_rule->object = object; + if (is_object_pointer(id.type)) { + /* This should be catched by insert_rule(). */ + WARN_ON_ONCE(!id.key.object); + landlock_get_object(id.key.object); + } + + new_rule->key = id.key; new_rule->num_layers = new_num_layers; /* Copies the original layer stack. */ memcpy(new_rule->layers, layers, @@ -102,12 +117,29 @@ create_rule(struct landlock_object *const object, return new_rule; } -static void free_rule(struct landlock_rule *const rule) +static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset, + const enum landlock_key_type key_type) +{ + struct rb_root *root = NULL; + + switch (key_type) { + case LANDLOCK_KEY_INODE: + root = &ruleset->root_inode; + break; + } + if (WARN_ON_ONCE(!root)) + return ERR_PTR(-EINVAL); + return root; +} + +static void free_rule(struct landlock_rule *const rule, + const enum landlock_key_type key_type) { might_sleep(); if (!rule) return; - landlock_put_object(rule->object); + if (is_object_pointer(key_type)) + landlock_put_object(rule->key.object); kfree(rule); } @@ -129,8 +161,8 @@ static void build_check_ruleset(void) * insert_rule - Create and insert a rule in a ruleset * * @ruleset: The ruleset to be updated. - * @object: The object to build the new rule with. The underlying kernel - * object must be held by the caller. + * @id: The ID to build the new rule with. The underlying kernel object, if + * any, must be held by the caller. * @layers: One or multiple layers to be copied into the new rule. * @num_layers: The number of @layers entries. * @@ -144,26 +176,37 @@ static void build_check_ruleset(void) * access rights. */ static int insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, + const struct landlock_id id, const struct landlock_layer (*const layers)[], - size_t num_layers) + const size_t num_layers) { struct rb_node **walker_node; struct rb_node *parent_node = NULL; struct landlock_rule *new_rule; + struct rb_root *root; might_sleep(); lockdep_assert_held(&ruleset->lock); - if (WARN_ON_ONCE(!object || !layers)) + if (WARN_ON_ONCE(!layers)) return -ENOENT; - walker_node = &(ruleset->root.rb_node); + + if (is_object_pointer(id.type)) { + if (WARN_ON_ONCE(!id.key.object)) + return -ENOENT; + } + + root = get_root(ruleset, id.type); + if (IS_ERR(root)) + return PTR_ERR(root); + + walker_node = &root->rb_node; while (*walker_node) { struct landlock_rule *const this = rb_entry(*walker_node, struct landlock_rule, node); - if (this->object != object) { + if (this->key.data != id.key.data) { parent_node = *walker_node; - if (this->object < object) + if (this->key.data < id.key.data) walker_node = &((*walker_node)->rb_right); else walker_node = &((*walker_node)->rb_left); @@ -195,24 +238,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset, * Intersects access rights when it is a merge between a * ruleset and a domain. */ - new_rule = create_rule(object, &this->layers, this->num_layers, + new_rule = create_rule(id, &this->layers, this->num_layers, &(*layers)[0]); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); - rb_replace_node(&this->node, &new_rule->node, &ruleset->root); - free_rule(this); + rb_replace_node(&this->node, &new_rule->node, root); + free_rule(this, id.type); return 0; } - /* There is no match for @object. */ + /* There is no match for @id. */ build_check_ruleset(); if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES) return -E2BIG; - new_rule = create_rule(object, layers, num_layers, NULL); + new_rule = create_rule(id, layers, num_layers, NULL); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); rb_link_node(&new_rule->node, parent_node, walker_node); - rb_insert_color(&new_rule->node, &ruleset->root); + rb_insert_color(&new_rule->node, root); ruleset->num_rules++; return 0; } @@ -230,7 +273,7 @@ static void build_check_layer(void) /* @ruleset must be locked by the caller. */ int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, + const struct landlock_id id, const access_mask_t access) { struct landlock_layer layers[] = { { @@ -240,7 +283,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, } }; build_check_layer(); - return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers)); + return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers)); } static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy) @@ -263,6 +306,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst, struct landlock_ruleset *const src) { struct landlock_rule *walker_rule, *next_rule; + struct rb_root *src_root; int err = 0; might_sleep(); @@ -273,6 +317,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst, if (WARN_ON_ONCE(!dst || !dst->hierarchy)) return -EINVAL; + src_root = get_root(src, LANDLOCK_KEY_INODE); + if (IS_ERR(src_root)) + return PTR_ERR(src_root); + /* Locks @dst first because we are its only owner. */ mutex_lock(&dst->lock); mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING); @@ -285,23 +333,23 @@ static int merge_ruleset(struct landlock_ruleset *const dst, dst->access_masks[dst->num_layers - 1] = src->access_masks[0]; /* Merges the @src tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root, + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root, node) { struct landlock_layer layers[] = { { .level = dst->num_layers, } }; + const struct landlock_id id = { + .key = walker_rule->key, + .type = LANDLOCK_KEY_INODE, + }; - if (WARN_ON_ONCE(walker_rule->num_layers != 1)) { - err = -EINVAL; - goto out_unlock; - } - if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) { - err = -EINVAL; - goto out_unlock; - } + if (WARN_ON_ONCE(walker_rule->num_layers != 1)) + return -EINVAL; + if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) + return -EINVAL; layers[0].access = walker_rule->layers[0].access; - err = insert_rule(dst, walker_rule->object, &layers, - ARRAY_SIZE(layers)); + + err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers)); if (err) goto out_unlock; } @@ -316,21 +364,29 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, struct landlock_ruleset *const child) { struct landlock_rule *walker_rule, *next_rule; + struct rb_root *parent_root; int err = 0; might_sleep(); if (!parent) return 0; + parent_root = get_root(parent, LANDLOCK_KEY_INODE); + if (IS_ERR(parent_root)) + return PTR_ERR(parent_root); + /* Locks @child first because we are its only owner. */ mutex_lock(&child->lock); mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING); /* Copies the @parent tree. */ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &parent->root, node) { - err = insert_rule(child, walker_rule->object, - &walker_rule->layers, + parent_root, node) { + const struct landlock_id id = { + .key = walker_rule->key, + .type = LANDLOCK_KEY_INODE, + }; + err = insert_rule(child, id, &walker_rule->layers, walker_rule->num_layers); if (err) goto out_unlock; @@ -362,8 +418,9 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) struct landlock_rule *freeme, *next; might_sleep(); - rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node) - free_rule(freeme); + rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, + node) + free_rule(freeme, LANDLOCK_KEY_INODE); put_hierarchy(ruleset->hierarchy); kfree(ruleset); } @@ -454,20 +511,23 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent, */ const struct landlock_rule * landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object) + const struct landlock_id id) { + const struct rb_root *root; const struct rb_node *node; - if (!object) + root = get_root((struct landlock_ruleset *)ruleset, id.type); + if (IS_ERR(root)) return NULL; - node = ruleset->root.rb_node; + node = root->rb_node; + while (node) { struct landlock_rule *this = rb_entry(node, struct landlock_rule, node); - if (this->object == object) + if (this->key.data == id.key.data) return this; - if (this->object < object) + if (this->key.data < id.key.data) node = node->rb_right; else node = node->rb_left; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 647d44284080..bb1408cc8dd2 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -49,6 +49,33 @@ struct landlock_layer { access_mask_t access; }; +/** + * union landlock_key - Key of a ruleset's red-black tree + */ +union landlock_key { + struct landlock_object *object; + 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 { + union landlock_key key; + const enum landlock_key_type type; +}; + /** * struct landlock_rule - Access rights tied to an object */ @@ -58,12 +85,13 @@ struct landlock_rule { */ struct rb_node node; /** - * @object: Pointer to identify a kernel object (e.g. an inode). This - * is used as a key for this ruleset element. This pointer is set once - * and never modified. It always points to an allocated object because - * each rule increments the refcount of its object. + * @key: A union to identify either a kernel object (e.g. an inode) or + * a raw data value (e.g. a network socket port). This is used as a key + * for this ruleset element. The pointer is set once and never + * modified. It always points to an allocated object because each rule + * increments the refcount of its object. */ - struct landlock_object *object; + union landlock_key key; /** * @num_layers: Number of entries in @layers. */ @@ -99,11 +127,12 @@ struct landlock_hierarchy { */ struct landlock_ruleset { /** - * @root: Root of a red-black tree containing &struct landlock_rule - * nodes. Once a ruleset is tied to a process (i.e. as a domain), this - * tree is immutable until @usage reaches zero. + * @root_inode: Root of a red-black tree containing &struct + * landlock_rule nodes with inode object. Once a ruleset is tied to a + * process (i.e. as a domain), this tree is immutable until @usage + * reaches zero. */ - struct rb_root root; + struct rb_root root_inode; /** * @hierarchy: Enables hierarchy identification even when a parent * domain vanishes. This is needed for the ptrace protection. @@ -165,7 +194,7 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, + const struct landlock_id id, const access_mask_t access); struct landlock_ruleset * @@ -174,7 +203,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) { From patchwork Mon Aug 29 17:03:46 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958239 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 85F05C6FA81 for ; Mon, 29 Aug 2022 17:04:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231273AbiH2REX (ORCPT ); Mon, 29 Aug 2022 13:04:23 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37586 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231256AbiH2RER (ORCPT ); Mon, 29 Aug 2022 13:04:17 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A5E279C1D6; Mon, 29 Aug 2022 10:04:15 -0700 (PDT) Received: from fraeml734-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGQ41skz689Nc; Tue, 30 Aug 2022 01:03:42 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml734-chm.china.huawei.com (10.206.15.215) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:13 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:12 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 03/18] landlock: refactor merge/inherit_ruleset functions Date: Tue, 30 Aug 2022 01:03:46 +0800 Message-ID: <20220829170401.834298-4-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Refactors merge_ruleset() and inherit_ruleset() functions to support new rule types. This patch adds merge_tree() and inherit_tree() helpers. Each has key_type argument to choose a particular rb_tree structure in a ruleset. Signed-off-by: Konstantin Meskhidze --- 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, 69 insertions(+), 39 deletions(-) -- 2.25.1 diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 41de17d1869e..3a5ef356aaa3 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -302,36 +302,18 @@ 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; - - 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 +322,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 +333,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 +373,60 @@ 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; - - 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)); From patchwork Mon Aug 29 17:03:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958240 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7B589C6FA83 for ; Mon, 29 Aug 2022 17:04:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231278AbiH2REZ (ORCPT ); Mon, 29 Aug 2022 13:04:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37640 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231261AbiH2RET (ORCPT ); Mon, 29 Aug 2022 13:04:19 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 524959C1FF; Mon, 29 Aug 2022 10:04:17 -0700 (PDT) Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcBr02prz67Klm; Tue, 30 Aug 2022 01:00:36 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:15 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:14 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 04/18] landlock: move helper functions Date: Tue, 30 Aug 2022 01:03:47 +0800 Message-ID: <20220829170401.834298-5-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch moves unmask_layers() and init_layer_masks() helpers to ruleset.c to share with landlock network implementation in following commits. Signed-off-by: Konstantin Meskhidze --- 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 | 85 ------------------------------------- security/landlock/ruleset.c | 84 ++++++++++++++++++++++++++++++++++++ security/landlock/ruleset.h | 10 +++++ 3 files changed, 94 insertions(+), 85 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index cca87fcd222d..b03d6153f628 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 inline bool -unmask_layers(const struct landlock_rule *const rule, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) -{ - size_t layer_level; - - if (!access_request || !layer_masks) - return true; - if (!rule) - return false; - - /* - * An access is granted if, for each policy layer, at least one rule - * encountered on the pathwalk grants the requested access, - * regardless of its position in the layer stack. We must then check - * the remaining layers for each inode, from the first added layer to - * the last one. When there is multiple requested accesses, for each - * policy layer, the full set of requested accesses may not be granted - * by only one rule, but by the union (binary OR) of multiple rules. - * E.g. /a/b + /a => /a/b - */ - for (layer_level = 0; layer_level < rule->num_layers; layer_level++) { - const struct landlock_layer *const layer = - &rule->layers[layer_level]; - const layer_mask_t layer_bit = BIT_ULL(layer->level - 1); - const unsigned long access_req = access_request; - unsigned long access_bit; - bool is_empty; - - /* - * Records in @layer_masks which layer grants access to each - * requested access. - */ - is_empty = true; - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - if (layer->access & BIT_ULL(access_bit)) - (*layer_masks)[access_bit] &= ~layer_bit; - is_empty = is_empty && !(*layer_masks)[access_bit]; - } - if (is_empty) - return true; - } - return false; -} - /* * Allows access to pseudo filesystems that will never be mountable (e.g. * sockfs, pipefs), but can still be reachable through @@ -303,37 +249,6 @@ get_handled_accesses(const struct landlock_ruleset *const domain) return access_dom; } -static inline access_mask_t -init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) -{ - access_mask_t handled_accesses = 0; - size_t layer_level; - - memset(layer_masks, 0, sizeof(*layer_masks)); - /* An empty access request can happen because of O_WRONLY | O_RDWR. */ - if (!access_request) - return 0; - - /* Saves all handled accesses per layer. */ - for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { - const unsigned long access_req = access_request; - unsigned long access_bit; - - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - if (landlock_get_fs_access_mask(domain, layer_level) & - BIT_ULL(access_bit)) { - (*layer_masks)[access_bit] |= - BIT_ULL(layer_level); - handled_accesses |= BIT_ULL(access_bit); - } - } - } - return handled_accesses; -} - /* * Check that a destination file hierarchy has more restrictions than a source * file hierarchy. This is only used for link and rename actions. diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 3a5ef356aaa3..671a95e2a345 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -564,3 +564,87 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset, } return NULL; } + +/* + * @layer_masks is read and may be updated according to the access request and + * the matching rule. + * + * Returns true if the request is allowed (i.e. relevant layer masks for the + * request are empty). + */ +bool unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + size_t layer_level; + + if (!access_request || !layer_masks) + return true; + if (!rule) + return false; + + /* + * An access is granted if, for each policy layer, at least one rule + * encountered on the pathwalk grants the requested access, + * regardless of its position in the layer stack. We must then check + * the remaining layers for each inode, from the first added layer to + * the last one. When there is multiple requested accesses, for each + * policy layer, the full set of requested accesses may not be granted + * by only one rule, but by the union (binary OR) of multiple rules. + * E.g. /a/b + /a => /a/b + */ + 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; +} + +access_mask_t +init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +{ + access_mask_t handled_accesses = 0; + size_t layer_level; + + memset(layer_masks, 0, sizeof(*layer_masks)); + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ + if (!access_request) + return 0; + + /* Saves all handled accesses per layer. */ + for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { + const unsigned long access_req = access_request; + unsigned long access_bit; + + for_each_set_bit(access_bit, &access_req, + ARRAY_SIZE(*layer_masks)) { + if (landlock_get_fs_access_mask(domain, layer_level) & + BIT_ULL(access_bit)) { + (*layer_masks)[access_bit] |= + BIT_ULL(layer_level); + handled_accesses |= BIT_ULL(access_bit); + } + } + } + return handled_accesses; +} diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index bb1408cc8dd2..d7d9b987829c 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -235,4 +235,14 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, LANDLOCK_SHIFT_ACCESS_FS) & LANDLOCK_MASK_ACCESS_FS; } + +bool unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]); + +access_mask_t +init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]); + #endif /* _SECURITY_LANDLOCK_RULESET_H */ From patchwork Mon Aug 29 17:03:48 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958241 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0DC54C6FA81 for ; Mon, 29 Aug 2022 17:04:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231289AbiH2REg (ORCPT ); Mon, 29 Aug 2022 13:04:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37738 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231263AbiH2REW (ORCPT ); Mon, 29 Aug 2022 13:04:22 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D1D699C219; Mon, 29 Aug 2022 10:04:18 -0700 (PDT) Received: from fraeml713-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcBs3kBzz67DRW; Tue, 30 Aug 2022 01:00:37 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml713-chm.china.huawei.com (10.206.15.32) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:16 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:15 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 05/18] landlock: refactor helper functions Date: Tue, 30 Aug 2022 01:03:48 +0800 Message-ID: <20220829170401.834298-6-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds new key_type argument to init_layer_masks() helper functions. This modification supports implementing new rule types in the next Landlock versions. Signed-off-by: Konstantin Meskhidze --- 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 | 33 +++++++++++++++++----------- security/landlock/ruleset.c | 44 +++++++++++++++++++++++++++---------- security/landlock/ruleset.h | 11 +++++----- 3 files changed, 58 insertions(+), 30 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index b03d6153f628..a4d9aea539cd 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -439,16 +439,20 @@ static int check_access_path_dual( if (unlikely(dentry_child1)) { unmask_layers(find_rule(domain, dentry_child1), init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child1), - &_layer_masks_child1); + &_layer_masks_child1, + LANDLOCK_KEY_INODE), + &_layer_masks_child1, + ARRAY_SIZE(_layer_masks_child1)); layer_masks_child1 = &_layer_masks_child1; child1_is_directory = d_is_dir(dentry_child1); } if (unlikely(dentry_child2)) { unmask_layers(find_rule(domain, dentry_child2), init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child2), - &_layer_masks_child2); + &_layer_masks_child2, + LANDLOCK_KEY_INODE), + &_layer_masks_child2, + ARRAY_SIZE(_layer_masks_child2)); layer_masks_child2 = &_layer_masks_child2; child2_is_directory = d_is_dir(dentry_child2); } @@ -500,15 +504,16 @@ static int check_access_path_dual( } rule = find_rule(domain, walker_path.dentry); - allowed_parent1 = unmask_layers(rule, access_masked_parent1, - layer_masks_parent1); - allowed_parent2 = unmask_layers(rule, access_masked_parent2, - layer_masks_parent2); + allowed_parent1 = unmask_layers( + rule, access_masked_parent1, layer_masks_parent1, + ARRAY_SIZE(*layer_masks_parent1)); + allowed_parent2 = unmask_layers( + rule, access_masked_parent2, layer_masks_parent2, + ARRAY_SIZE(*layer_masks_parent2)); /* Stops when a rule from each layer grants access. */ if (allowed_parent1 && allowed_parent2) break; - jump_up: if (walker_path.dentry == walker_path.mnt->mnt_root) { if (follow_up(&walker_path)) { @@ -564,7 +569,8 @@ static inline int check_access_path(const struct landlock_ruleset *const domain, { layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; - access_request = init_layer_masks(domain, access_request, &layer_masks); + access_request = init_layer_masks(domain, access_request, &layer_masks, + LANDLOCK_KEY_INODE); return check_access_path_dual(domain, path, access_request, &layer_masks, NULL, 0, NULL, NULL); } @@ -648,7 +654,7 @@ static bool collect_domain_accesses( return true; access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - layer_masks_dom); + layer_masks_dom, LANDLOCK_KEY_INODE); dget(dir); while (true) { @@ -656,7 +662,8 @@ static bool collect_domain_accesses( /* Gets all layers allowing all domain accesses. */ if (unmask_layers(find_rule(domain, dir), access_dom, - layer_masks_dom)) { + layer_masks_dom, + ARRAY_SIZE(*layer_masks_dom))) { /* * Stops when all handled accesses are allowed by at * least one rule in each layer. @@ -772,7 +779,7 @@ static int current_check_refer_path(struct dentry *const old_dentry, */ access_request_parent1 = init_layer_masks( dom, access_request_parent1 | access_request_parent2, - &layer_masks_parent1); + &layer_masks_parent1, LANDLOCK_KEY_INODE); return check_access_path_dual(dom, new_dir, access_request_parent1, &layer_masks_parent1, NULL, 0, diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 671a95e2a345..84fcd8eb30d4 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -574,7 +574,8 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset, */ bool unmask_layers(const struct landlock_rule *const rule, const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) + layer_mask_t (*const layer_masks)[], + const size_t masks_array_size) { size_t layer_level; @@ -606,8 +607,7 @@ bool unmask_layers(const struct landlock_rule *const rule, * requested access. */ is_empty = true; - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { + for_each_set_bit(access_bit, &access_req, masks_array_size) { if (layer->access & BIT_ULL(access_bit)) (*layer_masks)[access_bit] &= ~layer_bit; is_empty = is_empty && !(*layer_masks)[access_bit]; @@ -618,15 +618,36 @@ bool unmask_layers(const struct landlock_rule *const rule, return false; } -access_mask_t -init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +typedef access_mask_t +get_access_mask_t(const struct landlock_ruleset *const ruleset, + const u16 layer_level); + +/* + * @layer_masks must contain LANDLOCK_NUM_ACCESS_FS or LANDLOCK_NUM_ACCESS_NET + * elements according to @key_type. + */ +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + const enum landlock_key_type key_type) { access_mask_t handled_accesses = 0; - size_t layer_level; + size_t layer_level, num_access; + get_access_mask_t *get_access_mask; + + switch (key_type) { + case LANDLOCK_KEY_INODE: + get_access_mask = landlock_get_fs_access_mask; + num_access = LANDLOCK_NUM_ACCESS_FS; + break; + default: + WARN_ON_ONCE(1); + return 0; + } + + memset(layer_masks, 0, + array_size(sizeof((*layer_masks)[0]), num_access)); - memset(layer_masks, 0, sizeof(*layer_masks)); /* An empty access request can happen because of O_WRONLY | O_RDWR. */ if (!access_request) return 0; @@ -636,9 +657,8 @@ init_layer_masks(const struct landlock_ruleset *const domain, const unsigned long access_req = access_request; unsigned long access_bit; - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - if (landlock_get_fs_access_mask(domain, layer_level) & + for_each_set_bit(access_bit, &access_req, num_access) { + if (get_access_mask(domain, layer_level) & BIT_ULL(access_bit)) { (*layer_masks)[access_bit] |= BIT_ULL(layer_level); diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index d7d9b987829c..2083855bf42d 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -238,11 +238,12 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, bool unmask_layers(const struct landlock_rule *const rule, const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]); + layer_mask_t (*const layer_masks)[], + const size_t masks_array_size); -access_mask_t -init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]); +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + const enum landlock_key_type key_type); #endif /* _SECURITY_LANDLOCK_RULESET_H */ From patchwork Mon Aug 29 17:03:49 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958242 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 94EC1C6FA81 for ; Mon, 29 Aug 2022 17:04:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231274AbiH2REv (ORCPT ); Mon, 29 Aug 2022 13:04:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37808 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231276AbiH2REY (ORCPT ); Mon, 29 Aug 2022 13:04:24 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E036A9C2D1; Mon, 29 Aug 2022 10:04:20 -0700 (PDT) Received: from fraeml715-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcBv1Phdz687Wr; Tue, 30 Aug 2022 01:00:39 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml715-chm.china.huawei.com (10.206.15.34) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:18 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:17 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 06/18] landlock: refactor landlock_add_rule syscall Date: Tue, 30 Aug 2022 01:03:49 +0800 Message-ID: <20220829170401.834298-7-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Modifies landlock_add_rule syscall to support new rule types in future Landlock versions. Adds add_rule_path_beneath() helper to support current filesystem rules. Signed-off-by: Konstantin Meskhidze --- 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 | 99 +++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 47 deletions(-) -- 2.25.1 diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 6593381466e0..28acc4cef3e8 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -274,6 +274,47 @@ static int get_path_from_fd(const s32 fd, struct path *const path) return err; } +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, + const void __user *const rule_attr) +{ + struct landlock_path_beneath_attr path_beneath_attr; + struct path path; + int res, err; + u32 mask; + + /* Copies raw user space buffer, only one type for now. */ + res = copy_from_user(&path_beneath_attr, rule_attr, + sizeof(path_beneath_attr)); + if (res) + return -EFAULT; + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored in path walks. + */ + if (!path_beneath_attr.allowed_access) + return -ENOMSG; + /* + * Checks that allowed_access matches the @ruleset constraints + * (ruleset->access_masks[0] is automatically upgraded to 64-bits). + */ + mask = landlock_get_fs_access_mask(ruleset, 0); + if ((path_beneath_attr.allowed_access | mask) != mask) + return -EINVAL; + + /* Gets and checks the new rule. */ + err = get_path_from_fd(path_beneath_attr.parent_fd, &path); + if (err) + return err; + + /* Imports the new rule. */ + err = landlock_append_fs_rule(ruleset, &path, + path_beneath_attr.allowed_access); + path_put(&path); + + return err; +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * @@ -292,13 +333,14 @@ static int get_path_from_fd(const s32 fd, struct path *const path) * * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. - * &landlock_path_beneath_attr.allowed_access is not a subset of the - * ruleset handled accesses); + * &landlock_path_beneath_attr.allowed_access is not a subset of the rule's + * accesses); * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a * member of @rule_attr is not a file descriptor as expected; * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of - * @rule_attr is not the expected file descriptor type; + * @rule_attr is not the expected file descriptor type (e.g. file open + * without O_PATH); * - EPERM: @ruleset_fd has no write access to the underlying ruleset; * - EFAULT: @rule_attr inconsistency. */ @@ -306,10 +348,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, const enum landlock_rule_type, rule_type, const void __user *const, rule_attr, const __u32, flags) { - struct landlock_path_beneath_attr path_beneath_attr; - struct path path; struct landlock_ruleset *ruleset; - int res, err; + int err; if (!landlock_initialized) return -EOPNOTSUPP; @@ -323,49 +363,14 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, if (IS_ERR(ruleset)) return PTR_ERR(ruleset); - if (rule_type != LANDLOCK_RULE_PATH_BENEATH) { + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + err = add_rule_path_beneath(ruleset, rule_attr); + break; + default: err = -EINVAL; - goto out_put_ruleset; - } - - /* Copies raw user space buffer, only one type for now. */ - res = copy_from_user(&path_beneath_attr, rule_attr, - sizeof(path_beneath_attr)); - if (res) { - err = -EFAULT; - goto out_put_ruleset; + break; } - - /* - * Informs about useless rule: empty allowed_access (i.e. deny rules) - * are ignored in path walks. - */ - if (!path_beneath_attr.allowed_access) { - err = -ENOMSG; - goto out_put_ruleset; - } - /* - * Checks that allowed_access matches the @ruleset constraints - * (ruleset->access_masks[0] is automatically upgraded to 64-bits). - */ - if ((path_beneath_attr.allowed_access | - landlock_get_fs_access_mask(ruleset, 0)) != - landlock_get_fs_access_mask(ruleset, 0)) { - err = -EINVAL; - goto out_put_ruleset; - } - - /* Gets and checks the new rule. */ - err = get_path_from_fd(path_beneath_attr.parent_fd, &path); - if (err) - goto out_put_ruleset; - - /* Imports the new rule. */ - err = landlock_append_fs_rule(ruleset, &path, - path_beneath_attr.allowed_access); - path_put(&path); - -out_put_ruleset: landlock_put_ruleset(ruleset); return err; } From patchwork Mon Aug 29 17:03:50 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958243 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 26CD0C6FA81 for ; Mon, 29 Aug 2022 17:04:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231265AbiH2REx (ORCPT ); Mon, 29 Aug 2022 13:04:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37758 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231293AbiH2REg (ORCPT ); Mon, 29 Aug 2022 13:04:36 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9B5619C1F6; Mon, 29 Aug 2022 10:04:22 -0700 (PDT) Received: from fraeml710-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcBw4jtpz67Klm; Tue, 30 Aug 2022 01:00:40 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml710-chm.china.huawei.com (10.206.15.59) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:19 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:19 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 07/18] landlock: user space API network support Date: Tue, 30 Aug 2022 01:03:50 +0800 Message-ID: <20220829170401.834298-8-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Refactors user space API to support network actions. Adds new network access flags, network rule and network attributes. Signed-off-by: Konstantin Meskhidze --- Changes since v6: * None. Changes since v5: * Formats code with clang-format-14. Changes since v4: * None Changes since v3: * Splits commit. * Refactors User API for network rule type. --- include/uapi/linux/landlock.h | 49 +++++++++++++++++++++++++++++++++++ security/landlock/syscalls.c | 3 ++- 2 files changed, 51 insertions(+), 1 deletion(-) -- 2.25.1 diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 735b1fe8326e..1ce2be6a78af 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -31,6 +31,13 @@ struct landlock_ruleset_attr { * this access right. */ __u64 handled_access_fs; + + /** + * @handled_access_net: Bitmask of actions (cf. `Network flags`_) + * that is handled by this ruleset and should then be forbidden if no + * rule explicitly allow them. + */ + __u64 handled_access_net; }; /* @@ -54,6 +61,11 @@ enum landlock_rule_type { * landlock_path_beneath_attr . */ LANDLOCK_RULE_PATH_BENEATH = 1, + /** + * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct + * landlock_net_service_attr . + */ + LANDLOCK_RULE_NET_SERVICE = 2, }; /** @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr { */ } __attribute__((packed)); +/** + * struct landlock_net_service_attr - TCP subnet definition + * + * Argument of sys_landlock_add_rule(). + */ +struct landlock_net_service_attr { + /** + * @allowed_access: Bitmask of allowed access network for services + * (cf. `Network flags`_). + */ + __u64 allowed_access; + /** + * @port: Network port. + */ + __u16 port; + +} __attribute__((packed)); + /** * DOC: fs_access * @@ -169,4 +199,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/syscalls.c b/security/landlock/syscalls.c index 28acc4cef3e8..ffd5805eddd9 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -82,8 +82,9 @@ static void build_check_abi(void) * struct size. */ ruleset_size = sizeof(ruleset_attr.handled_access_fs); + ruleset_size += sizeof(ruleset_attr.handled_access_net); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 8); + BUILD_BUG_ON(sizeof(ruleset_attr) != 16); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); From patchwork Mon Aug 29 17:03:51 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958245 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8EC9DC6FA69 for ; Mon, 29 Aug 2022 17:05:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231349AbiH2RFZ (ORCPT ); Mon, 29 Aug 2022 13:05:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38258 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231256AbiH2REw (ORCPT ); Mon, 29 Aug 2022 13:04:52 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 888C79C2FC; Mon, 29 Aug 2022 10:04:24 -0700 (PDT) Received: from fraeml712-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGX531Hz67wXw; Tue, 30 Aug 2022 01:03:48 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml712-chm.china.huawei.com (10.206.15.61) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:21 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:20 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 08/18] landlock: add network rules support Date: Tue, 30 Aug 2022 01:03:51 +0800 Message-ID: <20220829170401.834298-9-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This commit adds network rules support in internal landlock functions (presented in ruleset.c) and landlock_create_ruleset syscall. Signed-off-by: Konstantin Meskhidze --- 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. --- security/landlock/limits.h | 6 +++++- security/landlock/ruleset.c | 38 +++++++++++++++++++++++++++++---- security/landlock/ruleset.h | 41 ++++++++++++++++++++++++++++++++++-- security/landlock/syscalls.c | 8 ++++++- 4 files changed, 85 insertions(+), 8 deletions(-) -- 2.25.1 diff --git a/security/landlock/limits.h b/security/landlock/limits.h index bafb3b8dc677..8a1a6463c64e 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -23,6 +23,10 @@ #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) #define LANDLOCK_SHIFT_ACCESS_FS 0 -/* clang-format on */ +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP +#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) +#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) +#define LANDLOCK_SHIFT_ACCESS_NET LANDLOCK_NUM_ACCESS_FS +/* clang-format on */ #endif /* _SECURITY_LANDLOCK_LIMITS_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 84fcd8eb30d4..442f212039df 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -36,6 +36,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) refcount_set(&new_ruleset->usage, 1); mutex_init(&new_ruleset->lock); new_ruleset->root_inode = RB_ROOT; + new_ruleset->root_net_port = RB_ROOT; new_ruleset->num_layers = num_layers; /* * hierarchy = NULL @@ -46,16 +47,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 +79,8 @@ static inline bool is_object_pointer(const enum landlock_key_type key_type) switch (key_type) { case LANDLOCK_KEY_INODE: return true; + case LANDLOCK_KEY_NET_PORT: + return false; } WARN_ON_ONCE(1); return false; @@ -126,6 +134,9 @@ static inline struct rb_root *get_root(struct landlock_ruleset *const ruleset, case LANDLOCK_KEY_INODE: root = &ruleset->root_inode; break; + case LANDLOCK_KEY_NET_PORT: + root = &ruleset->root_net_port; + break; } if (WARN_ON_ONCE(!root)) return ERR_PTR(-EINVAL); @@ -154,7 +165,9 @@ static void build_check_ruleset(void) BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES); BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); BUILD_BUG_ON(access_masks < - (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS)); + (LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) + + (LANDLOCK_MASK_ACCESS_NET + << LANDLOCK_SHIFT_ACCESS_NET)); } /** @@ -367,6 +380,11 @@ static int merge_ruleset(struct landlock_ruleset *const dst, if (err) goto out_unlock; + /* Merges the @src network port tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT); + if (err) + goto out_unlock; + out_unlock: mutex_unlock(&src->lock); mutex_unlock(&dst->lock); @@ -419,6 +437,11 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, if (err) goto out_unlock; + /* Copies the @parent network port tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT); + if (err) + goto out_unlock; + if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) { err = -EINVAL; goto out_unlock; @@ -451,6 +474,9 @@ 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); + rbtree_postorder_for_each_entry_safe(freeme, next, + &ruleset->root_net_port, node) + free_rule(freeme, LANDLOCK_KEY_NET_PORT); put_hierarchy(ruleset->hierarchy); kfree(ruleset); } @@ -640,6 +666,10 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, get_access_mask = landlock_get_fs_access_mask; num_access = LANDLOCK_NUM_ACCESS_FS; break; + case LANDLOCK_KEY_NET_PORT: + get_access_mask = landlock_get_net_access_mask; + num_access = LANDLOCK_NUM_ACCESS_NET; + break; default: WARN_ON_ONCE(1); return 0; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 2083855bf42d..d456ee90b648 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -26,7 +26,7 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); 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); @@ -66,6 +66,11 @@ enum landlock_key_type { * keys. */ LANDLOCK_KEY_INODE = 1, + /** + * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's + * node keys. + */ + LANDLOCK_KEY_NET_PORT = 2, }; /** @@ -133,6 +138,12 @@ struct landlock_ruleset { * reaches zero. */ struct rb_root root_inode; + /** + * @root_net_port: Root of a red-black tree containing object nodes + * for network port. Once a ruleset is tied to a process (i.e. as a domain), + * this tree is immutable until @usage reaches zero. + */ + struct rb_root root_net_port; /** * @hierarchy: Enables hierarchy identification even when a parent * domain vanishes. This is needed for the ptrace protection. @@ -188,7 +199,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); @@ -226,6 +238,21 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset, (fs_mask << LANDLOCK_SHIFT_ACCESS_FS); } +/* A helper function to set a network mask. */ +static inline void +landlock_add_net_access_mask(struct landlock_ruleset *const ruleset, + const access_mask_t net_access_mask, + const u16 layer_level) +{ + access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET; + + /* Should already be checked in sys_landlock_create_ruleset(). */ + WARN_ON_ONCE(net_access_mask != net_mask); + // TODO: Add tests to check "|=" and not "=" + ruleset->access_masks[layer_level] |= + (net_mask << LANDLOCK_SHIFT_ACCESS_NET); +} + /* A helper function to get a filesystem mask. */ static inline access_mask_t landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, @@ -236,6 +263,16 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, LANDLOCK_MASK_ACCESS_FS; } +/* A helper function to get a network mask. */ +static inline access_mask_t +landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset, + const u16 layer_level) +{ + return (ruleset->access_masks[layer_level] >> + LANDLOCK_SHIFT_ACCESS_NET) & + LANDLOCK_MASK_ACCESS_NET; +} + bool unmask_layers(const struct landlock_rule *const rule, const access_mask_t access_request, layer_mask_t (*const layer_masks)[], diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index ffd5805eddd9..641155f6f6f8 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -189,8 +189,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset, LANDLOCK_MASK_ACCESS_FS) return -EINVAL; + /* Checks network content (and 32-bits cast). */ + if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) != + LANDLOCK_MASK_ACCESS_NET) + return -EINVAL; + /* Checks arguments and transforms to kernel struct. */ - ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs); + ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, + ruleset_attr.handled_access_net); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); From patchwork Mon Aug 29 17:03:52 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958244 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 36D91C6FA69 for ; Mon, 29 Aug 2022 17:05:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231334AbiH2RE6 (ORCPT ); Mon, 29 Aug 2022 13:04:58 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37738 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231281AbiH2REv (ORCPT ); Mon, 29 Aug 2022 13:04:51 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 139F69C505; Mon, 29 Aug 2022 10:04:24 -0700 (PDT) Received: from fraeml711-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGZ0bTTz67wBp; Tue, 30 Aug 2022 01:03:50 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml711-chm.china.huawei.com (10.206.15.60) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:22 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:21 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 09/18] landlock: implement TCP network hooks Date: Tue, 30 Aug 2022 01:03:52 +0800 Message-ID: <20220829170401.834298-10-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch adds support of socket_bind() and socket_connect() hooks. It's possible to restrict binding and connecting of TCP types of sockets to particular ports. It's just basic idea of how Landlock could support network confinement. Signed-off-by: Konstantin Meskhidze --- Changes since v6: * Updates copyright. * Refactors landlock_append_net_rule() and check_socket_access() functions with landlock_id type. Changes since v5: * Fixes some logic errors. * Formats code with clang-format-14. Changes since v4: * Factors out CONFIG_INET into make file. * Refactors check_socket_access(). * Adds helper get_port(). * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect functions to support AF_INET6 family. * Adds AF_UNSPEC family support in hook_socket_bind/connect functions. * Refactors add_rule_net_service() and landlock_add_rule syscall to support network rule inserting. * Refactors init_layer_masks() to support network rules. Changes since v3: * Splits commit. * Adds SECURITY_NETWORK in config. * Adds IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration. * Adds hook_socket_bind and hook_socket_connect hooks. --- security/landlock/Kconfig | 1 + security/landlock/Makefile | 2 + security/landlock/net.c | 161 +++++++++++++++++++++++++++++++++++ security/landlock/net.h | 26 ++++++ security/landlock/setup.c | 2 + security/landlock/syscalls.c | 59 ++++++++++++- 6 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 security/landlock/net.c create mode 100644 security/landlock/net.h -- 2.25.1 diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig index 8e33c4e8ffb8..10c099097533 100644 --- a/security/landlock/Kconfig +++ b/security/landlock/Kconfig @@ -3,6 +3,7 @@ config SECURITY_LANDLOCK bool "Landlock support" depends on SECURITY && !ARCH_EPHEMERAL_INODES + select SECURITY_NETWORK select SECURITY_PATH help Landlock is a sandboxing mechanism that enables processes to restrict diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 7bbd2f413b3e..53d3c92ae22e 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o landlock-y := setup.o syscalls.o object.o ruleset.o \ cred.o ptrace.o fs.o + +landlock-$(CONFIG_INET) += net.o \ No newline at end of file diff --git a/security/landlock/net.c b/security/landlock/net.c new file mode 100644 index 000000000000..0d249ad619bf --- /dev/null +++ b/security/landlock/net.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock LSM - Network management and hooks + * + * Copyright © 2022 Huawei Tech. Co., Ltd. + * Copyright © 2022 Microsoft Corporation + */ + +#include +#include +#include +#include + +#include "cred.h" +#include "limits.h" +#include "net.h" + +int landlock_append_net_rule(struct landlock_ruleset *const ruleset, u16 port, + u32 access_rights) +{ + int err; + const struct landlock_id id = { + .key.data = port, + .type = LANDLOCK_KEY_NET_PORT, + }; + BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); + + /* Transforms relative access rights to absolute ones. */ + access_rights |= LANDLOCK_MASK_ACCESS_NET & + ~landlock_get_net_access_mask(ruleset, 0); + + mutex_lock(&ruleset->lock); + err = landlock_insert_rule(ruleset, id, access_rights); + mutex_unlock(&ruleset->lock); + + return err; +} + +static int check_socket_access(const struct landlock_ruleset *const domain, + u16 port, access_mask_t access_request) +{ + bool allowed = false; + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {}; + const struct landlock_rule *rule; + access_mask_t handled_access; + const struct landlock_id id = { + .key.data = port, + .type = LANDLOCK_KEY_NET_PORT, + }; + + if (WARN_ON_ONCE(!domain)) + return 0; + if (WARN_ON_ONCE(domain->num_layers < 1)) + return -EACCES; + + rule = landlock_find_rule(domain, id); + handled_access = init_layer_masks(domain, access_request, &layer_masks, + LANDLOCK_KEY_NET_PORT); + allowed = unmask_layers(rule, handled_access, &layer_masks, + ARRAY_SIZE(layer_masks)); + + return allowed ? 0 : -EACCES; +} + +static u16 get_port(const struct sockaddr *const address) +{ + /* Gets port value in host byte order. */ + switch (address->sa_family) { + case AF_UNSPEC: + case AF_INET: { + const struct sockaddr_in *const sockaddr = + (struct sockaddr_in *)address; + return ntohs(sockaddr->sin_port); + } +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: { + const struct sockaddr_in6 *const sockaddr_ip6 = + (struct sockaddr_in6 *)address; + return ntohs(sockaddr_ip6->sin6_port); + } +#endif + } + WARN_ON_ONCE(1); + return 0; +} + +static int hook_socket_bind(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + if (!dom) + return 0; + + /* Check if it's a TCP socket. */ + if (sock->type != SOCK_STREAM) + return 0; + + switch (address->sa_family) { + case AF_UNSPEC: + case AF_INET: +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: +#endif + return check_socket_access(dom, get_port(address), + LANDLOCK_ACCESS_NET_BIND_TCP); + default: + return 0; + } +} + +static int hook_socket_connect(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + if (!dom) + return 0; + + /* Check if it's a TCP socket. */ + if (sock->type != SOCK_STREAM) + return 0; + + /* Check if the hook is AF_INET* socket's action. */ + switch (address->sa_family) { + case AF_INET: +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: +#endif + return check_socket_access(dom, get_port(address), + LANDLOCK_ACCESS_NET_CONNECT_TCP); + case AF_UNSPEC: { + u16 i; + /* + * If just in a layer a mask supports connect access, + * the socket_connect() hook with AF_UNSPEC family flag + * must be banned. This prevents from disconnecting already + * connected sockets. + */ + for (i = 0; i < dom->num_layers; i++) { + if (landlock_get_net_access_mask(dom, i) & + LANDLOCK_ACCESS_NET_CONNECT_TCP) + return -EACCES; + } + } + } + return 0; +} + +static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(socket_bind, hook_socket_bind), + LSM_HOOK_INIT(socket_connect, hook_socket_connect), +}; + +__init void landlock_add_net_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + LANDLOCK_NAME); +} diff --git a/security/landlock/net.h b/security/landlock/net.h new file mode 100644 index 000000000000..2c63a8f1b258 --- /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, u16 port, + u32 access_hierarchy); +#else /* IS_ENABLED(CONFIG_INET) */ +static inline void landlock_add_net_hooks(void) +{ +} +#endif /* IS_ENABLED(CONFIG_INET) */ + +#endif /* _SECURITY_LANDLOCK_NET_H */ diff --git a/security/landlock/setup.c b/security/landlock/setup.c index f8e8e980454c..8059dc0b47d3 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -14,6 +14,7 @@ #include "fs.h" #include "ptrace.h" #include "setup.h" +#include "net.h" bool landlock_initialized __lsm_ro_after_init = false; @@ -28,6 +29,7 @@ static int __init landlock_init(void) landlock_add_cred_hooks(); landlock_add_ptrace_hooks(); landlock_add_fs_hooks(); + landlock_add_net_hooks(); landlock_initialized = true; pr_info("Up and running.\n"); return 0; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 641155f6f6f8..16880e951346 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -29,6 +29,7 @@ #include "cred.h" #include "fs.h" #include "limits.h" +#include "net.h" #include "ruleset.h" #include "setup.h" @@ -74,7 +75,8 @@ static void build_check_abi(void) { struct landlock_ruleset_attr ruleset_attr; struct landlock_path_beneath_attr path_beneath_attr; - size_t ruleset_size, path_beneath_size; + struct landlock_net_service_attr net_service_attr; + size_t ruleset_size, path_beneath_size, net_service_size; /* * For each user space ABI structures, first checks that there is no @@ -90,6 +92,11 @@ static void build_check_abi(void) path_beneath_size += sizeof(path_beneath_attr.parent_fd); BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size); BUILD_BUG_ON(sizeof(path_beneath_attr) != 12); + + net_service_size = sizeof(net_service_attr.allowed_access); + net_service_size += sizeof(net_service_attr.port); + BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size); + BUILD_BUG_ON(sizeof(net_service_attr) != 10); } /* Ruleset handling */ @@ -322,13 +329,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, return err; } +static int add_rule_net_service(struct landlock_ruleset *ruleset, + const void __user *const rule_attr) +{ +#if IS_ENABLED(CONFIG_INET) + struct landlock_net_service_attr net_service_attr; + int res; + u32 mask; + + /* Copies raw user space buffer, only one type for now. */ + res = copy_from_user(&net_service_attr, rule_attr, + sizeof(net_service_attr)); + if (res) + return -EFAULT; + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored by network actions. + */ + if (!net_service_attr.allowed_access) + return -ENOMSG; + + /* + * Checks that allowed_access matches the @ruleset constraints + * (ruleset->access_masks[0] is automatically upgraded to 64-bits). + */ + mask = landlock_get_net_access_mask(ruleset, 0); + if ((net_service_attr.allowed_access | mask) != mask) + return -EINVAL; + + /* Denies inserting a rule with port 0. */ + if (net_service_attr.port == 0) + return -EINVAL; + + /* Imports the new rule. */ + return landlock_append_net_rule(ruleset, net_service_attr.port, + net_service_attr.allowed_access); +#else /* IS_ENABLED(CONFIG_INET) */ + return -EAFNOSUPPORT; +#endif /* IS_ENABLED(CONFIG_INET) */ +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * * @ruleset_fd: File descriptor tied to the ruleset that should be extended * with the new rule. - * @rule_type: Identify the structure type pointed to by @rule_attr (only - * LANDLOCK_RULE_PATH_BENEATH for now). + * @rule_type: Identify the structure type pointed to by @rule_attr: + * LANDLOCK_RULE_PATH_BENEATH or LANDLOCK_RULE_NET_SERVICE. * @rule_attr: Pointer to a rule (only of type &struct * landlock_path_beneath_attr for now). * @flags: Must be 0. @@ -339,6 +387,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, * Possible returned errors are: * * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; + * - EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not + * supported by the running kernel; * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. * &landlock_path_beneath_attr.allowed_access is not a subset of the rule's * accesses); @@ -374,6 +424,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; From patchwork Mon Aug 29 17:03:53 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958246 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 998CEC6FA82 for ; Mon, 29 Aug 2022 17:05:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231363AbiH2RF0 (ORCPT ); Mon, 29 Aug 2022 13:05:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37754 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231313AbiH2REw (ORCPT ); Mon, 29 Aug 2022 13:04:52 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 29B009C52C; Mon, 29 Aug 2022 10:04:26 -0700 (PDT) Received: from fraeml709-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcC10k08z67DRW; Tue, 30 Aug 2022 01:00:45 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml709-chm.china.huawei.com (10.206.15.37) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:24 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:23 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 10/18] seltests/landlock: move helper function Date: Tue, 30 Aug 2022 01:03:53 +0800 Message-ID: <20220829170401.834298-11-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: 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 --- 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(-) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 7ba18eb23783..48870afb054b 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -187,3 +187,13 @@ clear_cap(struct __test_metadata *const _metadata, const cap_value_t caps) { _effective_cap(_metadata, caps, CAP_CLEAR); } + +__attribute__((__unused__)) static void +enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd) +{ + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) + { + TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); + } +} diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index debe2d9ea6cf..25a655891754 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -556,16 +556,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[] = { From patchwork Mon Aug 29 17:03:54 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958247 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 92CECC6FA84 for ; Mon, 29 Aug 2022 17:05:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231318AbiH2RF1 (ORCPT ); Mon, 29 Aug 2022 13:05:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37756 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231316AbiH2REx (ORCPT ); Mon, 29 Aug 2022 13:04:53 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CC2D69C8C6; Mon, 29 Aug 2022 10:04:28 -0700 (PDT) Received: from fraeml707-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGf3BkDz683mQ; Tue, 30 Aug 2022 01:03:54 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml707-chm.china.huawei.com (10.206.15.35) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:25 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:24 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 11/18] seltests/landlock: add tests for bind() hooks Date: Tue, 30 Aug 2022 01:03:54 +0800 Message-ID: <20220829170401.834298-12-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds selftests for bind() socket action. The first is with no landlock restrictions: - bind without restrictions for ip4; - bind without restrictions for ip6; The second ones is with mixed landlock rules: - bind with restrictions for ip4; - bind with restrictions for ip6; Signed-off-by: Konstantin Meskhidze --- Changes since v6: * None. Changes since v5: * Splits commit. * Adds local address 127.0.0.1. * Adds FIXTURE_VARIANT and FIXTURE_VARIANT_ADD helpers to support both ip4 and ip6 family tests and shorten the code. * Adds create_socket_variant() and bind_variant() helpers. * Gets rid of reuse_addr variable in create_socket_variant. * Formats code with clang-format-14. Changes since v4: * Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4 and struct sockaddr_in addr6 in FIXTURE. * Refactors FIXTURE_SETUP: - initializing self->port, self->addr4 and self->addr6. - adding network namespace. * Refactors code with self->port, self->addr4 and self->addr6 variables. * Adds selftests for IP6 family: - bind_no_restrictions_ip6. - bind_with_restrictions_ip6. * Refactors selftests/landlock/config * Moves enforce_ruleset() into common.h Changes since v3: * Split commit. * Add helper create_socket. * Add FIXTURE_SETUP. --- tools/testing/selftests/landlock/config | 4 + tools/testing/selftests/landlock/net_test.c | 180 ++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 tools/testing/selftests/landlock/net_test.c -- 2.25.1 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/net_test.c b/tools/testing/selftests/landlock/net_test.c new file mode 100644 index 000000000000..79c71fa37ddb --- /dev/null +++ b/tools/testing/selftests/landlock/net_test.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock tests - Network + * + * Copyright (C) 2022 Huawei Tech. Co., Ltd. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define MAX_SOCKET_NUM 10 + +#define SOCK_PORT_START 3470 +#define SOCK_PORT_ADD 10 + +#define IP_ADDRESS "127.0.0.1" + +FIXTURE(socket) +{ + uint port[MAX_SOCKET_NUM]; + struct sockaddr_in addr4[MAX_SOCKET_NUM]; + struct sockaddr_in6 addr6[MAX_SOCKET_NUM]; +}; + +FIXTURE_VARIANT(socket) +{ + const bool is_ipv4; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(socket, ipv4) { + /* clang-format on */ + .is_ipv4 = true, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(socket, ipv6) { + /* clang-format on */ + .is_ipv4 = false, +}; + +static int create_socket_variant(const 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 FIXTURE_VARIANT(socket) *const variant, + const int sockfd, + const FIXTURE_DATA(socket) *const self, + const size_t index) +{ + if (variant->is_ipv4) + return bind(sockfd, &self->addr4[index], + sizeof(self->addr4[index])); + else + return bind(sockfd, &self->addr6[index], + sizeof(self->addr6[index])); +} + +FIXTURE_SETUP(socket) +{ + int i; + /* Creates IP4 socket addresses. */ + for (i = 0; i < MAX_SOCKET_NUM; i++) { + self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i; + self->addr4[i].sin_family = AF_INET; + self->addr4[i].sin_port = htons(self->port[i]); + self->addr4[i].sin_addr.s_addr = inet_addr(IP_ADDRESS); + memset(&(self->addr4[i].sin_zero), '\0', 8); + } + + /* Creates IP6 socket addresses. */ + for (i = 0; i < MAX_SOCKET_NUM; i++) { + self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD * i; + self->addr6[i].sin6_family = AF_INET6; + self->addr6[i].sin6_port = htons(self->port[i]); + inet_pton(AF_INET6, IP_ADDRESS, &(self->addr6[i].sin6_addr)); + } + + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, unshare(CLONE_NEWNET)); + ASSERT_EQ(0, system("ip link set dev lo up")); + clear_cap(_metadata, CAP_SYS_ADMIN); +} + +FIXTURE_TEARDOWN(socket) +{ +} + +TEST_F(socket, bind_no_restrictions) +{ + int sockfd; + + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + + /* Binds a socket to port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + ASSERT_EQ(0, close(sockfd)); +} + +TEST_F(socket, bind_with_restrictions) +{ + int sockfd; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[1], + }; + struct landlock_net_service_attr net_service_3 = { + .allowed_access = 0, + .port = self->port[2], + }; + + const int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and deny bind operations to the port[1] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + /* Empty allowed_access (i.e. deny rules) are ignored in network actions + * for port[2] socket. + */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_3, 0)); + ASSERT_EQ(ENOMSG, errno); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + /* Close bounded socket. */ + ASSERT_EQ(0, close(sockfd)); + + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[1]. */ + ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 1)); + ASSERT_EQ(EACCES, errno); + + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[2]. */ + ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2)); + ASSERT_EQ(EACCES, errno); +} +TEST_HARNESS_MAIN From patchwork Mon Aug 29 17:03:55 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958248 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1C3E1C6FA81 for ; Mon, 29 Aug 2022 17:05:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231379AbiH2RFc (ORCPT ); Mon, 29 Aug 2022 13:05:32 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37776 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231321AbiH2REx (ORCPT ); Mon, 29 Aug 2022 13:04:53 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 854329C511; Mon, 29 Aug 2022 10:04:31 -0700 (PDT) Received: from fraeml706-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcC46g2Lz6842f; Tue, 30 Aug 2022 01:00:48 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml706-chm.china.huawei.com (10.206.15.55) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:27 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:27 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 12/18] seltests/landlock: add tests for connect() hooks Date: Tue, 30 Aug 2022 01:03:55 +0800 Message-ID: <20220829170401.834298-13-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds selftests for connect socket action. The first are with no landlock restrictions: - connect without restrictions for ip4; - connect without restrictions for ip6; The second ones are with mixed landlock rules: - connect with restrictions ip4; - connect with restrictions ip6; Signed-off-by: Konstantin Meskhidze --- Changes since v6: * None. Changes since v5: * Adds connect_variant() helper. * Formats code with clang-format-14. Changes since v4: * Adds selftests for IP6 family: - connect_no_restrictions_ip6. - connect_with_restrictions_ip6. * Refactors code with self->port, self->addr4 and self->addr6 variables. Changes since v3: * Split commit. --- tools/testing/selftests/landlock/net_test.c | 174 ++++++++++++++++++++ 1 file changed, 174 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 79c71fa37ddb..9c3d1e425439 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -26,6 +26,9 @@ #define IP_ADDRESS "127.0.0.1" +/* Number pending connections queue to be hold */ +#define BACKLOG 10 + FIXTURE(socket) { uint port[MAX_SOCKET_NUM]; @@ -72,6 +75,19 @@ static int bind_variant(const FIXTURE_VARIANT(socket) *const variant, sizeof(self->addr6[index])); } +static int connect_variant(const FIXTURE_VARIANT(socket) *const variant, + const int sockfd, + const FIXTURE_DATA(socket) *const self, + const size_t index) +{ + if (variant->is_ipv4) + return connect(sockfd, &self->addr4[index], + sizeof(self->addr4[index])); + else + return connect(sockfd, &self->addr6[index], + sizeof(self->addr6[index])); +} + FIXTURE_SETUP(socket) { int i; @@ -177,4 +193,162 @@ TEST_F(socket, bind_with_restrictions) ASSERT_EQ(-1, bind_variant(variant, sockfd, self, 2)); ASSERT_EQ(EACCES, errno); } + +TEST_F(socket, connect_no_restrictions) +{ + int sockfd, new_fd; + pid_t child; + int status; + + /* Creates a server socket. */ + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + + /* Binds a socket to port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + /* Makes listening socket. */ + ASSERT_EQ(0, listen(sockfd, BACKLOG)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int child_sockfd; + + /* Closes listening socket for the child. */ + ASSERT_EQ(0, close(sockfd)); + /* Create a stream client socket. */ + child_sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, child_sockfd, self, 0)); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Accepts connection from the child. */ + new_fd = accept(sockfd, NULL, 0); + ASSERT_LE(0, new_fd); + + /* Closes connection. */ + ASSERT_EQ(0, close(new_fd)); + + /* Closes listening socket for the parent. */ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} + +TEST_F(socket, connect_with_restrictions) +{ + int new_fd; + int sockfd_1, sockfd_2; + pid_t child_1, child_2; + int status; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->port[1], + }; + + const int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and deny bind operations to the port[1] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + /* Creates a server socket 1. */ + sockfd_1 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_1); + + /* Binds the socket 1 to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0)); + + /* Makes listening socket 1. */ + ASSERT_EQ(0, listen(sockfd_1, BACKLOG)); + + child_1 = fork(); + ASSERT_LE(0, child_1); + if (child_1 == 0) { + int child_sockfd; + + /* Closes listening socket for the child. */ + ASSERT_EQ(0, close(sockfd_1)); + /* Creates a stream client socket. */ + child_sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, child_sockfd, self, 0)); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Accepts connection from the child 1. */ + new_fd = accept(sockfd_1, NULL, 0); + ASSERT_LE(0, new_fd); + + /* Closes connection. */ + ASSERT_EQ(0, close(new_fd)); + + /* Closes listening socket 1 for the parent. */ + ASSERT_EQ(0, close(sockfd_1)); + + ASSERT_EQ(child_1, waitpid(child_1, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + /* Creates a server socket 2. */ + sockfd_2 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_2); + + /* Binds the socket 2 to address with port[1]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd_2, self, 1)); + + /* Makes listening socket 2. */ + ASSERT_EQ(0, listen(sockfd_2, BACKLOG)); + + child_2 = fork(); + ASSERT_LE(0, child_2); + if (child_2 == 0) { + int child_sockfd; + + /* Closes listening socket for the child. */ + ASSERT_EQ(0, close(sockfd_2)); + /* Creates a stream client socket. */ + child_sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket with port[1]. */ + ASSERT_EQ(-1, connect_variant(variant, child_sockfd, self, 1)); + ASSERT_EQ(EACCES, errno); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + + /* Closes listening socket 2 for the parent. */ + ASSERT_EQ(0, close(sockfd_2)); + + ASSERT_EQ(child_2, waitpid(child_2, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} TEST_HARNESS_MAIN From patchwork Mon Aug 29 17:03:56 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958250 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3A6FAC6FA84 for ; Mon, 29 Aug 2022 17:05:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231238AbiH2RFf (ORCPT ); Mon, 29 Aug 2022 13:05:35 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37808 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231331AbiH2REx (ORCPT ); Mon, 29 Aug 2022 13:04:53 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 42D719C8EC; Mon, 29 Aug 2022 10:04:32 -0700 (PDT) Received: from fraeml705-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGj0kn8z67ZgK; Tue, 30 Aug 2022 01:03:57 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml705-chm.china.huawei.com (10.206.15.54) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:29 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:28 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 13/18] seltests/landlock: add AF_UNSPEC family test Date: Tue, 30 Aug 2022 01:03:56 +0800 Message-ID: <20220829170401.834298-14-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds two selftests for connect() action with AF_UNSPEC family flag. The one is with no landlock restrictions allows to disconnect already connected socket with connect(..., AF_UNSPEC, ...): - connect_afunspec_no_restictions; The second one refuses landlocked process to disconnect already connected socket: - connect_afunspec_with_restictions; Signed-off-by: Konstantin Meskhidze --- Changes since v6: * None. Changes since v5: * Formats code with clang-format-14. Changes since v4: * Refactors code with self->port, self->addr4 variables. * Adds bind() hook check for with AF_UNSPEC family. Changes since v3: * Adds connect_afunspec_no_restictions test. * Adds connect_afunspec_with_restictions test. --- tools/testing/selftests/landlock/net_test.c | 113 ++++++++++++++++++++ 1 file changed, 113 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 9c3d1e425439..40aef7c683af 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -351,4 +351,117 @@ TEST_F(socket, connect_with_restrictions) ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } + +TEST_F(socket, connect_afunspec_no_restictions) +{ + int sockfd; + pid_t child; + int status; + + /* Creates a server socket 1. */ + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + + /* Binds the socket 1 to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + /* Makes connection to the socket with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC }; + + /* Child tries to disconnect already connected socket. */ + ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&addr_unspec, + sizeof(addr_unspec))); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Closes listening socket 1 for the parent. */ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} + +TEST_F(socket, connect_afunspec_with_restictions) +{ + int sockfd; + pid_t child; + int status; + + struct landlock_ruleset_attr ruleset_attr_1 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + struct landlock_ruleset_attr ruleset_attr_2 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd_1 = landlock_create_ruleset( + &ruleset_attr_1, sizeof(ruleset_attr_1), 0); + ASSERT_LE(0, ruleset_fd_1); + + /* Allows bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_1); + + /* Creates a server socket 1. */ + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + + /* Binds the socket 1 to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + /* Makes connection to socket with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0)); + + const int ruleset_fd_2 = landlock_create_ruleset( + &ruleset_attr_2, sizeof(ruleset_attr_2), 0); + ASSERT_LE(0, ruleset_fd_2); + + /* Allows connect and bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_2); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + struct sockaddr addr_unspec = { .sa_family = AF_UNSPEC }; + + /* Child tries to disconnect already connected socket. */ + ASSERT_EQ(-1, connect(sockfd, (struct sockaddr *)&addr_unspec, + sizeof(addr_unspec))); + ASSERT_EQ(EACCES, errno); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Closes listening socket 1 for the parent. */ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} TEST_HARNESS_MAIN From patchwork Mon Aug 29 17:03:57 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958249 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 90419C6FA82 for ; Mon, 29 Aug 2022 17:05:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231391AbiH2RFe (ORCPT ); Mon, 29 Aug 2022 13:05:34 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39368 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231330AbiH2REx (ORCPT ); Mon, 29 Aug 2022 13:04:53 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 40C6F9CCDD; Mon, 29 Aug 2022 10:04:33 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGk3VbBz67wBp; Tue, 30 Aug 2022 01:03:58 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml704-chm.china.huawei.com (10.206.15.53) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:31 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:30 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 14/18] seltests/landlock: add rules overlapping test Date: Tue, 30 Aug 2022 01:03:57 +0800 Message-ID: <20220829170401.834298-15-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch adds overlapping rules for one port. First rule adds just bind() access right for a port. The second one adds both bind() and connect() access rights for the same port. Signed-off-by: Konstantin Meskhidze --- Changes since v6: * None. Changes since v5: * Formats code with clang-format-14. Changes since v4: * Refactors code with self->port, self->addr4 variables. Changes since v3: * Adds ruleset_overlap test. --- tools/testing/selftests/landlock/net_test.c | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 40aef7c683af..b3b38745f4eb 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -464,4 +464,93 @@ TEST_F(socket, connect_afunspec_with_restictions) ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } + +TEST_F(socket, ruleset_overlap) +{ + int sockfd; + int one = 1; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + + .port = self->port[0], + }; + + int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + /* Creates a server socket. */ + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* Binds the socket to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + /* Makes connection to socket with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, sockfd, self, 0)); + + /* Closes socket. */ + ASSERT_EQ(0, close(sockfd)); + + /* Creates another ruleset layer. */ + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* + * Allows bind operations to the port[0] socket in + * the new ruleset layer. + */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + + /* Enforces the new ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + /* Creates a server socket. */ + sockfd = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* Binds the socket to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd, self, 0)); + + /* + * Forbids to connect the socket to address with port[0], + * cause just one ruleset layer has connect() access rule. + */ + ASSERT_EQ(-1, connect_variant(variant, sockfd, self, 0)); + ASSERT_EQ(EACCES, errno); + + /* Closes socket. */ + ASSERT_EQ(0, close(sockfd)); +} + TEST_HARNESS_MAIN From patchwork Mon Aug 29 17:03:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958252 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 800C1C6FA83 for ; Mon, 29 Aug 2022 17:06:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231411AbiH2RGW (ORCPT ); Mon, 29 Aug 2022 13:06:22 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37754 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231276AbiH2RFZ (ORCPT ); Mon, 29 Aug 2022 13:05:25 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0A81F9CCEE; Mon, 29 Aug 2022 10:04:34 -0700 (PDT) Received: from fraeml703-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGn3tHKz67wrR; Tue, 30 Aug 2022 01:04:01 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml703-chm.china.huawei.com (10.206.15.52) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:32 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:31 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 15/18] seltests/landlock: add ruleset expanding test Date: Tue, 30 Aug 2022 01:03:58 +0800 Message-ID: <20220829170401.834298-16-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch adds expanding rulesets in which rules are gradually added one by one, restricting sockets' connections. Signed-off-by: Konstantin Meskhidze --- Changes since v6: * None. Changes since v5: * Formats code with clang-format-14. Changes since v4: * Refactors code with self->port, self->addr4 variables. Changes since v3: * Adds ruleset_expanding test. --- tools/testing/selftests/landlock/net_test.c | 166 ++++++++++++++++++++ 1 file changed, 166 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index b3b38745f4eb..a93224d1521b 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -553,4 +553,170 @@ TEST_F(socket, ruleset_overlap) ASSERT_EQ(0, close(sockfd)); } +TEST_F(socket, ruleset_expanding) +{ + int sockfd_1, sockfd_2; + int one = 1; + + struct landlock_ruleset_attr ruleset_attr_1 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd_1 = landlock_create_ruleset( + &ruleset_attr_1, sizeof(ruleset_attr_1), 0); + ASSERT_LE(0, ruleset_fd_1); + + /* Adds rule to port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_1); + ASSERT_EQ(0, close(ruleset_fd_1)); + + /* Creates a socket 1. */ + sockfd_1 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_1); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* Binds the socket 1 to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0)); + + /* Makes connection to socket 1 with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, sockfd_1, self, 0)); + + /* Closes socket 1. */ + ASSERT_EQ(0, close(sockfd_1)); + + /* Creates a socket 2. */ + sockfd_2 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_2); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd_2, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* + * Forbids to bind the socket 2 to address with port[1], + * cause there is no rule with bind() access for port[1]. + */ + ASSERT_EQ(-1, bind_variant(variant, sockfd_2, self, 1)); + ASSERT_EQ(EACCES, errno); + + /* Expands network mask. */ + struct landlock_ruleset_attr ruleset_attr_2 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + + /* Adds connect() access to port[0]. */ + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + + .port = self->port[0], + }; + /* Adds bind() access to port[1]. */ + struct landlock_net_service_attr net_service_3 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[1], + }; + + const int ruleset_fd_2 = landlock_create_ruleset( + &ruleset_attr_2, sizeof(ruleset_attr_2), 0); + ASSERT_LE(0, ruleset_fd_2); + + /* Adds rule to port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + /* Adds rule to port[1] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE, + &net_service_3, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_2); + ASSERT_EQ(0, close(ruleset_fd_2)); + + /* Creates a socket 1. */ + sockfd_1 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_1); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* Binds the socket 1 to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0)); + + /* Makes connection to socket 1 with port[0]. */ + ASSERT_EQ(0, connect_variant(variant, sockfd_1, self, 0)); + + /* Closes socket 1. */ + ASSERT_EQ(0, close(sockfd_1)); + + /* Creates a socket 2. */ + sockfd_2 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_2); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd_2, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* + * Forbids to bind the socket 2 to address with port[1], + * cause just one layer has bind() access rule. + */ + ASSERT_EQ(-1, bind_variant(variant, sockfd_1, self, 1)); + ASSERT_EQ(EACCES, errno); + + /* Expands network mask. */ + struct landlock_ruleset_attr ruleset_attr_3 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + + /* Restricts connect() access to port[0]. */ + struct landlock_net_service_attr net_service_4 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd_3 = landlock_create_ruleset( + &ruleset_attr_3, sizeof(ruleset_attr_3), 0); + ASSERT_LE(0, ruleset_fd_3); + + /* Adds rule to port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_3, LANDLOCK_RULE_NET_SERVICE, + &net_service_4, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_3); + ASSERT_EQ(0, close(ruleset_fd_3)); + + /* Creates a socket 1. */ + sockfd_1 = create_socket_variant(variant, SOCK_STREAM); + ASSERT_LE(0, sockfd_1); + /* Allows to reuse of local address. */ + ASSERT_EQ(0, setsockopt(sockfd_1, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one))); + + /* Binds the socket 1 to address with port[0]. */ + ASSERT_EQ(0, bind_variant(variant, sockfd_1, self, 0)); + + /* + * Forbids to connect the socket 1 to address with port[0], + * cause just one layer has connect() access rule. + */ + ASSERT_EQ(-1, connect_variant(variant, sockfd_1, self, 0)); + ASSERT_EQ(EACCES, errno); + + /* Closes socket 1. */ + ASSERT_EQ(0, close(sockfd_1)); +} TEST_HARNESS_MAIN From patchwork Mon Aug 29 17:03:59 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958254 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BC832C6FA81 for ; Mon, 29 Aug 2022 17:07:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231423AbiH2RHE (ORCPT ); Mon, 29 Aug 2022 13:07:04 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43030 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231331AbiH2RFg (ORCPT ); Mon, 29 Aug 2022 13:05:36 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B99DB9C230; Mon, 29 Aug 2022 10:04:36 -0700 (PDT) Received: from fraeml702-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGq0Xjgz688y7; Tue, 30 Aug 2022 01:04:03 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml702-chm.china.huawei.com (10.206.15.51) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:34 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:33 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 16/18] seltests/landlock: add invalid input data test Date: Tue, 30 Aug 2022 01:03:59 +0800 Message-ID: <20220829170401.834298-17-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch adds rules with invalid user space supplied data: - out of range ruleset attribute; - unhandled allowed access; - zero port value; - zero access value; Signed-off-by: Konstantin Meskhidze --- Changes since v6: * Adds invalid ruleset attribute test. Changes since v5: * Formats code with clang-format-14. Changes since v4: * Refactors code with self->port variable. Changes since v3: * Adds inval test. --- tools/testing/selftests/landlock/net_test.c | 66 ++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index a93224d1521b..067ba45f58a5 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -26,9 +26,12 @@ #define IP_ADDRESS "127.0.0.1" -/* Number pending connections queue to be hold */ +/* Number pending connections queue to be hold. */ #define BACKLOG 10 +/* Invalid attribute, out of landlock network access range. */ +#define LANDLOCK_INVAL_ATTR 7 + FIXTURE(socket) { uint port[MAX_SOCKET_NUM]; @@ -719,4 +722,65 @@ TEST_F(socket, ruleset_expanding) /* Closes socket 1. */ ASSERT_EQ(0, close(sockfd_1)); } + +TEST_F(socket, inval) +{ + 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_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = 0, + }; + struct landlock_net_service_attr net_service_3 = { + .allowed_access = 0, + .port = self->port[1], + }; + struct landlock_net_service_attr net_service_4 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->port[2], + }; + + /* 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)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); +} TEST_HARNESS_MAIN From patchwork Mon Aug 29 17:04:00 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958251 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 96C82C6FA69 for ; Mon, 29 Aug 2022 17:06:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231256AbiH2RGV (ORCPT ); Mon, 29 Aug 2022 13:06:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42134 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231351AbiH2RFZ (ORCPT ); Mon, 29 Aug 2022 13:05:25 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 780EC9D106; Mon, 29 Aug 2022 10:04:38 -0700 (PDT) Received: from fraeml701-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcGr5mWSz67sHf; Tue, 30 Aug 2022 01:04:04 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml701-chm.china.huawei.com (10.206.15.50) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:35 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:34 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 17/18] samples/landlock: add network demo Date: Tue, 30 Aug 2022 01:04:00 +0800 Message-ID: <20220829170401.834298-18-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: 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 --- 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 | 123 +++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 11 deletions(-) -- 2.25.1 diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 771b6b10d519..7f88067534df 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -51,6 +51,8 @@ static inline int landlock_restrict_self(const int ruleset_fd, #define ENV_FS_RO_NAME "LL_FS_RO" #define ENV_FS_RW_NAME "LL_FS_RW" +#define ENV_TCP_BIND_NAME "LL_TCP_BIND" +#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" #define ENV_PATH_TOKEN ":" static int parse_path(char *env_path, const char ***const path_list) @@ -71,6 +73,20 @@ static int parse_path(char *env_path, const char ***const path_list) return num_paths; } +static int parse_port_num(char *env_port) +{ + int i, num_ports = 0; + + if (env_port) { + num_ports++; + for (i = 0; env_port[i]; i++) { + if (env_port[i] == ENV_PATH_TOKEN[0]) + num_ports++; + } + } + return num_ports; +} + /* clang-format off */ #define ACCESS_FILE ( \ @@ -81,8 +97,8 @@ static int parse_path(char *env_path, const char ***const path_list) /* clang-format on */ -static int populate_ruleset(const char *const env_var, const int ruleset_fd, - const __u64 allowed_access) +static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd, + const __u64 allowed_access) { int num_paths, i, ret = 1; char *env_path_name; @@ -143,6 +159,48 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd, return ret; } +static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, + const __u64 allowed_access) +{ + int num_ports, i, ret = 1; + char *env_port_name; + struct landlock_net_service_attr net_service = { + .allowed_access = 0, + .port = 0, + }; + + env_port_name = getenv(env_var); + if (!env_port_name) { + ret = 0; + goto out_free_name; + } + env_port_name = strdup(env_port_name); + unsetenv(env_var); + num_ports = parse_port_num(env_port_name); + + if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) { + ret = 0; + goto out_free_name; + } + + for (i = 0; i < num_ports; i++) { + net_service.allowed_access = allowed_access; + net_service.port = atoi(strsep(&env_port_name, ENV_PATH_TOKEN)); + if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service, 0)) { + fprintf(stderr, + "Failed to update the ruleset with port \"%d\": %s\n", + net_service.port, strerror(errno)); + goto out_free_name; + } + } + ret = 0; + +out_free_name: + free(env_port_name); + return ret; +} + /* clang-format off */ #define ACCESS_FS_ROUGHLY_READ ( \ @@ -171,32 +229,50 @@ int main(const int argc, char *const argv[], char *const *const envp) const char *cmd_path; char *const *cmd_argv; int ruleset_fd, abi; + char *env_port_name; __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, - access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; + access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE, + access_net_tcp = 0; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = access_fs_rw, + .handled_access_net = access_net_tcp, }; if (argc < 2) { fprintf(stderr, - "usage: %s=\"...\" %s=\"...\" %s [args]...\n\n", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s " + " [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", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, + ENV_TCP_CONNECT_NAME, argv[0]); return 1; } @@ -224,15 +300,32 @@ int main(const int argc, char *const argv[], char *const *const envp) } return 1; } + + /* Adds optionally network bind() support. */ + env_port_name = getenv(ENV_TCP_BIND_NAME); + if (env_port_name) { + access_net_tcp |= LANDLOCK_ACCESS_NET_BIND_TCP; + } + /* Adds optionally network connect() support. */ + env_port_name = getenv(ENV_TCP_CONNECT_NAME); + if (env_port_name) { + access_net_tcp |= LANDLOCK_ACCESS_NET_CONNECT_TCP; + } + ruleset_attr.handled_access_net = access_net_tcp; + /* Best-effort security. */ switch (abi) { case 1: /* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; + /* Removes network support for ABI < 2 */ + ruleset_attr.handled_access_net = 0; __attribute__((fallthrough)); case 2: /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; + /* Removes network support for ABI < 3 */ + ruleset_attr.handled_access_net = 0; } access_fs_ro &= ruleset_attr.handled_access_fs; access_fs_rw &= ruleset_attr.handled_access_fs; @@ -243,10 +336,18 @@ int main(const int argc, char *const argv[], char *const *const envp) perror("Failed to create a ruleset"); return 1; } - if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) { + if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) { + goto err_close_ruleset; + } + if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) { + goto err_close_ruleset; + } + if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd, + LANDLOCK_ACCESS_NET_BIND_TCP)) { goto err_close_ruleset; } - if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) { + if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd, + LANDLOCK_ACCESS_NET_CONNECT_TCP)) { goto err_close_ruleset; } if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { From patchwork Mon Aug 29 17:04:01 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Konstantin Meskhidze (A)" X-Patchwork-Id: 12958253 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D0898C6FA82 for ; Mon, 29 Aug 2022 17:06:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231366AbiH2RGZ (ORCPT ); Mon, 29 Aug 2022 13:06:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42256 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231315AbiH2RF0 (ORCPT ); Mon, 29 Aug 2022 13:05:26 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6D9DA9D64F; Mon, 29 Aug 2022 10:04:40 -0700 (PDT) Received: from fraeml745-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4MGcCG2XVCz67Ybb; Tue, 30 Aug 2022 01:00:58 +0800 (CST) Received: from lhrpeml500004.china.huawei.com (7.191.163.9) by fraeml745-chm.china.huawei.com (10.206.15.226) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.31; Mon, 29 Aug 2022 19:04:37 +0200 Received: from mscphis00759.huawei.com (10.123.66.134) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.24; Mon, 29 Aug 2022 18:04:36 +0100 From: Konstantin Meskhidze To: CC: , , , , , , , Subject: [PATCH v7 18/18] landlock: Document Landlock's network support Date: Tue, 30 Aug 2022 01:04:01 +0800 Message-ID: <20220829170401.834298-19-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> References: <20220829170401.834298-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.66.134] X-ClientProxiedBy: mscpeml100002.china.huawei.com (7.188.26.75) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Describe network access rules for TCP sockets. Add network access example in the tutorial. Point out AF_UNSPEC socket family behaviour. Point out UDP sockets issues. Add kernel configuration support for network. Signed-off-by: Konstantin Meskhidze --- Changes since v6: * Adds network support documentaion. --- Documentation/userspace-api/landlock.rst | 84 +++++++++++++++++++----- 1 file changed, 66 insertions(+), 18 deletions(-) -- 2.25.1 diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst index 2509c2fbf98f..4b099d1b5a9d 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -11,10 +11,10 @@ Landlock: unprivileged access control :Date: August 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 3 a port "object" appears with related network actions +for TCP4/TCP6 sockets 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,9 +75,9 @@ 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 and +third version of the ABI. .. code-block:: c @@ -87,9 +92,13 @@ the ABI. /* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; __attribute__((fallthrough)); + /* Removes network support for ABI < 2 */ + ruleset_attr.handled_access_net = 0; case 2: /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; + /* Removes network support for ABI < 3 */ + ruleset_attr.handled_access_net = 0; } This enables to create an inclusive ruleset that will contain our rules. @@ -129,6 +138,24 @@ descriptor. } err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0); + +It may also be required to create rules following the same logic as explained +for the ruleset creation, by filtering access rights according to the Landlock +ABI version. In this example, this is not required because all of the requested +`allowed_access` rights are already available in ABI 1. + +For network part we can add number of rules containing a port number and actions +that a process is allowed to do for certian ports. + +.. code-block:: c + + struct landlock_net_service_attr net_service = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = 8080, + }; + + err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service, 0); close(path_beneath.parent_fd); if (err) { perror("Failed to update ruleset"); @@ -136,13 +163,9 @@ descriptor. return 1; } -It may also be required to create rules following the same logic as explained -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 +denying all other handled accesses for the filesystem. The ruleset also contains +a rule allowing to bind current proccess to the port 8080. The next step is to restrict the current thread from gaining more privileges (e.g. thanks to a SUID binary). @@ -280,6 +303,13 @@ It should also be noted that truncating files does not require the system call, this can also be done through :manpage:`open(2)` with the flags `O_RDONLY | O_TRUNC`. +AF_UNSPEC socket family +----------------------- + +Sockets of AF_UNSPEC family types are treated as AF_INET(TCP4) socket for bind() +hook. But connect() hook is not allowed by Landlock for AF_UNSPEC sockets. This +logic prevents from disconnecting already connected sockets. + Compatibility ============= @@ -339,7 +369,7 @@ Access rights ------------- .. kernel-doc:: include/uapi/linux/landlock.h - :identifiers: fs_access + :identifiers: fs_access net_access Creating a new ruleset ---------------------- @@ -358,6 +388,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 ------------------- @@ -406,6 +437,13 @@ Memory usage Kernel memory allocated to create rulesets is accounted and can be restricted by the Documentation/admin-guide/cgroup-v1/memory.rst. +UDP sockets restricting +----------------------- + +Current network part supports to restrict just TCP sockets type. UPD sockets sandboxing +adds additional issues due to unconnected nature of the protocol. UDP sockets support +might come in future Landlock versions. + Previous limitations ==================== @@ -435,6 +473,13 @@ always allowed when using a kernel that only supports the first or second ABI. Starting with the Landlock ABI version 3, it is now possible to securely control truncation thanks to the new `LANDLOCK_ACCESS_FS_TRUNCATE` access right. +Network support (ABI < 3) +------------------------- + +Starting with the Landlock ABI version 3, it is now possible to restrict TCP +sockets bind() and connect() syscalls for specific ports allowing processes +to establish secure connections. + .. _kernel_support: Kernel support @@ -453,6 +498,9 @@ still enable it by adding ``lsm=landlock,[...]`` to Documentation/admin-guide/kernel-parameters.rst thanks to the bootloader configuration. +To support Landlock's network part, the kernel must be configured with `CONFIG_NET=y` +and `CONFIG_INET=y` options. For TCP6 family sockets `CONFIG_IPV6=y` must be switched on. + Questions and answers =====================