From patchwork Mon May 16 15:20:24 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: 12850968 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 4BECAC433EF for ; Mon, 16 May 2022 15:20:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238130AbiEPPUx (ORCPT ); Mon, 16 May 2022 11:20:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57566 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245166AbiEPPUv (ORCPT ); Mon, 16 May 2022 11:20:51 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1B6B63981A; Mon, 16 May 2022 08:20:50 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22y51Jvyz67x9X; Mon, 16 May 2022 23:20:45 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:47 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 01/15] landlock: access mask renaming Date: Mon, 16 May 2022 23:20:24 +0800 Message-ID: <20220516152038.39594-2-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Currently Landlock supports filesystem restrictions. To support network type rules, this modification extends and renames ruleset's access masks. This patch adds filesystem helper functions to set and get filesystem mask. Also the modification adds a helper structure landlock_access_mask to support managing multiple access mask. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Splits commit. * Adds get_mask, set_mask helpers for filesystem. * Adds new struct landlock_access_mask. Changes since v4: * Deletes struct landlock_access_mask. --- security/landlock/fs.c | 15 ++++++++------- security/landlock/ruleset.c | 25 +++++++++++++------------ security/landlock/ruleset.h | 27 ++++++++++++++++++++++----- security/landlock/syscalls.c | 7 ++++--- 4 files changed, 47 insertions(+), 27 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index ec5a6247cd3e..8eea52e5a3a4 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -167,7 +167,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, return -EINVAL; /* Transforms relative access rights to absolute ones. */ - access_rights |= LANDLOCK_MASK_ACCESS_FS & ~ruleset->fs_access_masks[0]; + access_rights |= LANDLOCK_MASK_ACCESS_FS & + ~landlock_get_fs_access_mask(ruleset, 0); object = get_inode_object(d_backing_inode(path->dentry)); if (IS_ERR(object)) return PTR_ERR(object); @@ -285,9 +286,9 @@ get_handled_accesses(const struct landlock_ruleset *const domain) size_t layer_level; for (layer_level = 0; layer_level < domain->num_layers; - layer_level++) { - if (domain->fs_access_masks[layer_level] & - BIT_ULL(access_bit)) { + layer_level++) { + if (landlock_get_fs_access_mask(domain, layer_level) & + BIT_ULL(access_bit)) { access_dom |= BIT_ULL(access_bit); break; } @@ -315,9 +316,9 @@ init_layer_masks(const struct landlock_ruleset *const domain, unsigned long access_bit; for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - if (domain->fs_access_masks[layer_level] & - BIT_ULL(access_bit)) { + ARRAY_SIZE(*layer_masks)) { + if (landlock_get_fs_access_mask(domain, layer_level) & + BIT_ULL(access_bit)) { (*layer_masks)[access_bit] |= BIT_ULL(layer_level); handled_accesses |= BIT_ULL(access_bit); diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 996484f98bfd..b8917f6a8050 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -28,9 +28,9 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) { struct landlock_ruleset *new_ruleset; - new_ruleset = - kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers), - GFP_KERNEL_ACCOUNT); + new_ruleset = kzalloc(struct_size(new_ruleset, access_masks, + num_layers), GFP_KERNEL_ACCOUNT); + if (!new_ruleset) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); @@ -40,22 +40,23 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) /* * hierarchy = NULL * num_rules = 0 - * fs_access_masks[] = 0 + * access_masks[] = 0 */ return new_ruleset; } -struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask) +struct landlock_ruleset *landlock_create_ruleset( + const access_mask_t access_mask) { struct landlock_ruleset *new_ruleset; /* Informs about useless ruleset. */ - if (!fs_access_mask) + if (!access_mask) return ERR_PTR(-ENOMSG); new_ruleset = create_ruleset(1); if (!IS_ERR(new_ruleset)) - new_ruleset->fs_access_masks[0] = fs_access_mask; + landlock_set_fs_access_mask(new_ruleset, access_mask, 0); + return new_ruleset; } @@ -117,7 +118,7 @@ static void build_check_ruleset(void) .num_rules = ~0, .num_layers = ~0, }; - typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0; + typeof(ruleset.access_masks[0]) fs_access_mask = ~0; BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES); BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); @@ -281,7 +282,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst, err = -EINVAL; goto out_unlock; } - dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0]; + dst->access_masks[dst->num_layers - 1] = src->access_masks[0]; /* Merges the @src tree. */ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root, @@ -340,8 +341,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, goto out_unlock; } /* Copies the parent layer stack and leaves a space for the new layer. */ - memcpy(child->fs_access_masks, parent->fs_access_masks, - flex_array_size(parent, fs_access_masks, parent->num_layers)); + memcpy(child->access_masks, parent->access_masks, + flex_array_size(parent, access_masks, parent->num_layers)); if (WARN_ON_ONCE(!parent->hierarchy)) { err = -EINVAL; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index d43231b783e4..f27a79624962 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -20,6 +20,7 @@ #include "object.h" typedef u16 access_mask_t; + /* Makes sure all filesystem access rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ @@ -110,7 +111,7 @@ struct landlock_ruleset { * section. This is only used by * landlock_put_ruleset_deferred() when @usage reaches zero. * The fields @lock, @usage, @num_rules, @num_layers and - * @fs_access_masks are then unused. + * @access_masks are then unused. */ struct work_struct work_free; struct { @@ -137,7 +138,7 @@ struct landlock_ruleset { */ u32 num_layers; /** - * @fs_access_masks: Contains the subset of filesystem + * @access_masks: Contains the subset of filesystem * actions that are restricted by a ruleset. A domain * saves all layers of merged rulesets in a stack * (FAM), starting from the first layer to the last @@ -148,13 +149,13 @@ struct landlock_ruleset { * layers are set once and never changed for the * lifetime of the ruleset. */ - access_mask_t fs_access_masks[]; + u32 access_masks[]; }; }; }; -struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask); +struct landlock_ruleset *landlock_create_ruleset( + const access_mask_t access_mask); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); @@ -177,4 +178,20 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) refcount_inc(&ruleset->usage); } +/* A helper function to set a filesystem mask */ +static inline void landlock_set_fs_access_mask(struct landlock_ruleset *ruleset, + const access_mask_t access_maskset, + u16 mask_level) +{ + ruleset->access_masks[mask_level] = access_maskset; +} + +/* A helper function to get a filesystem mask */ +static inline u32 landlock_get_fs_access_mask( + const struct landlock_ruleset *ruleset, + u16 mask_level) +{ + return ruleset->access_masks[mask_level]; +} + #endif /* _SECURITY_LANDLOCK_RULESET_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 735a0865ea11..1db799d1a50b 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -346,10 +346,11 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, } /* * Checks that allowed_access matches the @ruleset constraints - * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits). + * (ruleset->access_masks[0] is automatically upgraded to 64-bits). */ - if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) != - ruleset->fs_access_masks[0]) { + if ((path_beneath_attr.allowed_access | + landlock_get_fs_access_mask(ruleset, 0)) != + landlock_get_fs_access_mask(ruleset, 0)) { err = -EINVAL; goto out_put_ruleset; } From patchwork Mon May 16 15:20:25 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: 12850969 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 2F7CDC433F5 for ; Mon, 16 May 2022 15:21:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236208AbiEPPVX (ORCPT ); Mon, 16 May 2022 11:21:23 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57736 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245185AbiEPPUz (ORCPT ); Mon, 16 May 2022 11:20:55 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 133873B550; Mon, 16 May 2022 08:20:52 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22tk1nNdz688Zv; Mon, 16 May 2022 23:17:50 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:49 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 02/15] landlock: landlock_find/insert_rule refactoring Date: Mon, 16 May 2022 23:20:25 +0800 Message-ID: <20220516152038.39594-3-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: A new object union added to support a socket port rule type. To support it landlock_insert_rule() and landlock_find_rule() were refactored. Now adding or searching a rule in a ruleset depends on a rule_type argument provided in refactored functions mentioned above. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Refactoring landlock_insert_rule and landlock_find_rule functions. * Rename new_ruleset->root_inode. Changes since v4: * Refactoring insert_rule() and create_rule() functions by deleting rule_type from their arguments list, it helps to reduce useless code. --- security/landlock/fs.c | 8 ++- security/landlock/ruleset.c | 129 +++++++++++++++++++++++++----------- security/landlock/ruleset.h | 32 +++++---- 3 files changed, 113 insertions(+), 56 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 8eea52e5a3a4..5de24d4dd74c 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -173,7 +173,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, if (IS_ERR(object)) return PTR_ERR(object); mutex_lock(&ruleset->lock); - err = landlock_insert_rule(ruleset, object, access_rights); + err = landlock_insert_rule(ruleset, object, 0, access_rights, + LANDLOCK_RULE_PATH_BENEATH); mutex_unlock(&ruleset->lock); /* * No need to check for an error because landlock_insert_rule() @@ -203,8 +204,9 @@ find_rule(const struct landlock_ruleset *const domain, inode = d_backing_inode(dentry); rcu_read_lock(); - rule = landlock_find_rule( - domain, rcu_dereference(landlock_inode(inode)->object)); + rule = landlock_find_rule(domain, + (uintptr_t)rcu_dereference(landlock_inode(inode)->object), + LANDLOCK_RULE_PATH_BENEATH); rcu_read_unlock(); return rule; } diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index b8917f6a8050..f079a2a320f1 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -35,7 +35,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); mutex_init(&new_ruleset->lock); - new_ruleset->root = RB_ROOT; + new_ruleset->root_inode = RB_ROOT; new_ruleset->num_layers = num_layers; /* * hierarchy = NULL @@ -69,10 +69,12 @@ static void build_check_rule(void) BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS); } -static struct landlock_rule * -create_rule(struct landlock_object *const object, - const struct landlock_layer (*const layers)[], const u32 num_layers, - const struct landlock_layer *const new_layer) +static struct landlock_rule *create_rule( + struct landlock_object *const object_ptr, + const uintptr_t object_data, + const struct landlock_layer (*const layers)[], + const u32 num_layers, + const struct landlock_layer *const new_layer) { struct landlock_rule *new_rule; u32 new_num_layers; @@ -91,8 +93,15 @@ create_rule(struct landlock_object *const object, if (!new_rule) return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); - landlock_get_object(object); - new_rule->object = object; + + if (object_ptr) { + landlock_get_object(object_ptr); + new_rule->object.ptr = object_ptr; + } else if (object_ptr && object_data) { + WARN_ON_ONCE(1); + return ERR_PTR(-EINVAL); + } + new_rule->num_layers = new_num_layers; /* Copies the original layer stack. */ memcpy(new_rule->layers, layers, @@ -108,7 +117,7 @@ static void free_rule(struct landlock_rule *const rule) might_sleep(); if (!rule) return; - landlock_put_object(rule->object); + landlock_put_object(rule->object.ptr); kfree(rule); } @@ -144,26 +153,44 @@ static void build_check_ruleset(void) * access rights. */ static int insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, - const struct landlock_layer (*const layers)[], - size_t num_layers) + struct landlock_object *const object_ptr, + uintptr_t object_data, u16 rule_type, + const struct landlock_layer (*const layers)[], + size_t num_layers) { struct rb_node **walker_node; struct rb_node *parent_node = NULL; struct landlock_rule *new_rule; + struct rb_root *root; might_sleep(); lockdep_assert_held(&ruleset->lock); - if (WARN_ON_ONCE(!object || !layers)) + /* Choose rb_tree structure depending on a rule type */ + + if (WARN_ON_ONCE(!layers)) return -ENOENT; - walker_node = &(ruleset->root.rb_node); + if (WARN_ON_ONCE(object_ptr && object_data)) + return -EINVAL; + + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + if (WARN_ON_ONCE(!object_ptr)) + return -ENOENT; + object_data = (uintptr_t)object_ptr; + root = &ruleset->root_inode; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } + walker_node = &root->rb_node; while (*walker_node) { struct landlock_rule *const this = rb_entry(*walker_node, struct landlock_rule, node); - if (this->object != object) { + if (this->object.data != object_data) { parent_node = *walker_node; - if (this->object < object) + if (this->object.data < object_data) walker_node = &((*walker_node)->rb_right); else walker_node = &((*walker_node)->rb_left); @@ -195,11 +222,16 @@ static int insert_rule(struct landlock_ruleset *const ruleset, * Intersects access rights when it is a merge between a * ruleset and a domain. */ - new_rule = create_rule(object, &this->layers, this->num_layers, - &(*layers)[0]); + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + new_rule = create_rule(object_ptr, 0, &this->layers, + this->num_layers, + &(*layers)[0]); + break; + } if (IS_ERR(new_rule)) return PTR_ERR(new_rule); - rb_replace_node(&this->node, &new_rule->node, &ruleset->root); + rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode); free_rule(this); return 0; } @@ -208,11 +240,15 @@ static int insert_rule(struct landlock_ruleset *const ruleset, build_check_ruleset(); if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES) return -E2BIG; - new_rule = create_rule(object, layers, num_layers, NULL); + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL); + break; + } if (IS_ERR(new_rule)) return PTR_ERR(new_rule); rb_link_node(&new_rule->node, parent_node, walker_node); - rb_insert_color(&new_rule->node, &ruleset->root); + rb_insert_color(&new_rule->node, &ruleset->root_inode); ruleset->num_rules++; return 0; } @@ -230,8 +266,10 @@ static void build_check_layer(void) /* @ruleset must be locked by the caller. */ int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, - const access_mask_t access) + struct landlock_object *const object_ptr, + const uintptr_t object_data, + const access_mask_t access, + const u16 rule_type) { struct landlock_layer layers[] = { { .access = access, @@ -240,7 +278,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, } }; build_check_layer(); - return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers)); + return insert_rule(ruleset, object_ptr, object_data, rule_type, &layers, + ARRAY_SIZE(layers)); } static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy) @@ -285,9 +324,9 @@ static int merge_ruleset(struct landlock_ruleset *const dst, dst->access_masks[dst->num_layers - 1] = src->access_masks[0]; /* Merges the @src tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root, - node) { - struct landlock_layer layers[] = { { + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, + &src->root_inode, node) { + struct landlock_layer layers[] = {{ .level = dst->num_layers, } }; @@ -300,7 +339,9 @@ static int merge_ruleset(struct landlock_ruleset *const dst, goto out_unlock; } layers[0].access = walker_rule->layers[0].access; - err = insert_rule(dst, walker_rule->object, &layers, + + err = insert_rule(dst, walker_rule->object.ptr, 0, + LANDLOCK_RULE_PATH_BENEATH, &layers, ARRAY_SIZE(layers)); if (err) goto out_unlock; @@ -328,10 +369,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, /* Copies the @parent tree. */ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &parent->root, node) { - err = insert_rule(child, walker_rule->object, - &walker_rule->layers, - walker_rule->num_layers); + &parent->root_inode, node) { + err = insert_rule(child, walker_rule->object.ptr, 0, + LANDLOCK_RULE_PATH_BENEATH, &walker_rule->layers, + walker_rule->num_layers); if (err) goto out_unlock; } @@ -362,7 +403,8 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) struct landlock_rule *freeme, *next; might_sleep(); - rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node) + rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, + node) free_rule(freeme); put_hierarchy(ruleset->hierarchy); kfree(ruleset); @@ -452,22 +494,31 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent, /* * The returned access has the same lifetime as @ruleset. */ -const struct landlock_rule * -landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object) +const struct landlock_rule *landlock_find_rule( + const struct landlock_ruleset *const ruleset, + const uintptr_t object_data, const u16 rule_type) { const struct rb_node *node; - if (!object) + if (!object_data) return NULL; - node = ruleset->root.rb_node; + + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + node = ruleset->root_inode.rb_node; + break; + default: + WARN_ON_ONCE(1); + return NULL; + } + while (node) { struct landlock_rule *this = rb_entry(node, struct landlock_rule, node); - if (this->object == object) + if (this->object.data == object_data) return this; - if (this->object < object) + if (this->object.data < object_data) node = node->rb_right; else node = node->rb_left; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index f27a79624962..3066e5d7180c 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -54,15 +54,17 @@ struct landlock_rule { */ struct rb_node node; /** - * @object: Pointer to identify a kernel object (e.g. an inode). This - * is used as a key for this ruleset element. This pointer is set once - * and never modified. It always points to an allocated object because - * each rule increments the refcount of its object. - */ - struct landlock_object *object; - /** - * @num_layers: Number of entries in @layers. + * @object: A union to identify either a kernel object (e.g. an inode) or + * a raw data value (e.g. a network socket port). This is used as a key + * for this ruleset element. This pointer/@object.ptr/ is set once and + * never modified. It always points to an allocated object because each + * rule increments the refcount of its object (for inodes); */ + union { + struct landlock_object *ptr; + uintptr_t data; + } object; + u32 num_layers; /** * @layers: Stack of layers, from the latest to the newest, implemented @@ -99,7 +101,7 @@ struct landlock_ruleset { * nodes. Once a ruleset is tied to a process (i.e. as a domain), this * tree is immutable until @usage reaches zero. */ - struct rb_root root; + struct rb_root root_inode; /** * @hierarchy: Enables hierarchy identification even when a parent * domain vanishes. This is needed for the ptrace protection. @@ -161,16 +163,18 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); int landlock_insert_rule(struct landlock_ruleset *const ruleset, - struct landlock_object *const object, - const access_mask_t access); + struct landlock_object *const object_ptr, + const uintptr_t object_data, + const access_mask_t access, + const u16 rule_type); struct landlock_ruleset * landlock_merge_ruleset(struct landlock_ruleset *const parent, struct landlock_ruleset *const ruleset); -const struct landlock_rule * -landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_object *const object); +const struct landlock_rule *landlock_find_rule( + const struct landlock_ruleset *const ruleset, + const uintptr_t object_data, const u16 rule_type); static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) { From patchwork Mon May 16 15:20:26 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: 12850970 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 DA13EC4332F for ; Mon, 16 May 2022 15:21:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245217AbiEPPVZ (ORCPT ); Mon, 16 May 2022 11:21:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57740 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245186AbiEPPUz (ORCPT ); Mon, 16 May 2022 11:20:55 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3FF02393D1; Mon, 16 May 2022 08:20:53 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22y82WxJz67wkW; Mon, 16 May 2022 23:20:48 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:51 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 03/15] landlock: merge and inherit function refactoring Date: Mon, 16 May 2022 23:20:26 +0800 Message-ID: <20220516152038.39594-4-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Merge_ruleset() and inherit_ruleset() functions were refactored to support new rule types. This patch adds tree_merge() and tree_copy() helpers. Each has rule_type argument to choose a particular rb_tree structure in a ruleset. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Refactoring functions: -insert_rule. -merge_ruleset. -tree_merge. -inherit_ruleset. -tree_copy. -free_rule. Changes since v4: * None --- security/landlock/ruleset.c | 144 ++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 46 deletions(-) -- 2.25.1 diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index f079a2a320f1..4b4c9953bb32 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -112,12 +112,16 @@ static struct landlock_rule *create_rule( return new_rule; } -static void free_rule(struct landlock_rule *const rule) +static void free_rule(struct landlock_rule *const rule, const u16 rule_type) { might_sleep(); if (!rule) return; - landlock_put_object(rule->object.ptr); + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + landlock_put_object(rule->object.ptr); + break; + } kfree(rule); } @@ -227,12 +231,12 @@ static int insert_rule(struct landlock_ruleset *const ruleset, new_rule = create_rule(object_ptr, 0, &this->layers, this->num_layers, &(*layers)[0]); + if (IS_ERR(new_rule)) + return PTR_ERR(new_rule); + rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode); + free_rule(this, rule_type); break; } - if (IS_ERR(new_rule)) - return PTR_ERR(new_rule); - rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode); - free_rule(this); return 0; } @@ -243,13 +247,12 @@ static int insert_rule(struct landlock_ruleset *const ruleset, switch (rule_type) { case LANDLOCK_RULE_PATH_BENEATH: new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL); + if (IS_ERR(new_rule)) + return PTR_ERR(new_rule); + rb_link_node(&new_rule->node, parent_node, walker_node); + rb_insert_color(&new_rule->node, &ruleset->root_inode); break; } - if (IS_ERR(new_rule)) - return PTR_ERR(new_rule); - rb_link_node(&new_rule->node, parent_node, walker_node); - rb_insert_color(&new_rule->node, &ruleset->root_inode); - ruleset->num_rules++; return 0; } @@ -298,10 +301,53 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy) } } +static int tree_merge(struct landlock_ruleset *const src, + struct landlock_ruleset *const dst, u16 rule_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *src_root; + int err = 0; + + /* Choose rb_tree structure depending on a rule type */ + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + src_root = &src->root_inode; + break; + default: + return -EINVAL; + } + /* Merges the @src tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, + src_root, node) { + struct landlock_layer layers[] = {{ + .level = dst->num_layers, + }}; + + if (WARN_ON_ONCE(walker_rule->num_layers != 1)) { + err = -EINVAL; + return err; + } + if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) { + err = -EINVAL; + return err; + } + layers[0].access = walker_rule->layers[0].access; + + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + err = insert_rule(dst, walker_rule->object.ptr, 0, rule_type, + &layers, ARRAY_SIZE(layers)); + break; + } + if (err) + return err; + } + return err; +} + static int merge_ruleset(struct landlock_ruleset *const dst, struct landlock_ruleset *const src) { - struct landlock_rule *walker_rule, *next_rule; int err = 0; might_sleep(); @@ -323,29 +369,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst, } dst->access_masks[dst->num_layers - 1] = src->access_masks[0]; - /* Merges the @src tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &src->root_inode, node) { - struct landlock_layer layers[] = {{ - .level = dst->num_layers, - } }; - - if (WARN_ON_ONCE(walker_rule->num_layers != 1)) { - err = -EINVAL; - goto out_unlock; - } - if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) { - err = -EINVAL; - goto out_unlock; - } - layers[0].access = walker_rule->layers[0].access; - - err = insert_rule(dst, walker_rule->object.ptr, 0, - LANDLOCK_RULE_PATH_BENEATH, &layers, - ARRAY_SIZE(layers)); - if (err) - goto out_unlock; - } + /* Merges the @src inode tree. */ + err = tree_merge(src, dst, LANDLOCK_RULE_PATH_BENEATH); + if (err) + goto out_unlock; out_unlock: mutex_unlock(&src->lock); @@ -353,10 +380,40 @@ static int merge_ruleset(struct landlock_ruleset *const dst, return err; } +static int tree_copy(struct landlock_ruleset *const parent, + struct landlock_ruleset *const child, u16 rule_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *parent_root; + int err = 0; + + /* Choose rb_tree structure depending on a rule type */ + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + parent_root = &parent->root_inode; + break; + default: + return -EINVAL; + } + /* Copies the @parent inode tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, + parent_root, node) { + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + err = insert_rule(child, walker_rule->object.ptr, 0, + rule_type, &walker_rule->layers, + walker_rule->num_layers); + break; + } + if (err) + return err; + } + return err; +} + static int inherit_ruleset(struct landlock_ruleset *const parent, struct landlock_ruleset *const child) { - struct landlock_rule *walker_rule, *next_rule; int err = 0; might_sleep(); @@ -367,15 +424,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, mutex_lock(&child->lock); mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING); - /* Copies the @parent tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - &parent->root_inode, node) { - err = insert_rule(child, walker_rule->object.ptr, 0, - LANDLOCK_RULE_PATH_BENEATH, &walker_rule->layers, - walker_rule->num_layers); - if (err) - goto out_unlock; - } + /* Copies the @parent inode tree. */ + err = tree_copy(parent, child, LANDLOCK_RULE_PATH_BENEATH); + if (err) + goto out_unlock; if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) { err = -EINVAL; @@ -405,7 +457,7 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) might_sleep(); rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, node) - free_rule(freeme); + free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH); put_hierarchy(ruleset->hierarchy); kfree(ruleset); } From patchwork Mon May 16 15:20:27 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: 12850971 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 43A77C4167B for ; Mon, 16 May 2022 15:21:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245201AbiEPPVZ (ORCPT ); Mon, 16 May 2022 11:21:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58084 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245194AbiEPPVD (ORCPT ); Mon, 16 May 2022 11:21:03 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9F0CE3B3F1; Mon, 16 May 2022 08:20:54 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22y94yGFz67XGR; Mon, 16 May 2022 23:20:49 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:52 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 04/15] landlock: helper functions refactoring Date: Mon, 16 May 2022 23:20:27 +0800 Message-ID: <20220516152038.39594-5-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Unmask_layers(), init_layer_masks() and get_handled_accesses() helper functions move to ruleset.c and rule_type argument is added. This modification supports implementing new rule types into next landlock versions. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Splits commit. * Refactoring landlock_unmask_layers functions. Changes since v4: * Refactoring init_layer_masks(), get_handled_accesses() and unmask_layers() functions to support multiple rule types. * Refactoring landlock_get_fs_access_mask() function with LANDLOCK_MASK_ACCESS_FS mask. --- security/landlock/fs.c | 158 ++++++++---------------------------- security/landlock/ruleset.c | 152 +++++++++++++++++++++++++++++++--- security/landlock/ruleset.h | 17 +++- 3 files changed, 192 insertions(+), 135 deletions(-) -- 2.25.1 diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 5de24d4dd74c..3506e182b23e 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -211,60 +211,6 @@ find_rule(const struct landlock_ruleset *const domain, return rule; } -/* - * @layer_masks is read and may be updated according to the access request and - * the matching rule. - * - * Returns true if the request is allowed (i.e. relevant layer masks for the - * request are empty). - */ -static inline bool -unmask_layers(const struct landlock_rule *const rule, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) -{ - size_t layer_level; - - if (!access_request || !layer_masks) - return true; - if (!rule) - return false; - - /* - * An access is granted if, for each policy layer, at least one rule - * encountered on the pathwalk grants the requested access, - * regardless of its position in the layer stack. We must then check - * the remaining layers for each inode, from the first added layer to - * the last one. When there is multiple requested accesses, for each - * policy layer, the full set of requested accesses may not be granted - * by only one rule, but by the union (binary OR) of multiple rules. - * E.g. /a/b + /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 @@ -277,59 +223,6 @@ static inline bool is_nouser_or_private(const struct dentry *dentry) unlikely(IS_PRIVATE(d_backing_inode(dentry)))); } -static inline access_mask_t -get_handled_accesses(const struct landlock_ruleset *const domain) -{ - access_mask_t access_dom = 0; - unsigned long access_bit; - - for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS; - access_bit++) { - size_t layer_level; - - for (layer_level = 0; layer_level < domain->num_layers; - layer_level++) { - if (landlock_get_fs_access_mask(domain, layer_level) & - BIT_ULL(access_bit)) { - access_dom |= BIT_ULL(access_bit); - break; - } - } - } - return access_dom; -} - -static inline access_mask_t -init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) -{ - access_mask_t handled_accesses = 0; - size_t layer_level; - - memset(layer_masks, 0, sizeof(*layer_masks)); - /* An empty access request can happen because of O_WRONLY | O_RDWR. */ - if (!access_request) - return 0; - - /* Saves all handled accesses per layer. */ - for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { - const unsigned long access_req = access_request; - unsigned long access_bit; - - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(*layer_masks)) { - if (landlock_get_fs_access_mask(domain, layer_level) & - BIT_ULL(access_bit)) { - (*layer_masks)[access_bit] |= - BIT_ULL(layer_level); - handled_accesses |= BIT_ULL(access_bit); - } - } - } - return handled_accesses; -} - /* * Check that a destination file hierarchy has more restrictions than a source * file hierarchy. This is only used for link and rename actions. @@ -506,7 +399,8 @@ static int check_access_path_dual( * a superset of the meaningful requested accesses). */ access_masked_parent1 = access_masked_parent2 = - get_handled_accesses(domain); + get_handled_accesses(domain, LANDLOCK_RULE_PATH_BENEATH, + LANDLOCK_NUM_ACCESS_FS); is_dom_check = true; } else { if (WARN_ON_ONCE(dentry_child1 || dentry_child2)) @@ -519,17 +413,25 @@ static int check_access_path_dual( if (unlikely(dentry_child1)) { unmask_layers(find_rule(domain, dentry_child1), - init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child1), - &_layer_masks_child1); + init_layer_masks(domain, + LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child1, + sizeof(_layer_masks_child1), + LANDLOCK_RULE_PATH_BENEATH), + &_layer_masks_child1, + ARRAY_SIZE(_layer_masks_child1)); layer_masks_child1 = &_layer_masks_child1; child1_is_directory = d_is_dir(dentry_child1); } if (unlikely(dentry_child2)) { unmask_layers(find_rule(domain, dentry_child2), - init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child2), - &_layer_masks_child2); + init_layer_masks(domain, + LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child2, + sizeof(_layer_masks_child2), + LANDLOCK_RULE_PATH_BENEATH), + &_layer_masks_child2, + ARRAY_SIZE(_layer_masks_child2)); layer_masks_child2 = &_layer_masks_child2; child2_is_directory = d_is_dir(dentry_child2); } @@ -582,14 +484,15 @@ static int check_access_path_dual( rule = find_rule(domain, walker_path.dentry); allowed_parent1 = unmask_layers(rule, access_masked_parent1, - layer_masks_parent1); + layer_masks_parent1, + ARRAY_SIZE(*layer_masks_parent1)); allowed_parent2 = unmask_layers(rule, access_masked_parent2, - layer_masks_parent2); + layer_masks_parent2, + ARRAY_SIZE(*layer_masks_parent2)); /* Stops when a rule from each layer grants access. */ if (allowed_parent1 && allowed_parent2) break; - jump_up: if (walker_path.dentry == walker_path.mnt->mnt_root) { if (follow_up(&walker_path)) { @@ -645,7 +548,9 @@ static inline int check_access_path(const struct landlock_ruleset *const domain, { layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; - access_request = init_layer_masks(domain, access_request, &layer_masks); + access_request = init_layer_masks(domain, access_request, + &layer_masks, sizeof(layer_masks), + LANDLOCK_RULE_PATH_BENEATH); return check_access_path_dual(domain, path, access_request, &layer_masks, NULL, 0, NULL, NULL); } @@ -729,7 +634,8 @@ static bool collect_domain_accesses( return true; access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - layer_masks_dom); + layer_masks_dom, sizeof(*layer_masks_dom), + LANDLOCK_RULE_PATH_BENEATH); dget(dir); while (true) { @@ -737,7 +643,8 @@ static bool collect_domain_accesses( /* Gets all layers allowing all domain accesses. */ if (unmask_layers(find_rule(domain, dir), access_dom, - layer_masks_dom)) { + layer_masks_dom, + ARRAY_SIZE(*layer_masks_dom))) { /* * Stops when all handled accesses are allowed by at * least one rule in each layer. @@ -851,9 +758,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, * The LANDLOCK_ACCESS_FS_REFER access right is not required * for same-directory referer (i.e. no reparenting). */ - access_request_parent1 = init_layer_masks( - dom, access_request_parent1 | access_request_parent2, - &layer_masks_parent1); + access_request_parent1 = init_layer_masks(dom, + access_request_parent1 | access_request_parent2, + &layer_masks_parent1, sizeof(layer_masks_parent1), + LANDLOCK_RULE_PATH_BENEATH); return check_access_path_dual(dom, new_dir, access_request_parent1, &layer_masks_parent1, NULL, 0, @@ -861,7 +769,9 @@ static int current_check_refer_path(struct dentry *const old_dentry, } /* Backward compatibility: no reparenting support. */ - if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER)) + if (!(get_handled_accesses(dom, LANDLOCK_RULE_PATH_BENEATH, + LANDLOCK_NUM_ACCESS_FS) & + LANDLOCK_ACCESS_FS_REFER)) return -EXDEV; access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER; diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 4b4c9953bb32..c4ed783d655b 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -233,7 +233,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset, &(*layers)[0]); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); - rb_replace_node(&this->node, &new_rule->node, &ruleset->root_inode); + rb_replace_node(&this->node, &new_rule->node, + &ruleset->root_inode); free_rule(this, rule_type); break; } @@ -246,7 +247,8 @@ static int insert_rule(struct landlock_ruleset *const ruleset, return -E2BIG; switch (rule_type) { case LANDLOCK_RULE_PATH_BENEATH: - new_rule = create_rule(object_ptr, 0, layers, num_layers, NULL); + new_rule = create_rule(object_ptr, 0, layers, + num_layers, NULL); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); rb_link_node(&new_rule->node, parent_node, walker_node); @@ -281,8 +283,8 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, } }; build_check_layer(); - return insert_rule(ruleset, object_ptr, object_data, rule_type, &layers, - ARRAY_SIZE(layers)); + return insert_rule(ruleset, object_ptr, object_data, rule_type, + &layers, ARRAY_SIZE(layers)); } static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy) @@ -335,8 +337,9 @@ static int tree_merge(struct landlock_ruleset *const src, switch (rule_type) { case LANDLOCK_RULE_PATH_BENEATH: - err = insert_rule(dst, walker_rule->object.ptr, 0, rule_type, - &layers, ARRAY_SIZE(layers)); + err = insert_rule(dst, walker_rule->object.ptr, 0, + rule_type, &layers, + ARRAY_SIZE(layers)); break; } if (err) @@ -433,9 +436,13 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, err = -EINVAL; goto out_unlock; } - /* Copies the parent layer stack and leaves a space for the new layer. */ + /* + * Copies the parent layer stack and leaves a space + * for the new layer. + */ memcpy(child->access_masks, parent->access_masks, - flex_array_size(parent, access_masks, parent->num_layers)); + flex_array_size(parent, access_masks, + parent->num_layers)); if (WARN_ON_ONCE(!parent->hierarchy)) { err = -EINVAL; @@ -455,8 +462,9 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) struct landlock_rule *freeme, *next; might_sleep(); - rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, - node) + rbtree_postorder_for_each_entry_safe(freeme, next, + &ruleset->root_inode, + node) free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH); put_hierarchy(ruleset->hierarchy); kfree(ruleset); @@ -577,3 +585,127 @@ const struct landlock_rule *landlock_find_rule( } return NULL; } + +access_mask_t get_handled_accesses( + const struct landlock_ruleset *const domain, + u16 rule_type, u16 num_access) +{ + access_mask_t access_dom = 0; + unsigned long access_bit; + + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS; + access_bit++) { + size_t layer_level; + + for (layer_level = 0; layer_level < domain->num_layers; + layer_level++) { + if (landlock_get_fs_access_mask(domain, + layer_level) & + BIT_ULL(access_bit)) { + access_dom |= BIT_ULL(access_bit); + break; + } + } + } + break; + default: + break; + } + return access_dom; +} + +/* + * @layer_masks is read and may be updated according to the access request and + * the matching rule. + * + * Returns true if the request is allowed (i.e. relevant layer masks for the + * request are empty). + */ +bool unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], size_t masks_array_size) +{ + size_t layer_level; + + if (!access_request || !layer_masks) + return true; + if (!rule) + return false; + + /* + * An access is granted if, for each policy layer, at least one rule + * encountered on the pathwalk grants the requested access, + * regardless of its position in the layer stack. We must then check + * the remaining layers for each inode, from the first added layer to + * the last one. When there is multiple requested accesses, for each + * policy layer, the full set of requested accesses may not be granted + * by only one rule, but by the union (binary OR) of multiple rules. + * E.g. /a/b + /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, masks_array_size) { + if (layer->access & BIT_ULL(access_bit)) + (*layer_masks)[access_bit] &= ~layer_bit; + is_empty = is_empty && !(*layer_masks)[access_bit]; + } + if (is_empty) + return true; + } + return false; +} + +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + size_t masks_size, + u16 rule_type) +{ + access_mask_t handled_accesses = 0; + size_t layer_level; + + memset(layer_masks, 0, masks_size); + + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ + if (!access_request) + return 0; + + /* Saves all handled accesses per layer. */ + for (layer_level = 0; layer_level < domain->num_layers; + layer_level++) { + const unsigned long access_req = access_request; + unsigned long access_bit; + + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + for_each_set_bit(access_bit, &access_req, + LANDLOCK_NUM_ACCESS_FS) { + if (landlock_get_fs_access_mask(domain, + layer_level) & + BIT_ULL(access_bit)) { + (*layer_masks)[access_bit] |= + BIT_ULL(layer_level); + handled_accesses |= + BIT_ULL(access_bit); + } + } + break; + default: + return 0; + } + } + return handled_accesses; +} diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 3066e5d7180c..f3cd890d0348 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -195,7 +195,22 @@ static inline u32 landlock_get_fs_access_mask( const struct landlock_ruleset *ruleset, u16 mask_level) { - return ruleset->access_masks[mask_level]; + return (ruleset->access_masks[mask_level] & LANDLOCK_MASK_ACCESS_FS); } +access_mask_t get_handled_accesses( + const struct landlock_ruleset *const domain, + u16 rule_type, u16 num_access); + +bool unmask_layers(const struct landlock_rule *const rule, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + size_t masks_array_size); + +access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, + const access_mask_t access_request, + layer_mask_t (*const layer_masks)[], + size_t masks_size, + u16 rule_type); + #endif /* _SECURITY_LANDLOCK_RULESET_H */ From patchwork Mon May 16 15:20:28 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: 12850982 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 423CDC433F5 for ; Mon, 16 May 2022 15:21:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245202AbiEPPVx (ORCPT ); Mon, 16 May 2022 11:21:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58084 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245199AbiEPPVE (ORCPT ); Mon, 16 May 2022 11:21:04 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DE31E3BA74; Mon, 16 May 2022 08:20:55 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22yB6mHvz6H6sk; Mon, 16 May 2022 23:20:50 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:53 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 05/15] landlock: landlock_add_rule syscall refactoring Date: Mon, 16 May 2022 23:20:28 +0800 Message-ID: <20220516152038.39594-6-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Landlock_add_rule syscall was refactored to support new rule types in future Landlock versions. Add_rule_path_beneath() helper was added to support current filesystem rules. It is called by the switch case. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Refactoring landlock_add_rule syscall. Changes since v4: * Refactoring add_rule_path_beneath() and landlock_add_rule() functions to optimize code usage. * Refactoring base_test.c seltest: adds LANDLOCK_RULE_PATH_BENEATH rule type in landlock_add_rule() call. --- security/landlock/syscalls.c | 105 ++++++++++--------- tools/testing/selftests/landlock/base_test.c | 4 +- 2 files changed, 59 insertions(+), 50 deletions(-) -- 2.25.1 diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 1db799d1a50b..412ced6c512f 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -274,67 +274,23 @@ static int get_path_from_fd(const s32 fd, struct path *const path) return err; } -/** - * sys_landlock_add_rule - Add a new rule to a ruleset - * - * @ruleset_fd: File descriptor tied to the ruleset that should be extended - * with the new rule. - * @rule_type: Identify the structure type pointed to by @rule_attr (only - * LANDLOCK_RULE_PATH_BENEATH for now). - * @rule_attr: Pointer to a rule (only of type &struct - * landlock_path_beneath_attr for now). - * @flags: Must be 0. - * - * This system call enables to define a new rule and add it to an existing - * ruleset. - * - * Possible returned errors are: - * - * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; - * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. - * &landlock_path_beneath_attr.allowed_access is not a subset of the - * ruleset handled accesses); - * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); - * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a - * member of @rule_attr is not a file descriptor as expected; - * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of - * @rule_attr is not the expected file descriptor type; - * - EPERM: @ruleset_fd has no write access to the underlying ruleset; - * - EFAULT: @rule_attr inconsistency. - */ -SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, - const enum landlock_rule_type, rule_type, - const void __user *const, rule_attr, const __u32, flags) +static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_attr) { struct landlock_path_beneath_attr path_beneath_attr; struct path path; struct landlock_ruleset *ruleset; int res, err; - if (!landlock_initialized) - return -EOPNOTSUPP; - - /* No flag for now. */ - if (flags) - return -EINVAL; - /* Gets and checks the ruleset. */ ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); - if (rule_type != LANDLOCK_RULE_PATH_BENEATH) { - err = -EINVAL; - goto out_put_ruleset; - } - /* Copies raw user space buffer, only one type for now. */ res = copy_from_user(&path_beneath_attr, rule_attr, - sizeof(path_beneath_attr)); - if (res) { - err = -EFAULT; - goto out_put_ruleset; - } + sizeof(path_beneath_attr)); + if (res) + return -EFAULT; /* * Informs about useless rule: empty allowed_access (i.e. deny rules) @@ -370,6 +326,59 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, return err; } +/** + * sys_landlock_add_rule - Add a new rule to a ruleset + * + * @ruleset_fd: File descriptor tied to the ruleset that should be extended + * with the new rule. + * @rule_type: Identify the structure type pointed to by @rule_attr (only + * LANDLOCK_RULE_PATH_BENEATH for now). + * @rule_attr: Pointer to a rule (only of type &struct + * landlock_path_beneath_attr for now). + * @flags: Must be 0. + * + * This system call enables to define a new rule and add it to an existing + * ruleset. + * + * Possible returned errors are: + * + * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; + * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. + * &landlock_path_beneath_attr.allowed_access is not a subset of the rule's + * accesses); + * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access); + * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a + * member of @rule_attr is not a file descriptor as expected; + * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of + * @rule_attr is not the expected file descriptor type (e.g. file open + * without O_PATH); + * - EPERM: @ruleset_fd has no write access to the underlying ruleset; + * - EFAULT: @rule_attr inconsistency. + */ +SYSCALL_DEFINE4(landlock_add_rule, + const int, ruleset_fd, const enum landlock_rule_type, rule_type, + const void __user *const, rule_attr, const __u32, flags) +{ + int err; + + if (!landlock_initialized) + return -EOPNOTSUPP; + + /* No flag for now. */ + if (flags) + return -EINVAL; + + switch (rule_type) { + case LANDLOCK_RULE_PATH_BENEATH: + err = add_rule_path_beneath(ruleset_fd, rule_attr); + break; + default: + err = -EINVAL; + break; + } + return err; +} + /* Enforcement */ /** diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index da9290817866..0c4c3a538d54 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -156,11 +156,11 @@ TEST(add_rule_checks_ordering) ASSERT_LE(0, ruleset_fd); /* Checks invalid flags. */ - ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1)); + ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, NULL, 1)); ASSERT_EQ(EINVAL, errno); /* Checks invalid ruleset FD. */ - ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0)); + ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, NULL, 0)); ASSERT_EQ(EBADF, errno); /* Checks invalid rule type. */ From patchwork Mon May 16 15:20:29 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: 12850972 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 04F2FC433FE for ; Mon, 16 May 2022 15:21:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245230AbiEPPV3 (ORCPT ); Mon, 16 May 2022 11:21:29 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58272 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245227AbiEPPVF (ORCPT ); Mon, 16 May 2022 11:21:05 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id ABE613BF81; Mon, 16 May 2022 08:20:57 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22t25XvXz67Klm; Mon, 16 May 2022 23:17:14 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:54 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 06/15] landlock: user space API network support Date: Mon, 16 May 2022 23:20:29 +0800 Message-ID: <20220516152038.39594-7-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: User space API was refactored to support network actions. New network access flags, network rule and network attributes were added. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Refactoring User API for network rule type. Changes since v4: * None --- include/uapi/linux/landlock.h | 48 +++++++++++++++++++++++++++++++++++ security/landlock/syscalls.c | 3 ++- 2 files changed, 50 insertions(+), 1 deletion(-) -- 2.25.1 diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 23df4e0e8ace..91d6cb359bf8 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -31,6 +31,13 @@ struct landlock_ruleset_attr { * this access right. */ __u64 handled_access_fs; + + /** + * @handled_access_net: Bitmask of actions (cf. `Network flags`_) + * that is handled by this ruleset and should then be forbidden if no + * rule explicitly allow them. + */ + __u64 handled_access_net; }; /* @@ -54,6 +61,11 @@ enum landlock_rule_type { * landlock_path_beneath_attr . */ LANDLOCK_RULE_PATH_BENEATH = 1, + /** + * @LANDLOCK_RULE_NET_SERVICE: Type of a &struct + * landlock_net_service_attr . + */ + LANDLOCK_RULE_NET_SERVICE = 2, }; /** @@ -79,6 +91,24 @@ struct landlock_path_beneath_attr { */ } __attribute__((packed)); +/** + * struct landlock_net_service_attr - TCP subnet definition + * + * Argument of sys_landlock_add_rule(). + */ +struct landlock_net_service_attr { + /** + * @allowed_access: Bitmask of allowed access network for services + * (cf. `Network flags`_). + */ + __u64 allowed_access; + /** + * @port: Network port + */ + __u16 port; + +} __attribute__((packed)); + /** * DOC: fs_access * @@ -162,4 +192,22 @@ struct landlock_path_beneath_attr { #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) /* clang-format on */ +/** + * DOC: net_access + * + * Network flags + * ~~~~~~~~~~~~~~~~ + * + * These flags enable to restrict a sandboxed process to a set of network + * actions. + * + * TCP sockets with allowed actions: + * + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port. + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to + * a remote port. + */ +#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0) +#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) + #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 412ced6c512f..31f9facec123 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -82,8 +82,9 @@ static void build_check_abi(void) * struct size. */ ruleset_size = sizeof(ruleset_attr.handled_access_fs); + ruleset_size += sizeof(ruleset_attr.handled_access_net); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 8); + BUILD_BUG_ON(sizeof(ruleset_attr) != 16); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); From patchwork Mon May 16 15:20:30 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: 12850975 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 1F39BC4332F for ; Mon, 16 May 2022 15:21:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245253AbiEPPVa (ORCPT ); Mon, 16 May 2022 11:21:30 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58086 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245232AbiEPPVG (ORCPT ); Mon, 16 May 2022 11:21:06 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C4F4D3BF94; Mon, 16 May 2022 08:20:59 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22t44nJjz67Zm5; Mon, 16 May 2022 23:17:16 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:56 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 07/15] landlock: add support network rules Date: Mon, 16 May 2022 23:20:30 +0800 Message-ID: <20220516152038.39594-8-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This modification adds network rules support in internal landlock functions (presented in ruleset.c) and landlock_create_ruleset syscall. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Add network rule support for internal landlock functions. * Add set_mask and get_mask for network. * Add rb_root root_net_port. Changes since v4: * Refactoring landlock_create_ruleset() - splits ruleset and masks checks. * Refactoring landlock_create_ruleset() and landlock mask setters/getters to support two rule types. * Refactoring landlock_add_rule syscall add_rule_path_beneath function by factoring out get_ruleset_from_fd() and landlock_put_ruleset(). --- security/landlock/limits.h | 8 +++- security/landlock/ruleset.c | 82 +++++++++++++++++++++++++++++++----- security/landlock/ruleset.h | 34 +++++++++++++-- security/landlock/syscalls.c | 45 +++++++++++--------- 4 files changed, 132 insertions(+), 37 deletions(-) -- 2.25.1 diff --git a/security/landlock/limits.h b/security/landlock/limits.h index b54184ab9439..23694bf05cb7 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -22,6 +22,12 @@ #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) -/* clang-format on */ +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP +#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) +#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) +#define LANDLOCK_MASK_SHIFT_NET 16 + +#define LANDLOCK_RULE_TYPE_NUM LANDLOCK_RULE_NET_SERVICE +/* clang-format on */ #endif /* _SECURITY_LANDLOCK_LIMITS_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index c4ed783d655b..ea9ecb3f471a 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -36,6 +36,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) refcount_set(&new_ruleset->usage, 1); mutex_init(&new_ruleset->lock); new_ruleset->root_inode = RB_ROOT; + new_ruleset->root_net_port = RB_ROOT; new_ruleset->num_layers = num_layers; /* * hierarchy = NULL @@ -46,17 +47,21 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) } struct landlock_ruleset *landlock_create_ruleset( - const access_mask_t access_mask) + const access_mask_t access_mask_fs, + const access_mask_t access_mask_net) { struct landlock_ruleset *new_ruleset; /* Informs about useless ruleset. */ - if (!access_mask) + if (!access_mask_fs && !access_mask_net) return ERR_PTR(-ENOMSG); new_ruleset = create_ruleset(1); - if (!IS_ERR(new_ruleset)) - landlock_set_fs_access_mask(new_ruleset, access_mask, 0); - + if (IS_ERR(new_ruleset)) + return new_ruleset; + if (access_mask_fs) + landlock_set_fs_access_mask(new_ruleset, access_mask_fs, 0); + if (access_mask_net) + landlock_set_net_access_mask(new_ruleset, access_mask_net, 0); return new_ruleset; } @@ -94,9 +99,11 @@ static struct landlock_rule *create_rule( return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); - if (object_ptr) { + if (object_ptr && !object_data) { landlock_get_object(object_ptr); new_rule->object.ptr = object_ptr; + } else if (object_data && !object_ptr) { + new_rule->object.data = object_data; } else if (object_ptr && object_data) { WARN_ON_ONCE(1); return ERR_PTR(-EINVAL); @@ -132,10 +139,12 @@ static void build_check_ruleset(void) .num_layers = ~0, }; typeof(ruleset.access_masks[0]) fs_access_mask = ~0; + typeof(ruleset.access_masks[0]) net_access_mask = ~0; BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES); BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS); + BUILD_BUG_ON(net_access_mask < LANDLOCK_MASK_ACCESS_NET); } /** @@ -183,6 +192,11 @@ static int insert_rule(struct landlock_ruleset *const ruleset, object_data = (uintptr_t)object_ptr; root = &ruleset->root_inode; break; + case LANDLOCK_RULE_NET_SERVICE: + if (WARN_ON_ONCE(object_ptr)) + return -EINVAL; + root = &ruleset->root_net_port; + break; default: WARN_ON_ONCE(1); return -EINVAL; @@ -237,6 +251,16 @@ static int insert_rule(struct landlock_ruleset *const ruleset, &ruleset->root_inode); free_rule(this, rule_type); break; + case LANDLOCK_RULE_NET_SERVICE: + new_rule = create_rule(NULL, object_data, + &this->layers, this->num_layers, + &(*layers)[0]); + if (IS_ERR(new_rule)) + return PTR_ERR(new_rule); + rb_replace_node(&this->node, &new_rule->node, + &ruleset->root_net_port); + free_rule(this, rule_type); + break; } return 0; } @@ -254,6 +278,15 @@ static int insert_rule(struct landlock_ruleset *const ruleset, rb_link_node(&new_rule->node, parent_node, walker_node); rb_insert_color(&new_rule->node, &ruleset->root_inode); break; + case LANDLOCK_RULE_NET_SERVICE: + new_rule = create_rule(NULL, object_data, layers, + num_layers, NULL); + if (IS_ERR(new_rule)) + return PTR_ERR(new_rule); + rb_link_node(&new_rule->node, parent_node, walker_node); + rb_insert_color(&new_rule->node, &ruleset->root_net_port); + ruleset->num_rules++; + break; } return 0; } @@ -315,6 +348,9 @@ static int tree_merge(struct landlock_ruleset *const src, case LANDLOCK_RULE_PATH_BENEATH: src_root = &src->root_inode; break; + case LANDLOCK_RULE_NET_SERVICE: + src_root = &src->root_net_port; + break; default: return -EINVAL; } @@ -341,6 +377,11 @@ static int tree_merge(struct landlock_ruleset *const src, rule_type, &layers, ARRAY_SIZE(layers)); break; + case LANDLOCK_RULE_NET_SERVICE: + err = insert_rule(dst, NULL, walker_rule->object.data, + rule_type, &layers, + ARRAY_SIZE(layers)); + break; } if (err) return err; @@ -376,6 +417,10 @@ static int merge_ruleset(struct landlock_ruleset *const dst, err = tree_merge(src, dst, LANDLOCK_RULE_PATH_BENEATH); if (err) goto out_unlock; + /* Merges the @src network tree. */ + err = tree_merge(src, dst, LANDLOCK_RULE_NET_SERVICE); + if (err) + goto out_unlock; out_unlock: mutex_unlock(&src->lock); @@ -395,6 +440,9 @@ static int tree_copy(struct landlock_ruleset *const parent, case LANDLOCK_RULE_PATH_BENEATH: parent_root = &parent->root_inode; break; + case LANDLOCK_RULE_NET_SERVICE: + parent_root = &parent->root_net_port; + break; default: return -EINVAL; } @@ -407,6 +455,12 @@ static int tree_copy(struct landlock_ruleset *const parent, rule_type, &walker_rule->layers, walker_rule->num_layers); break; + case LANDLOCK_RULE_NET_SERVICE: + err = insert_rule(child, NULL, + walker_rule->object.data, rule_type, + &walker_rule->layers, + walker_rule->num_layers); + break; } if (err) return err; @@ -429,6 +483,10 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, /* Copies the @parent inode tree. */ err = tree_copy(parent, child, LANDLOCK_RULE_PATH_BENEATH); + if (err) + goto out_unlock; + /* Copies the @parent inode tree. */ + err = tree_copy(parent, child, LANDLOCK_RULE_NET_SERVICE); if (err) goto out_unlock; @@ -463,9 +521,11 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) might_sleep(); rbtree_postorder_for_each_entry_safe(freeme, next, - &ruleset->root_inode, - node) + &ruleset->root_inode, node) free_rule(freeme, LANDLOCK_RULE_PATH_BENEATH); + rbtree_postorder_for_each_entry_safe(freeme, next, + &ruleset->root_net_port, node) + free_rule(freeme, LANDLOCK_RULE_NET_SERVICE); put_hierarchy(ruleset->hierarchy); kfree(ruleset); } @@ -560,13 +620,13 @@ const struct landlock_rule *landlock_find_rule( { const struct rb_node *node; - if (!object_data) - return NULL; - switch (rule_type) { case LANDLOCK_RULE_PATH_BENEATH: node = ruleset->root_inode.rb_node; break; + case LANDLOCK_RULE_NET_SERVICE: + node = ruleset->root_net_port.rb_node; + break; default: WARN_ON_ONCE(1); return NULL; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index f3cd890d0348..916b30b31c06 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -102,6 +102,12 @@ struct landlock_ruleset { * tree is immutable until @usage reaches zero. */ struct rb_root root_inode; + /** + * @root_net_port: Root of a red-black tree containing object nodes + * for network port. Once a ruleset is tied to a process (i.e. as a domain), + * this tree is immutable until @usage reaches zero. + */ + struct rb_root root_net_port; /** * @hierarchy: Enables hierarchy identification even when a parent * domain vanishes. This is needed for the ptrace protection. @@ -157,7 +163,8 @@ struct landlock_ruleset { }; struct landlock_ruleset *landlock_create_ruleset( - const access_mask_t access_mask); + const access_mask_t access_mask_fs, + const access_mask_t access_mask_net); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); @@ -183,11 +190,12 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) } /* A helper function to set a filesystem mask */ -static inline void landlock_set_fs_access_mask(struct landlock_ruleset *ruleset, - const access_mask_t access_maskset, +static inline void landlock_set_fs_access_mask( + struct landlock_ruleset *ruleset, + const access_mask_t access_mask_fs, u16 mask_level) { - ruleset->access_masks[mask_level] = access_maskset; + ruleset->access_masks[mask_level] = access_mask_fs; } /* A helper function to get a filesystem mask */ @@ -198,6 +206,24 @@ static inline u32 landlock_get_fs_access_mask( return (ruleset->access_masks[mask_level] & LANDLOCK_MASK_ACCESS_FS); } +/* A helper function to set a network mask */ +static inline void landlock_set_net_access_mask( + struct landlock_ruleset *ruleset, + const access_mask_t access_mask_net, + u16 mask_level) +{ + ruleset->access_masks[mask_level] |= (access_mask_net << + LANDLOCK_MASK_SHIFT_NET); +} + +/* A helper function to get a network mask */ +static inline u32 landlock_get_net_access_mask( + const struct landlock_ruleset *ruleset, + u16 mask_level) +{ + return (ruleset->access_masks[mask_level] >> LANDLOCK_MASK_SHIFT_NET); +} + access_mask_t get_handled_accesses( const struct landlock_ruleset *const domain, u16 rule_type, u16 num_access); diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 31f9facec123..812541f4e155 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -189,8 +189,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset, LANDLOCK_MASK_ACCESS_FS) return -EINVAL; + /* Checks network content (and 32-bits cast). */ + if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) != + LANDLOCK_MASK_ACCESS_NET) + return -EINVAL; + /* Checks arguments and transforms to kernel struct. */ - ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs); + ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, + ruleset_attr.handled_access_net); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); @@ -275,21 +281,17 @@ static int get_path_from_fd(const s32 fd, struct path *const path) return err; } -static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_attr) +static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, + const void *const rule_attr) { struct landlock_path_beneath_attr path_beneath_attr; struct path path; - struct landlock_ruleset *ruleset; int res, err; - - /* Gets and checks the ruleset. */ - ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); - if (IS_ERR(ruleset)) - return PTR_ERR(ruleset); + u32 mask; /* Copies raw user space buffer, only one type for now. */ res = copy_from_user(&path_beneath_attr, rule_attr, - sizeof(path_beneath_attr)); + sizeof(path_beneath_attr)); if (res) return -EFAULT; @@ -298,32 +300,26 @@ static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_at * are ignored in path walks. */ if (!path_beneath_attr.allowed_access) { - err = -ENOMSG; - goto out_put_ruleset; + return -ENOMSG; } /* * Checks that allowed_access matches the @ruleset constraints * (ruleset->access_masks[0] is automatically upgraded to 64-bits). */ - if ((path_beneath_attr.allowed_access | - landlock_get_fs_access_mask(ruleset, 0)) != - landlock_get_fs_access_mask(ruleset, 0)) { - err = -EINVAL; - goto out_put_ruleset; - } + mask = landlock_get_fs_access_mask(ruleset, 0); + if ((path_beneath_attr.allowed_access | mask) != mask) + return -EINVAL; /* Gets and checks the new rule. */ err = get_path_from_fd(path_beneath_attr.parent_fd, &path); if (err) - goto out_put_ruleset; + return err; /* Imports the new rule. */ err = landlock_append_fs_rule(ruleset, &path, path_beneath_attr.allowed_access); path_put(&path); -out_put_ruleset: - landlock_put_ruleset(ruleset); return err; } @@ -360,6 +356,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, const enum landlock_rule_type, rule_type, const void __user *const, rule_attr, const __u32, flags) { + struct landlock_ruleset *ruleset; int err; if (!landlock_initialized) @@ -369,14 +366,20 @@ SYSCALL_DEFINE4(landlock_add_rule, if (flags) return -EINVAL; + /* Gets and checks the ruleset. */ + ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); + if (IS_ERR(ruleset)) + return PTR_ERR(ruleset); + switch (rule_type) { case LANDLOCK_RULE_PATH_BENEATH: - err = add_rule_path_beneath(ruleset_fd, rule_attr); + err = add_rule_path_beneath(ruleset, rule_attr); break; default: err = -EINVAL; break; } + landlock_put_ruleset(ruleset); return err; } From patchwork Mon May 16 15:20:31 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: 12850973 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 BBE25C43217 for ; Mon, 16 May 2022 15:21:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245186AbiEPPV3 (ORCPT ); Mon, 16 May 2022 11:21:29 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58382 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245242AbiEPPVH (ORCPT ); Mon, 16 May 2022 11:21:07 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 957B73BFA5; Mon, 16 May 2022 08:21:00 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22t60LyDz67n8d; Mon, 16 May 2022 23:17:18 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:58 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 08/15] landlock: TCP network hooks implementation Date: Mon, 16 May 2022 23:20:31 +0800 Message-ID: <20220516152038.39594-9-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Support of socket_bind() and socket_connect() hooks. Its possible to restrict binding and connecting of TCP types of sockets to particular ports. Its just basic idea how Landlock could support network confinement. Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Add SECURITY_NETWORK in config. * Add IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration. * Add hook_socket_bind and hook_socket_connect hooks. Changes since v4: * Factors out CONFIG_INET into make file. * Refactoring check_socket_access(). * Adds helper get_port(). * Adds CONFIG_IPV6 in get_port(), hook_socket_bind/connect functions to support AF_INET6 family. * Adds AF_UNSPEC family support in hook_socket_bind/connect functions. * Refactoring add_rule_net_service() and landlock_add_rule syscall to support network rule inserting. * Refactoring init_layer_masks() to support network rules. --- security/landlock/Kconfig | 1 + security/landlock/Makefile | 2 + security/landlock/net.c | 159 +++++++++++++++++++++++++++++++++++ security/landlock/net.h | 25 ++++++ security/landlock/ruleset.c | 15 +++- security/landlock/setup.c | 2 + security/landlock/syscalls.c | 63 ++++++++++++-- 7 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 security/landlock/net.c create mode 100644 security/landlock/net.h -- 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..9302e5891991 --- /dev/null +++ b/security/landlock/net.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock LSM - Network management and hooks + * + * Copyright (C) 2022 Huawei Tech. Co., Ltd. + */ + +#include +#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; + + /* Transforms relative access rights to absolute ones. */ + access_rights |= LANDLOCK_MASK_ACCESS_NET & + ~landlock_get_net_access_mask(ruleset, 0); + + BUILD_BUG_ON(sizeof(port) > sizeof(uintptr_t)); + mutex_lock(&ruleset->lock); + err = landlock_insert_rule(ruleset, NULL, port, + access_rights, LANDLOCK_RULE_NET_SERVICE); + mutex_unlock(&ruleset->lock); + + return err; +} + +static int check_socket_access(const struct landlock_ruleset *const domain, + u16 port, access_mask_t access_request) +{ + bool allowed = false; + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {}; + const struct landlock_rule *rule; + access_mask_t handled_access; + + if (WARN_ON_ONCE(!domain)) + return 0; + if (WARN_ON_ONCE(domain->num_layers < 1)) + return -EACCES; + + rule = landlock_find_rule(domain, port, + LANDLOCK_RULE_NET_SERVICE); + + handled_access = init_layer_masks(domain, access_request, + &layer_masks, sizeof(layer_masks), + LANDLOCK_RULE_NET_SERVICE); + allowed = unmask_layers(rule, handled_access, + &layer_masks, ARRAY_SIZE(layer_masks)); + + return allowed ? 0 : -EACCES; +} + +static u16 get_port(const struct sockaddr *const address) +{ + /* Gets port value in host byte order. */ + switch (address->sa_family) { + case AF_UNSPEC: + case AF_INET: + { + const struct sockaddr_in *const sockaddr = + (struct sockaddr_in *)address; + return ntohs(sockaddr->sin_port); + } +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + { + const struct sockaddr_in6 *const sockaddr_ip6 = + (struct sockaddr_in6 *)address; + return ntohs(sockaddr_ip6->sin6_port); + } +#endif + } + return 0; +} + +static int hook_socket_bind(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + if (!dom) + return 0; + + /* Check if it's a TCP socket */ + if (sock->type != SOCK_STREAM) + return 0; + + /* Get port value in host byte order */ + switch (address->sa_family) { + case AF_UNSPEC: + case AF_INET: +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: +#endif + return check_socket_access(dom, get_port(address), + LANDLOCK_ACCESS_NET_BIND_TCP); + default: + return 0; + } +} + +static int hook_socket_connect(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + if (!dom) + return 0; + + /* Check if it's a TCP socket */ + if (sock->type != SOCK_STREAM) + return 0; + + /* Get port value in host byte order */ + switch (address->sa_family) { + case AF_INET: +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: +#endif + return check_socket_access(dom, get_port(address), + LANDLOCK_ACCESS_NET_CONNECT_TCP); + case AF_UNSPEC: + { + u16 i; + /* + * If just in a layer a mask supports connect access, + * the socket_connect() hook with AF_UNSPEC family flag + * must be banned. This prevents from disconnecting already + * connected sockets. + */ + for (i = 0; i < dom->num_layers; i++) { + if (landlock_get_net_access_mask(dom, i) & + LANDLOCK_ACCESS_NET_CONNECT_TCP) + return -EACCES; + } + } + } + return 0; +} + +static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(socket_bind, hook_socket_bind), + LSM_HOOK_INIT(socket_connect, hook_socket_connect), +}; + +__init void landlock_add_net_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + LANDLOCK_NAME); +} diff --git a/security/landlock/net.h b/security/landlock/net.h new file mode 100644 index 000000000000..da5ce8fa04cc --- /dev/null +++ b/security/landlock/net.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock LSM - Network management and hooks + * + * Copyright (C) 2022 Huawei Tech. Co., Ltd. + */ + +#ifndef _SECURITY_LANDLOCK_NET_H +#define _SECURITY_LANDLOCK_NET_H + +#include "common.h" +#include "ruleset.h" +#include "setup.h" + +#if IS_ENABLED(CONFIG_INET) +__init void landlock_add_net_hooks(void); + +int landlock_append_net_rule(struct landlock_ruleset *const ruleset, + u16 port, u32 access_hierarchy); +#else /* IS_ENABLED(CONFIG_INET) */ +static inline void landlock_add_net_hooks(void) +{} +#endif /* IS_ENABLED(CONFIG_INET) */ + +#endif /* _SECURITY_LANDLOCK_NET_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index ea9ecb3f471a..317cf98890f6 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -671,7 +671,7 @@ access_mask_t get_handled_accesses( } break; default: - break; + return 0; } return access_dom; } @@ -763,6 +763,19 @@ access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, } } break; + case LANDLOCK_RULE_NET_SERVICE: + for_each_set_bit(access_bit, &access_req, + LANDLOCK_NUM_ACCESS_NET) { + if (landlock_get_net_access_mask(domain, + layer_level) & + BIT_ULL(access_bit)) { + (*layer_masks)[access_bit] |= + BIT_ULL(layer_level); + handled_accesses |= + BIT_ULL(access_bit); + } + } + break; default: return 0; } diff --git a/security/landlock/setup.c b/security/landlock/setup.c index f8e8e980454c..8059dc0b47d3 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -14,6 +14,7 @@ #include "fs.h" #include "ptrace.h" #include "setup.h" +#include "net.h" bool landlock_initialized __lsm_ro_after_init = false; @@ -28,6 +29,7 @@ static int __init landlock_init(void) landlock_add_cred_hooks(); landlock_add_ptrace_hooks(); landlock_add_fs_hooks(); + landlock_add_net_hooks(); landlock_initialized = true; pr_info("Up and running.\n"); return 0; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 812541f4e155..9454c6361011 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -29,6 +29,7 @@ #include "cred.h" #include "fs.h" #include "limits.h" +#include "net.h" #include "ruleset.h" #include "setup.h" @@ -74,7 +75,8 @@ static void build_check_abi(void) { struct landlock_ruleset_attr ruleset_attr; struct landlock_path_beneath_attr path_beneath_attr; - size_t ruleset_size, path_beneath_size; + struct landlock_net_service_attr net_service_attr; + size_t ruleset_size, path_beneath_size, net_service_size; /* * For each user space ABI structures, first checks that there is no @@ -90,6 +92,11 @@ static void build_check_abi(void) path_beneath_size += sizeof(path_beneath_attr.parent_fd); BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size); BUILD_BUG_ON(sizeof(path_beneath_attr) != 12); + + net_service_size = sizeof(net_service_attr.allowed_access); + net_service_size += sizeof(net_service_attr.port); + BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size); + BUILD_BUG_ON(sizeof(net_service_attr) != 10); } /* Ruleset handling */ @@ -299,9 +306,9 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, * Informs about useless rule: empty allowed_access (i.e. deny rules) * are ignored in path walks. */ - if (!path_beneath_attr.allowed_access) { + if (!path_beneath_attr.allowed_access) return -ENOMSG; - } + /* * Checks that allowed_access matches the @ruleset constraints * (ruleset->access_masks[0] is automatically upgraded to 64-bits). @@ -323,13 +330,54 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, return err; } +static int add_rule_net_service(struct landlock_ruleset *ruleset, + const void *const rule_attr) +{ +#if IS_ENABLED(CONFIG_INET) + struct landlock_net_service_attr net_service_attr; + int res; + u32 mask; + + /* Copies raw user space buffer, only one type for now. */ + res = copy_from_user(&net_service_attr, rule_attr, + sizeof(net_service_attr)); + if (res) + return -EFAULT; + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored by network actions + */ + if (!net_service_attr.allowed_access) + return -ENOMSG; + + /* + * Checks that allowed_access matches the @ruleset constraints + * (ruleset->access_masks[0] is automatically upgraded to 64-bits). + */ + mask = landlock_get_net_access_mask(ruleset, 0); + if ((net_service_attr.allowed_access | mask) != mask) + return -EINVAL; + + /* Denies inserting a rule with port 0 */ + if (net_service_attr.port == 0) + return -EINVAL; + + /* Imports the new rule. */ + return landlock_append_net_rule(ruleset, net_service_attr.port, + net_service_attr.allowed_access); +#else /* IS_ENABLED(CONFIG_INET) */ + return -EAFNOSUPPORT; +#endif /* IS_ENABLED(CONFIG_INET) */ +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * * @ruleset_fd: File descriptor tied to the ruleset that should be extended * with the new rule. - * @rule_type: Identify the structure type pointed to by @rule_attr (only - * LANDLOCK_RULE_PATH_BENEATH for now). + * @rule_type: Identify the structure type pointed to by @rule_attr: + * LANDLOCK_RULE_PATH_BENEATH or LANDLOCK_RULE_NET_SERVICE. * @rule_attr: Pointer to a rule (only of type &struct * landlock_path_beneath_attr for now). * @flags: Must be 0. @@ -340,6 +388,8 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, * Possible returned errors are: * * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; + * - EAFNOSUPPORT: @rule_type is LANDLOCK_RULE_NET_SERVICE but TCP/IP is not + * supported by the running kernel; * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e. * &landlock_path_beneath_attr.allowed_access is not a subset of the rule's * accesses); @@ -375,6 +425,9 @@ SYSCALL_DEFINE4(landlock_add_rule, case LANDLOCK_RULE_PATH_BENEATH: err = add_rule_path_beneath(ruleset, rule_attr); break; + case LANDLOCK_RULE_NET_SERVICE: + err = add_rule_net_service(ruleset, rule_attr); + break; default: err = -EINVAL; break; From patchwork Mon May 16 15:20:32 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: 12850976 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 75246C4167B for ; Mon, 16 May 2022 15:21:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245260AbiEPPVf (ORCPT ); Mon, 16 May 2022 11:21:35 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58144 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245266AbiEPPVI (ORCPT ); Mon, 16 May 2022 11:21:08 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BBA833BFAF; Mon, 16 May 2022 08:21:01 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22tw06Q2z67KdQ; Mon, 16 May 2022 23:18:00 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:20:59 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 09/15] seltests/landlock: add tests for bind() hooks Date: Mon, 16 May 2022 23:20:32 +0800 Message-ID: <20220516152038.39594-10-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds selftests for bind socket action. The first is with no landlock restrictions: - bind_no_restrictions_ip4; - bind_no_restrictions_ip6; The second ones is with mixed landlock rules: - bind_with_restrictions_ip4; - bind_with_restrictions_ip6; Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. * Add helper create_socket. * Add FIXTURE_SETUP. Changes since v4: * Adds port[MAX_SOCKET_NUM], struct sockaddr_in addr4 and struct sockaddr_in addr6 in FIXTURE. * Refactoring FIXTURE_SETUP: - initializing self->port, self->addr4 and self->addr6. - adding network namespace. * Refactoring code with self->port, self->addr4 and self->addr6 variables. * Adds selftests for IP6 family: - bind_no_restrictions_ip6. - bind_with_restrictions_ip6. * Refactoring selftests/landlock/config * Moves enforce_ruleset() into common.h --- tools/testing/selftests/landlock/common.h | 9 + tools/testing/selftests/landlock/config | 5 +- tools/testing/selftests/landlock/fs_test.c | 10 - tools/testing/selftests/landlock/net_test.c | 237 ++++++++++++++++++++ 4 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 tools/testing/selftests/landlock/net_test.c -- 2.25.1 diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 7ba18eb23783..c5381e641dfd 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -102,6 +102,15 @@ static inline int landlock_restrict_self(const int ruleset_fd, } #endif +static void enforce_ruleset(struct __test_metadata *const _metadata, + const int ruleset_fd) +{ + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) { + TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); + } +} + static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) { cap_t cap_p; diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config index 0f0a65287bac..b56f3274d3f5 100644 --- a/tools/testing/selftests/landlock/config +++ b/tools/testing/selftests/landlock/config @@ -1,7 +1,10 @@ +CONFIG_INET=y +CONFIG_IPV6=y +CONFIG_NET=y CONFIG_OVERLAY_FS=y CONFIG_SECURITY_LANDLOCK=y CONFIG_SECURITY_PATH=y CONFIG_SECURITY=y CONFIG_SHMEM=y CONFIG_TMPFS_XATTR=y -CONFIG_TMPFS=y +CONFIG_TMPFS=y \ No newline at end of file diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 21a2ce8fa739..036dd6f8f9ea 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -551,16 +551,6 @@ static int create_ruleset(struct __test_metadata *const _metadata, return ruleset_fd; } -static void enforce_ruleset(struct __test_metadata *const _metadata, - const int ruleset_fd) -{ - ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); - ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)) - { - TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); - } -} - TEST_F_FORK(layout1, proc_nsfs) { const struct rule rules[] = { diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c new file mode 100644 index 000000000000..478ef2eff559 --- /dev/null +++ b/tools/testing/selftests/landlock/net_test.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock tests - Network + * + * Copyright (C) 2022 Huawei Tech. Co., Ltd. + */ + +#define _GNU_SOURCE +#include +#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" + +/* Number pending connections queue to be hold */ +#define BACKLOG 10 + +static int create_socket(struct __test_metadata *const _metadata, + bool ip6, bool reuse_addr) +{ + int sockfd; + int one = 1; + + if (ip6) + sockfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0); + else + sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + + ASSERT_LE(0, sockfd); + /* Allows to reuse of local address */ + if (reuse_addr) + ASSERT_EQ(0, setsockopt(sockfd, SOL_SOCKET, + SO_REUSEADDR, &one, sizeof(one))); + return sockfd; +} + +FIXTURE(socket_test) { + uint port[MAX_SOCKET_NUM]; + struct sockaddr_in addr4[MAX_SOCKET_NUM]; + struct sockaddr_in6 addr6[MAX_SOCKET_NUM]; +}; + +FIXTURE_SETUP(socket_test) +{ + int i; + /* Creates IP4 socket addresses */ + for (i = 0; i < MAX_SOCKET_NUM; i++) { + self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD*i; + self->addr4[i].sin_family = AF_INET; + self->addr4[i].sin_port = htons(self->port[i]); + self->addr4[i].sin_addr.s_addr = htonl(INADDR_ANY); + memset(&(self->addr4[i].sin_zero), '\0', 8); + } + + /* Creates IP6 socket addresses */ + for (i = 0; i < MAX_SOCKET_NUM; i++) { + self->port[i] = SOCK_PORT_START + SOCK_PORT_ADD*i; + self->addr6[i].sin6_family = AF_INET6; + self->addr6[i].sin6_port = htons(self->port[i]); + self->addr6[i].sin6_addr = in6addr_any; + } + + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, unshare(CLONE_NEWNET)); + ASSERT_EQ(0, system("ip link set dev lo up")); + clear_cap(_metadata, CAP_SYS_ADMIN); +} + +FIXTURE_TEARDOWN(socket_test) +{ } + +TEST_F_FORK(socket_test, bind_no_restrictions_ip4) { + + int sockfd; + + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + + /* Binds a socket to port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + ASSERT_EQ(0, close(sockfd)); +} + +TEST_F_FORK(socket_test, bind_no_restrictions_ip6) { + + int sockfd; + + sockfd = create_socket(_metadata, true, false); + ASSERT_LE(0, sockfd); + + /* Binds a socket to port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0]))); + + ASSERT_EQ(0, close(sockfd)); +} + +TEST_F_FORK(socket_test, bind_with_restrictions_ip4) { + + int sockfd; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[1], + }; + struct landlock_net_service_attr net_service_3 = { + .allowed_access = 0, + .port = self->port[2], + }; + + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and deny bind operations to the port[1] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + /* Empty allowed_access (i.e. deny rules) are ignored in network actions + * for port[2] socket. + */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_3, 0)); + ASSERT_EQ(ENOMSG, errno); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Close bounded socket*/ + ASSERT_EQ(0, close(sockfd)); + + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[1] */ + ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[1]))); + ASSERT_EQ(EACCES, errno); + + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[2] */ + ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr4[2], sizeof(self->addr4[2]))); + ASSERT_EQ(EACCES, errno); +} + +TEST_F_FORK(socket_test, bind_with_restrictions_ip6) { + + int sockfd; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[1], + }; + struct landlock_net_service_attr net_service_3 = { + .allowed_access = 0, + .port = self->port[2], + }; + + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind operations to the port[0] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and deny bind operations to the port[1] socket. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + /* Empty allowed_access (i.e. deny rules) are ignored in network actions + * for port[2] socket. + */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_3, 0)); + ASSERT_EQ(ENOMSG, errno); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + sockfd = create_socket(_metadata, true, false); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0]))); + + /* Close bounded socket*/ + ASSERT_EQ(0, close(sockfd)); + + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[1] */ + ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[1], sizeof(self->addr6[1]))); + ASSERT_EQ(EACCES, errno); + + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + /* Binds a socket to port[2] */ + ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[2], sizeof(self->addr6[2]))); + ASSERT_EQ(EACCES, errno); +} +TEST_HARNESS_MAIN From patchwork Mon May 16 15:20:33 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: 12850977 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 8540EC4167D for ; Mon, 16 May 2022 15:21:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245185AbiEPPVg (ORCPT ); Mon, 16 May 2022 11:21:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58318 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245261AbiEPPVI (ORCPT ); Mon, 16 May 2022 11:21:08 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BBC093BFB1; Mon, 16 May 2022 08:21:02 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22yK6Kwhz67bhp; Mon, 16 May 2022 23:20:57 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:21:00 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 10/15] seltests/landlock: add tests for connect() hooks Date: Mon, 16 May 2022 23:20:33 +0800 Message-ID: <20220516152038.39594-11-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: Adds selftests for connect socket action. The first are with no landlock restrictions: - connect_no_restrictions_ip4; - connect_no_restrictions_ip6; The second ones are with mixed landlock rules: - connect_with_restrictions_ip4; - connect_with_restrictions_ip6; Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Split commit. Changes since v4: * Adds selftests for IP6 family: - connect_no_restrictions_ip6. - connect_with_restrictions_ip6. * Refactoring code with self->port, self->addr4 and self->addr6 variables. --- tools/testing/selftests/landlock/net_test.c | 322 ++++++++++++++++++++ 1 file changed, 322 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 478ef2eff559..cf914d311eb3 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -234,4 +234,326 @@ TEST_F_FORK(socket_test, bind_with_restrictions_ip6) { ASSERT_EQ(-1, bind(sockfd, (struct sockaddr *)&self->addr6[2], sizeof(self->addr6[2]))); ASSERT_EQ(EACCES, errno); } + +TEST_F_FORK(socket_test, connect_no_restrictions_ip4) { + + int sockfd, new_fd; + pid_t child; + int status; + + /* Creates a server socket */ + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + + /* Binds a socket to port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes listening socket */ + ASSERT_EQ(0, listen(sockfd, BACKLOG)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int child_sockfd; + + /* Closes listening socket for the child */ + ASSERT_EQ(0, close(sockfd)); + /* Create a stream client socket */ + child_sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket */ + ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Accepts connection from the child */ + new_fd = accept(sockfd, NULL, 0); + ASSERT_LE(0, new_fd); + + /* Closes connection */ + ASSERT_EQ(0, close(new_fd)); + + /* Closes listening socket for the parent*/ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} + +TEST_F_FORK(socket_test, connect_no_restrictions_ip6) { + + int sockfd, new_fd; + pid_t child; + int status; + + /* Creates a server socket */ + sockfd = create_socket(_metadata, true, false); + ASSERT_LE(0, sockfd); + + /* Binds a socket to port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0]))); + + /* Makes listening socket */ + ASSERT_EQ(0, listen(sockfd, BACKLOG)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int child_sockfd; + + /* Closes listening socket for the child */ + ASSERT_EQ(0, close(sockfd)); + /* Create a stream client socket */ + child_sockfd = create_socket(_metadata, true, false); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket */ + ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr6[0], + sizeof(self->addr6[0]))); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Accepts connection from the child */ + new_fd = accept(sockfd, NULL, 0); + ASSERT_LE(0, new_fd); + + /* Closes connection */ + ASSERT_EQ(0, close(new_fd)); + + /* Closes listening socket for the parent*/ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} + +TEST_F_FORK(socket_test, connect_with_restrictions_ip4) { + + int new_fd; + int sockfd_1, sockfd_2; + pid_t child_1, child_2; + int status; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->port[1], + }; + + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind operations to the port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and deny bind operations to the port[1] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + /* Creates a server socket 1 */ + sockfd_1 = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd_1); + + /* Binds the socket 1 to address with port[0] */ + ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes listening socket 1 */ + ASSERT_EQ(0, listen(sockfd_1, BACKLOG)); + + child_1 = fork(); + ASSERT_LE(0, child_1); + if (child_1 == 0) { + int child_sockfd; + + /* Closes listening socket for the child */ + ASSERT_EQ(0, close(sockfd_1)); + /* Creates a stream client socket */ + child_sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket */ + ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Accepts connection from the child 1 */ + new_fd = accept(sockfd_1, NULL, 0); + ASSERT_LE(0, new_fd); + + /* Closes connection */ + ASSERT_EQ(0, close(new_fd)); + + /* Closes listening socket 1 for the parent*/ + ASSERT_EQ(0, close(sockfd_1)); + + ASSERT_EQ(child_1, waitpid(child_1, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + /* Creates a server socket 2 */ + sockfd_2 = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd_2); + + /* Binds the socket 2 to address with port[1] */ + ASSERT_EQ(0, bind(sockfd_2, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[1]))); + + /* Makes listening socket 2 */ + ASSERT_EQ(0, listen(sockfd_2, BACKLOG)); + + child_2 = fork(); + ASSERT_LE(0, child_2); + if (child_2 == 0) { + int child_sockfd; + + /* Closes listening socket for the child */ + ASSERT_EQ(0, close(sockfd_2)); + /* Creates a stream client socket */ + child_sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket */ + ASSERT_EQ(-1, connect(child_sockfd, (struct sockaddr *)&self->addr4[1], + sizeof(self->addr4[1]))); + ASSERT_EQ(EACCES, errno); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + + /* Closes listening socket 2 for the parent*/ + ASSERT_EQ(0, close(sockfd_2)); + + ASSERT_EQ(child_2, waitpid(child_2, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} + +TEST_F_FORK(socket_test, connect_with_restrictions_ip6) { + + int new_fd; + int sockfd_1, sockfd_2; + pid_t child_1, child_2; + int status; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->port[1], + }; + + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind operations to the port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and deny bind operations to the port[1] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + /* Creates a server socket 1 */ + sockfd_1 = create_socket(_metadata, true, false); + ASSERT_LE(0, sockfd_1); + + /* Binds the socket 1 to address with port[0] */ + ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr6[0], sizeof(self->addr6[0]))); + + /* Makes listening socket 1 */ + ASSERT_EQ(0, listen(sockfd_1, BACKLOG)); + + child_1 = fork(); + ASSERT_LE(0, child_1); + if (child_1 == 0) { + int child_sockfd; + + /* Closes listening socket for the child */ + ASSERT_EQ(0, close(sockfd_1)); + /* Creates a stream client socket */ + child_sockfd = create_socket(_metadata, true, false); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket */ + ASSERT_EQ(0, connect(child_sockfd, (struct sockaddr *)&self->addr6[0], + sizeof(self->addr6[0]))); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Accepts connection from the child 1 */ + new_fd = accept(sockfd_1, NULL, 0); + ASSERT_LE(0, new_fd); + + /* Closes connection */ + ASSERT_EQ(0, close(new_fd)); + + /* Closes listening socket 1 for the parent*/ + ASSERT_EQ(0, close(sockfd_1)); + + ASSERT_EQ(child_1, waitpid(child_1, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + /* Creates a server socket 2 */ + sockfd_2 = create_socket(_metadata, true, false); + ASSERT_LE(0, sockfd_2); + + /* Binds the socket 2 to address with port[1] */ + ASSERT_EQ(0, bind(sockfd_2, (struct sockaddr *)&self->addr6[1], sizeof(self->addr6[1]))); + + /* Makes listening socket 2 */ + ASSERT_EQ(0, listen(sockfd_2, BACKLOG)); + + child_2 = fork(); + ASSERT_LE(0, child_2); + if (child_2 == 0) { + int child_sockfd; + + /* Closes listening socket for the child */ + ASSERT_EQ(0, close(sockfd_2)); + /* Creates a stream client socket */ + child_sockfd = create_socket(_metadata, true, false); + ASSERT_LE(0, child_sockfd); + + /* Makes connection to the listening socket */ + ASSERT_EQ(-1, connect(child_sockfd, (struct sockaddr *)&self->addr6[1], + sizeof(self->addr6[1]))); + ASSERT_EQ(EACCES, errno); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + + /* Closes listening socket 2 for the parent*/ + ASSERT_EQ(0, close(sockfd_2)); + + ASSERT_EQ(child_2, waitpid(child_2, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} TEST_HARNESS_MAIN From patchwork Mon May 16 15:20:34 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: 12850974 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 501E9C43217 for ; Mon, 16 May 2022 15:21:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245256AbiEPPVc (ORCPT ); Mon, 16 May 2022 11:21:32 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58604 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245272AbiEPPVJ (ORCPT ); Mon, 16 May 2022 11:21:09 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A9F3B10571; Mon, 16 May 2022 08:21:04 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22t94pCJz685ZG; Mon, 16 May 2022 23:17:21 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:21:01 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 11/15] seltests/landlock: connect() with AF_UNSPEC tests Date: Mon, 16 May 2022 23:20:34 +0800 Message-ID: <20220516152038.39594-12-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) 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 conneted socket with connect(..., AF_UNSPEC, ...): - connect_afunspec_no_restictions; The second one refuses landlocked process to disconnect already connected socket: - connect_afunspec_with_restictions; Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Add connect_afunspec_no_restictions test. * Add connect_afunspec_with_restictions test. Changes since v4: * Refactoring code with self->port, self->addr4 variables. * Adds bind() hook check for with AF_UNSPEC family. --- tools/testing/selftests/landlock/net_test.c | 121 ++++++++++++++++++++ 1 file changed, 121 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index cf914d311eb3..bf8e49466d1d 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -449,6 +449,7 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) { int new_fd; int sockfd_1, sockfd_2; pid_t child_1, child_2; + int status; struct landlock_ruleset_attr ruleset_attr = { @@ -467,10 +468,12 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) { const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); /* Allows connect and bind operations to the port[0] socket */ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); /* Allows connect and deny bind operations to the port[1] socket */ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, @@ -480,6 +483,7 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) { enforce_ruleset(_metadata, ruleset_fd); /* Creates a server socket 1 */ + sockfd_1 = create_socket(_metadata, true, false); ASSERT_LE(0, sockfd_1); @@ -556,4 +560,121 @@ TEST_F_FORK(socket_test, connect_with_restrictions_ip6) { ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } + +TEST_F_FORK(socket_test, connect_afunspec_no_restictions) { + + int sockfd; + pid_t child; + int status; + + /* Creates a server socket 1 */ + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + + /* Binds the socket 1 to address with port[0] with AF_UNSPEC family */ + self->addr4[0].sin_family = AF_UNSPEC; + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes connection to socket with port[0] */ + ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC}; + + /* Child tries to disconnect already connected socket */ + ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&addr_unspec, + sizeof(addr_unspec))); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Closes listening socket 1 for the parent*/ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} + +TEST_F_FORK(socket_test, connect_afunspec_with_restictions) { + + int sockfd; + pid_t child; + int status; + + struct landlock_ruleset_attr ruleset_attr_1 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + struct landlock_ruleset_attr ruleset_attr_2 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd_1 = landlock_create_ruleset(&ruleset_attr_1, + sizeof(ruleset_attr_1), 0); + ASSERT_LE(0, ruleset_fd_1); + + /* Allows bind operations to the port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_1); + + /* Creates a server socket 1 */ + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + + /* Binds the socket 1 to address with port[0] with AF_UNSPEC family */ + self->addr4[0].sin_family = AF_UNSPEC; + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes connection to socket with port[0] */ + ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + + const int ruleset_fd_2 = landlock_create_ruleset(&ruleset_attr_2, + sizeof(ruleset_attr_2), 0); + ASSERT_LE(0, ruleset_fd_2); + + /* Allows connect and bind operations to the port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_2); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + struct sockaddr addr_unspec = {.sa_family = AF_UNSPEC}; + + /* Child tries to disconnect already connected socket */ + ASSERT_EQ(-1, connect(sockfd, (struct sockaddr *)&addr_unspec, + sizeof(addr_unspec))); + ASSERT_EQ(EACCES, errno); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + /* Closes listening socket 1 for the parent*/ + ASSERT_EQ(0, close(sockfd)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); +} TEST_HARNESS_MAIN From patchwork Mon May 16 15:20:35 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: 12850980 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 88DE9C433F5 for ; Mon, 16 May 2022 15:21:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245237AbiEPPVj (ORCPT ); Mon, 16 May 2022 11:21:39 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58616 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245275AbiEPPVJ (ORCPT ); Mon, 16 May 2022 11:21:09 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A4CE43BA67; Mon, 16 May 2022 08:21:05 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22tz3lCvz6H7Ht; Mon, 16 May 2022 23:18:03 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:21:03 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 12/15] seltests/landlock: rules overlapping test Date: Mon, 16 May 2022 23:20:35 +0800 Message-ID: <20220516152038.39594-13-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) 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 v3: * Add ruleset_overlap test. Changes since v4: * Refactoring code with self->port, self->addr4 variables. --- tools/testing/selftests/landlock/net_test.c | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index bf8e49466d1d..1d8c9dfdbd48 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -677,4 +677,55 @@ TEST_F_FORK(socket_test, connect_afunspec_with_restictions) { ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } + +TEST_F_FORK(socket_test, ruleset_overlap) { + + int sockfd; + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows bind operations to the port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + /* Allows connect and bind operations to the port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + + /* Creates a server socket */ + sockfd = create_socket(_metadata, false, false); + ASSERT_LE(0, sockfd); + + /* Binds the socket to address with port[0] */ + ASSERT_EQ(0, bind(sockfd, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes connection to socket with port[0] */ + ASSERT_EQ(0, connect(sockfd, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + + /* Closes socket */ + ASSERT_EQ(0, close(sockfd)); +} + TEST_HARNESS_MAIN From patchwork Mon May 16 15:20:36 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: 12850978 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 C047BC4332F for ; Mon, 16 May 2022 15:21:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245193AbiEPPVh (ORCPT ); Mon, 16 May 2022 11:21:37 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58144 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245288AbiEPPVL (ORCPT ); Mon, 16 May 2022 11:21:11 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4318E3B550; Mon, 16 May 2022 08:21:10 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22tH6rCzz67n8d; Mon, 16 May 2022 23:17:27 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:21:08 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 13/15] seltests/landlock: ruleset expanding test Date: Mon, 16 May 2022 23:20:36 +0800 Message-ID: <20220516152038.39594-14-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) 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 v3: * Add ruleset_expanding test. Changes since v4: * Refactoring code with self->port, self->addr4 variables. --- tools/testing/selftests/landlock/net_test.c | 152 ++++++++++++++++++++ 1 file changed, 152 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 1d8c9dfdbd48..b1639a55a898 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -728,4 +728,156 @@ TEST_F_FORK(socket_test, ruleset_overlap) { ASSERT_EQ(0, close(sockfd)); } +TEST_F_FORK(socket_test, ruleset_expanding) { + + int sockfd_1, sockfd_2; + + struct landlock_ruleset_attr ruleset_attr_1 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd_1 = landlock_create_ruleset(&ruleset_attr_1, + sizeof(ruleset_attr_1), 0); + ASSERT_LE(0, ruleset_fd_1); + + /* Adds rule to port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_1, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_1); + ASSERT_EQ(0, close(ruleset_fd_1)); + + /* Creates a socket 1 */ + sockfd_1 = create_socket(_metadata, false, true); + ASSERT_LE(0, sockfd_1); + + /* Binds the socket 1 to address with port[0] */ + ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes connection to socket 1 with port[0] */ + ASSERT_EQ(0, connect(sockfd_1, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + + /* Closes socket 1 */ + ASSERT_EQ(0, close(sockfd_1)); + + /* Creates a socket 2 */ + sockfd_2 = create_socket(_metadata, false, true); + ASSERT_LE(0, sockfd_2); + + /* + * Forbids to bind the socket 2 to address with port[1], + * cause there is no rule with bind() access for port[1]. + */ + ASSERT_EQ(-1, bind(sockfd_2, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[1]))); + ASSERT_EQ(EACCES, errno); + + /* Expands network mask */ + struct landlock_ruleset_attr ruleset_attr_2 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + + /* Adds connect() access to port[0] */ + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + + .port = self->port[0], + }; + /* Adds bind() access to port[1] */ + struct landlock_net_service_attr net_service_3 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[1], + }; + + const int ruleset_fd_2 = landlock_create_ruleset(&ruleset_attr_2, + sizeof(ruleset_attr_2), 0); + ASSERT_LE(0, ruleset_fd_2); + + /* Adds rule to port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + /* Adds rule to port[1] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_2, LANDLOCK_RULE_NET_SERVICE, + &net_service_3, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_2); + ASSERT_EQ(0, close(ruleset_fd_2)); + + /* Creates a socket 1 */ + sockfd_1 = create_socket(_metadata, false, true); + ASSERT_LE(0, sockfd_1); + + /* Binds the socket 1 to address with port[0] */ + ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* Makes connection to socket 1 with port[0] */ + ASSERT_EQ(0, connect(sockfd_1, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + /* Closes socket 1 */ + ASSERT_EQ(0, close(sockfd_1)); + + /* Creates a socket 2 */ + sockfd_2 = create_socket(_metadata, false, true); + ASSERT_LE(0, sockfd_2); + + /* + * Forbids to bind the socket 2 to address with port[1], + * cause just one layer has bind() access rule. + */ + ASSERT_EQ(-1, bind(sockfd_2, (struct sockaddr *)&self->addr4[1], sizeof(self->addr4[1]))); + ASSERT_EQ(EACCES, errno); + + /* Expands network mask */ + struct landlock_ruleset_attr ruleset_attr_3 = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + + /* Restricts connect() access to port[0] */ + struct landlock_net_service_attr net_service_4 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = self->port[0], + }; + + const int ruleset_fd_3 = landlock_create_ruleset(&ruleset_attr_3, + sizeof(ruleset_attr_3), 0); + ASSERT_LE(0, ruleset_fd_3); + + /* Adds rule to port[0] socket */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_3, LANDLOCK_RULE_NET_SERVICE, + &net_service_4, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd_3); + ASSERT_EQ(0, close(ruleset_fd_3)); + + /* Creates a socket 1 */ + sockfd_1 = create_socket(_metadata, false, true); + ASSERT_LE(0, sockfd_1); + + /* Binds the socket 1 to address with port[0] */ + ASSERT_EQ(0, bind(sockfd_1, (struct sockaddr *)&self->addr4[0], sizeof(self->addr4[0]))); + + /* + * Forbids to bind the socket 1 to address with port[0], + * cause just one layer has connect() access rule. + */ + ASSERT_EQ(-1, connect(sockfd_1, (struct sockaddr *)&self->addr4[0], + sizeof(self->addr4[0]))); + ASSERT_EQ(EACCES, errno); + + /* Closes socket 1 */ + ASSERT_EQ(0, close(sockfd_1)); +} TEST_HARNESS_MAIN From patchwork Mon May 16 15:20:37 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: 12850979 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 E1DBAC433EF for ; Mon, 16 May 2022 15:21:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240352AbiEPPVi (ORCPT ); Mon, 16 May 2022 11:21:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58582 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245291AbiEPPVM (ORCPT ); Mon, 16 May 2022 11:21:12 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 96C4310571; Mon, 16 May 2022 08:21:11 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22tK2Cy7z67Zm5; Mon, 16 May 2022 23:17:29 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:21:09 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 14/15] seltests/landlock: invalid user input data test Date: Mon, 16 May 2022 23:20:37 +0800 Message-ID: <20220516152038.39594-15-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch adds rules with invalid user space supplied data: - unhandled allowed access; - zero port value; - zero access value; Signed-off-by: Konstantin Meskhidze --- Changes since v3: * Add inval test. Changes since v4: * Refactoring code with self->port variable. --- tools/testing/selftests/landlock/net_test.c | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) -- 2.25.1 diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index b1639a55a898..ca4b7a7256e8 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -880,4 +880,56 @@ TEST_F_FORK(socket_test, ruleset_expanding) { /* Closes socket 1 */ ASSERT_EQ(0, close(sockfd_1)); } + +TEST_F_FORK(socket_test, inval) { + + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP + }; + struct landlock_net_service_attr net_service_1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->port[0], + }; + struct landlock_net_service_attr net_service_2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = 0, + }; + struct landlock_net_service_attr net_service_3 = { + .allowed_access = 0, + .port = self->port[1], + }; + struct landlock_net_service_attr net_service_4 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->port[2], + }; + + /* Gets ruleset. */ + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Checks unhandled allowed_access. */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_1, 0)); + ASSERT_EQ(EINVAL, errno); + + /* Checks zero port value. */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_2, 0)); + ASSERT_EQ(EINVAL, errno); + + /* Checks zero access value. */ + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_3, 0)); + ASSERT_EQ(ENOMSG, errno); + + /* Adds with legitimate values. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service_4, 0)); + + /* Enforces the ruleset. */ + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); +} TEST_HARNESS_MAIN From patchwork Mon May 16 15:20:38 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: 12850981 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 03726C4332F for ; Mon, 16 May 2022 15:21:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245285AbiEPPVk (ORCPT ); Mon, 16 May 2022 11:21:40 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58848 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245296AbiEPPVN (ORCPT ); Mon, 16 May 2022 11:21:13 -0400 Received: from frasgout.his.huawei.com (frasgout.his.huawei.com [185.176.79.56]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id AC0D033A00; Mon, 16 May 2022 08:21:12 -0700 (PDT) Received: from fraeml704-chm.china.huawei.com (unknown [172.18.147.201]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4L22yW54Szz6H6tS; Mon, 16 May 2022 23:21:07 +0800 (CST) Received: from mscphispre00059.huawei.com (10.123.71.64) 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.24; Mon, 16 May 2022 17:21:10 +0200 From: Konstantin Meskhidze To: CC: , , , , , Subject: [PATCH v5 15/15] samples/landlock: adds network demo Date: Mon, 16 May 2022 23:20:38 +0800 Message-ID: <20220516152038.39594-16-konstantin.meskhidze@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> References: <20220516152038.39594-1-konstantin.meskhidze@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.123.71.64] X-ClientProxiedBy: mscpeml500001.china.huawei.com (7.188.26.142) To fraeml704-chm.china.huawei.com (10.206.15.53) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This commit adds network demo. It's possible to allow a sandoxer to bind/connect to a list of particular ports restricting networks actions to the rest of ports. Signed-off-by: Konstantin Meskhidze --- Changes since v4: * Adds ENV_TCP_BIND_NAME "LL_TCP_BIND" and ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" variables to insert TCP ports. * Renames populate_ruleset() to populate_ruleset_fs(). * Adds populate_ruleset_net() and parse_port_num() helpers. * Refactoring main() to support network sandboxing. --- samples/landlock/sandboxer.c | 105 +++++++++++++++++++++++++++++++---- security/landlock/ruleset.h | 4 +- 2 files changed, 95 insertions(+), 14 deletions(-) -- 2.25.1 diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 3e404e51ec64..4006c42eec1c 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -51,6 +51,8 @@ static inline int landlock_restrict_self(const int ruleset_fd, #define ENV_FS_RO_NAME "LL_FS_RO" #define ENV_FS_RW_NAME "LL_FS_RW" +#define ENV_TCP_BIND_NAME "LL_TCP_BIND" +#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" #define ENV_PATH_TOKEN ":" static int parse_path(char *env_path, const char ***const path_list) @@ -71,6 +73,20 @@ static int parse_path(char *env_path, const char ***const path_list) return num_paths; } +static int parse_port_num(char *env_port) +{ + int i, num_ports = 0; + + if (env_port) { + num_ports++; + for (i = 0; env_port[i]; i++) { + if (env_port[i] == ENV_PATH_TOKEN[0]) + num_ports++; + } + } + return num_ports; +} + /* clang-format off */ #define ACCESS_FILE ( \ @@ -80,7 +96,7 @@ static int parse_path(char *env_path, const char ***const path_list) /* clang-format on */ -static int populate_ruleset(const char *const env_var, const int ruleset_fd, +static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd, const __u64 allowed_access) { int num_paths, i, ret = 1; @@ -142,6 +158,49 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd, return ret; } +static int populate_ruleset_net(const char *const env_var, + const int ruleset_fd, + const __u64 allowed_access) +{ + int num_ports, i, ret = 1; + char *env_port_name; + struct landlock_net_service_attr net_service = { + .allowed_access = 0, + .port = 0, + }; + + env_port_name = getenv(env_var); + if (!env_port_name) { + /* Prevents users to forget a setting. */ + fprintf(stderr, "Missing environment variable %s\n", env_var); + return 1; + } + env_port_name = strdup(env_port_name); + unsetenv(env_var); + num_ports = parse_port_num(env_port_name); + + if (num_ports == 1 && (strtok(env_port_name, ENV_PATH_TOKEN) == NULL)) { + ret = 0; + goto out_free_name; + } + + for (i = 0; i < num_ports; i++) { + net_service.allowed_access = allowed_access; + net_service.port = atoi(strsep(&env_port_name, ENV_PATH_TOKEN)); + if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &net_service, 0)) { + fprintf(stderr, "Failed to update the ruleset with port \"%d\": %s\n", + net_service.port, strerror(errno)); + goto out_free_name; + } + } + ret = 0; + +out_free_name: + free(env_port_name); + return ret; +} + /* clang-format off */ #define ACCESS_FS_ROUGHLY_READ ( \ @@ -173,19 +232,24 @@ int main(const int argc, char *const argv[], char *const *const envp) char *const *cmd_argv; int ruleset_fd, abi; __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, - access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; + access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE, + access_net_tcp = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = access_fs_rw, + .handled_access_net = access_net_tcp, }; if (argc < 2) { fprintf(stderr, - "usage: %s=\"...\" %s=\"...\" %s [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); @@ -193,11 +257,19 @@ int main(const int argc, char *const argv[], char *const *const envp) "* %s: list of paths allowed to be used in a read-write way.\n", ENV_FS_RW_NAME); fprintf(stderr, - "\nexample:\n" + "* %s: list of ports allowed to bind (server).\n", + ENV_TCP_BIND_NAME); + fprintf(stderr, + "* %s: list of ports allowed to connect (client).\n", + ENV_TCP_CONNECT_NAME); + fprintf(stderr, "\nexample:\n" "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " + "%s=\"15000:16000\" " + "%s=\"10000:12000\" " "%s bash -i\n", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, + ENV_TCP_CONNECT_NAME, argv[0]); return 1; } @@ -234,16 +306,25 @@ int main(const int argc, char *const argv[], char *const *const envp) ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + if (ruleset_fd < 0) { perror("Failed to create a ruleset"); return 1; } - if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) { + if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) goto err_close_ruleset; - } - if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) { + + if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) goto err_close_ruleset; - } + + if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd, + LANDLOCK_ACCESS_NET_BIND_TCP)) + goto err_close_ruleset; + + if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd, + LANDLOCK_ACCESS_NET_CONNECT_TCP)) + goto err_close_ruleset; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("Failed to restrict privileges"); goto err_close_ruleset; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 916b30b31c06..e1ff40f238a6 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -19,7 +19,7 @@ #include "limits.h" #include "object.h" -typedef u16 access_mask_t; +typedef u32 access_mask_t; /* Makes sure all filesystem access rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); @@ -157,7 +157,7 @@ struct landlock_ruleset { * layers are set once and never changed for the * lifetime of the ruleset. */ - u32 access_masks[]; + access_mask_t access_masks[]; }; }; };