diff mbox series

[RFC,v4,03/15] landlock: landlock_find/insert_rule refactoring

Message ID 20220309134459.6448-4-konstantin.meskhidze@huawei.com (mailing list archive)
State New, archived
Headers show
Series Landlock LSM | expand

Commit Message

Konstantin Meskhidze (A) March 9, 2022, 1:44 p.m. UTC
A new object union added to support a socket port
rule type. To support it landlock_insert_rule() and
landlock_find_rule() were refactored. Now adding
or searching a rule in a ruleset depends on a
rule_type argument provided in refactored
functions mentioned above.

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

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

---
 security/landlock/fs.c      |   5 +-
 security/landlock/ruleset.c | 108 +++++++++++++++++++++++++-----------
 security/landlock/ruleset.h |  26 +++++----
 3 files changed, 94 insertions(+), 45 deletions(-)

--
2.25.1

Comments

Mickaël Salaün March 16, 2022, 8:27 a.m. UTC | #1
On 09/03/2022 14:44, Konstantin Meskhidze wrote:
> A new object union added to support a socket port
> rule type. To support it landlock_insert_rule() and
> landlock_find_rule() were refactored. Now adding
> or searching a rule in a ruleset depends on a
> rule_type argument provided in refactored
> functions mentioned above.
> 
> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
> ---
> 
> Changes since v3:
> * Split commit.
> * Refactoring landlock_insert_rule and landlock_find_rule functions.
> * Rename new_ruleset->root_inode.
> 
> ---
>   security/landlock/fs.c      |   5 +-
>   security/landlock/ruleset.c | 108 +++++++++++++++++++++++++-----------
>   security/landlock/ruleset.h |  26 +++++----
>   3 files changed, 94 insertions(+), 45 deletions(-)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 97f5c455f5a7..1497948d754f 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -168,7 +168,7 @@ 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);

For consistency, please use 80 columns everywhere.

>   	mutex_unlock(&ruleset->lock);
>   	/*
>   	 * No need to check for an error because landlock_insert_rule()
> @@ -195,7 +195,8 @@ static inline u64 unmask_layers(
>   	inode = d_backing_inode(path->dentry);
>   	rcu_read_lock();
>   	rule = landlock_find_rule(domain,
> -			rcu_dereference(landlock_inode(inode)->object));
> +			(uintptr_t)rcu_dereference(landlock_inode(inode)->object),
> +			LANDLOCK_RULE_PATH_BENEATH);
>   	rcu_read_unlock();
>   	if (!rule)
>   		return layer_mask;
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index a6212b752549..971685c48641 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -34,7 +34,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
> @@ -81,10 +81,12 @@ static void build_check_rule(void)
>   }
> 
>   static struct landlock_rule *create_rule(
> -		struct landlock_object *const object,
> +		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)
> +		const struct landlock_layer *const new_layer,
> +		const u16 rule_type)
>   {
>   	struct landlock_rule *new_rule;
>   	u32 new_num_layers;
> @@ -103,8 +105,16 @@ static struct landlock_rule *create_rule(
>   	if (!new_rule)
>   		return ERR_PTR(-ENOMEM);
>   	RB_CLEAR_NODE(&new_rule->node);
> -	landlock_get_object(object);
> -	new_rule->object = object;
> +
> +	switch (rule_type) {
> +	case LANDLOCK_RULE_PATH_BENEATH:
> +		landlock_get_object(object_ptr);
> +		new_rule->object.ptr = object_ptr;
> +		break;
> +	default:
> +		return ERR_PTR(-EINVAL);

This would lead to memory leak. You should at least add a 
WARN_ON_ONCE(1) here, but a proper solution would be to remove the use 
of rule_type and only rely on object_ptr and object_data values. You can 
also add a WARN_ON_ONCE(object_ptr && object_data).


> +	}
> +
>   	new_rule->num_layers = new_num_layers;
>   	/* Copies the original layer stack. */
>   	memcpy(new_rule->layers, layers,
> @@ -120,7 +130,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);
>   }
> 
> @@ -156,26 +166,38 @@ static void build_check_ruleset(void)
>    * access rights.
>    */
>   static int insert_rule(struct landlock_ruleset *const ruleset,
> -		struct landlock_object *const object,
> +		struct landlock_object *const object_ptr,
> +		const uintptr_t object_data,
>   		const struct landlock_layer (*const layers)[],
> -		size_t num_layers)
> +		size_t num_layers, u16 rule_type)
>   {
>   	struct rb_node **walker_node;
>   	struct rb_node *parent_node = NULL;
>   	struct landlock_rule *new_rule;
> +	uintptr_t object;
> +	struct rb_root *root;
> 
>   	might_sleep();
>   	lockdep_assert_held(&ruleset->lock);
> -	if (WARN_ON_ONCE(!object || !layers))
> -		return -ENOENT;

You can leave this code here.

> -	walker_node = &(ruleset->root.rb_node);
> +	/* Choose rb_tree structure depending on a rule type */
> +	switch (rule_type) {
> +	case LANDLOCK_RULE_PATH_BENEATH:
> +		if (WARN_ON_ONCE(!object_ptr || !layers))
> +			return -ENOENT;
> +		object = (uintptr_t)object_ptr;
> +		root = &ruleset->root_inode;
> +		break;
> +	default:
> +		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) {
>   			parent_node = *walker_node;
> -			if (this->object < object)
> +			if (this->object.data < object)
>   				walker_node = &((*walker_node)->rb_right);
>   			else
>   				walker_node = &((*walker_node)->rb_left);
> @@ -207,11 +229,15 @@ 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:

Same here and for the following code, you should replace such 
switch/case with an if (object_ptr).


> +			new_rule = create_rule(object_ptr, 0, &this->layers, this->num_layers,
> +					       &(*layers)[0], rule_type);
> +			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);

Use the root variable here. Same for the following code and patches.


>   		free_rule(this);
>   		return 0;
>   	}
> @@ -220,11 +246,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, rule_type);
> +		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;
>   }
> @@ -242,7 +272,9 @@ 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 u32 access)
> +		struct landlock_object *const object_ptr,
> +		const uintptr_t object_data,
> +		const u32 access, const u16 rule_type)
>   {
>   	struct landlock_layer layers[] = {{
>   		.access = access,
> @@ -251,7 +283,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, &layers,
> +			   ARRAY_SIZE(layers), rule_type);
>   }
> 
>   static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
> @@ -297,7 +330,7 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
> 
>   	/* Merges the @src tree. */
>   	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
> -			&src->root, node) {
> +			&src->root_inode, node) {
>   		struct landlock_layer layers[] = {{
>   			.level = dst->num_layers,
>   		}};
> @@ -311,8 +344,8 @@ 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,
> -				ARRAY_SIZE(layers));
> +		err = insert_rule(dst, walker_rule->object.ptr, 0, &layers,
> +				ARRAY_SIZE(layers), LANDLOCK_RULE_PATH_BENEATH);
>   		if (err)
>   			goto out_unlock;
>   	}
> @@ -323,6 +356,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
>   	return err;
>   }
> 
> +
> +

Useless lines.


>   static int inherit_ruleset(struct landlock_ruleset *const parent,
>   		struct landlock_ruleset *const child)
>   {
> @@ -339,9 +374,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,
> +				&walker_rule->layers, walker_rule->num_layers,
> +				LANDLOCK_RULE_PATH_BENEATH);
>   		if (err)
>   			goto out_unlock;
>   	}
> @@ -372,7 +408,7 @@ 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,
> +	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
>   			node)
>   		free_rule(freeme);
>   	put_hierarchy(ruleset->hierarchy);
> @@ -465,20 +501,28 @@ struct landlock_ruleset *landlock_merge_ruleset(
>    */
>   const struct landlock_rule *landlock_find_rule(
>   		const struct landlock_ruleset *const ruleset,
> -		const struct landlock_object *const object)
> +		const uintptr_t object_data, const u16 rule_type)
>   {
>   	const struct rb_node *node;
> 
> -	if (!object)
> +	if (!object_data)

object_data can be 0. You need to add a test with such value.

We need to be sure that this change cannot affect the current FS code.


>   		return NULL;
> -	node = ruleset->root.rb_node;
> +
> +	switch (rule_type) {
> +	case LANDLOCK_RULE_PATH_BENEATH:
> +		node = ruleset->root_inode.rb_node;
> +		break;
> +	default:
> +		return ERR_PTR(-EINVAL);

This is a bug. There is no check for such value. You need to check and 
update all call sites to catch such errors. Same for all new use of 
ERR_PTR().


> +	}
> +
>   	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 bc87e5f787f7..088b8d95f653 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -50,15 +50,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 socket port object.

…or a raw data value (e.g. a network socket port).


> This is used as a key for this ruleset element.
> +	 * This pointer is set once and never modified. It always points to an

s/This pointer/@object.ptr/


> +	 * 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
> @@ -95,7 +97,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.
> @@ -157,7 +159,9 @@ 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 u32 access);
> +			 struct landlock_object *const object_ptr,
> +			 const uintptr_t object_data,
> +			 const u32 access, const u16 rule_type);
> 
>   struct landlock_ruleset *landlock_merge_ruleset(
>   		struct landlock_ruleset *const parent,
> @@ -165,7 +169,7 @@ struct landlock_ruleset *landlock_merge_ruleset(
> 
>   const struct landlock_rule *landlock_find_rule(
>   		const struct landlock_ruleset *const ruleset,
> -		const struct landlock_object *const object);
> +		const uintptr_t object_data, const u16 rule_type);
> 
>   static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
>   {
> --
> 2.25.1
>
Konstantin Meskhidze (A) March 17, 2022, 2:29 p.m. UTC | #2
3/16/2022 11:27 AM, Mickaël Salaün пишет:
> 
> On 09/03/2022 14:44, Konstantin Meskhidze wrote:
>> A new object union added to support a socket port
>> rule type. To support it landlock_insert_rule() and
>> landlock_find_rule() were refactored. Now adding
>> or searching a rule in a ruleset depends on a
>> rule_type argument provided in refactored
>> functions mentioned above.
>>
>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>> ---
>>
>> Changes since v3:
>> * Split commit.
>> * Refactoring landlock_insert_rule and landlock_find_rule functions.
>> * Rename new_ruleset->root_inode.
>>
>> ---
>>   security/landlock/fs.c      |   5 +-
>>   security/landlock/ruleset.c | 108 +++++++++++++++++++++++++-----------
>>   security/landlock/ruleset.h |  26 +++++----
>>   3 files changed, 94 insertions(+), 45 deletions(-)
>>
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 97f5c455f5a7..1497948d754f 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -168,7 +168,7 @@ 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);
> 
> For consistency, please use 80 columns everywhere.

   Ok. I got it.
> 
>>       mutex_unlock(&ruleset->lock);
>>       /*
>>        * No need to check for an error because landlock_insert_rule()
>> @@ -195,7 +195,8 @@ static inline u64 unmask_layers(
>>       inode = d_backing_inode(path->dentry);
>>       rcu_read_lock();
>>       rule = landlock_find_rule(domain,
>> -            rcu_dereference(landlock_inode(inode)->object));
>> +            (uintptr_t)rcu_dereference(landlock_inode(inode)->object),
>> +            LANDLOCK_RULE_PATH_BENEATH);
>>       rcu_read_unlock();
>>       if (!rule)
>>           return layer_mask;
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index a6212b752549..971685c48641 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -34,7 +34,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
>> @@ -81,10 +81,12 @@ static void build_check_rule(void)
>>   }
>>
>>   static struct landlock_rule *create_rule(
>> -        struct landlock_object *const object,
>> +        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)
>> +        const struct landlock_layer *const new_layer,
>> +        const u16 rule_type)
>>   {
>>       struct landlock_rule *new_rule;
>>       u32 new_num_layers;
>> @@ -103,8 +105,16 @@ static struct landlock_rule *create_rule(
>>       if (!new_rule)
>>           return ERR_PTR(-ENOMEM);
>>       RB_CLEAR_NODE(&new_rule->node);
>> -    landlock_get_object(object);
>> -    new_rule->object = object;
>> +
>> +    switch (rule_type) {
>> +    case LANDLOCK_RULE_PATH_BENEATH:
>> +        landlock_get_object(object_ptr);
>> +        new_rule->object.ptr = object_ptr;
>> +        break;
>> +    default:
>> +        return ERR_PTR(-EINVAL);
> 
> This would lead to memory leak. You should at least add a 
> WARN_ON_ONCE(1) here, but a proper solution would be to remove the use 
> of rule_type and only rely on object_ptr and object_data values. You can 
> also add a WARN_ON_ONCE(object_ptr && object_data).
> 
>  
   But rule_type is needed here in coming commits to support network
   rules. For LANDLOCK_RULE_PATH_BENEATH rule type landlock_get_object() 
is used but for LANDLOCK_RULE_NET_SERVICE is not. Using rule type is 
convenient for distinguising between fs and network rules.
>> +    }
>> +
>>       new_rule->num_layers = new_num_layers;
>>       /* Copies the original layer stack. */
>>       memcpy(new_rule->layers, layers,
>> @@ -120,7 +130,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);
>>   }
>>
>> @@ -156,26 +166,38 @@ static void build_check_ruleset(void)
>>    * access rights.
>>    */
>>   static int insert_rule(struct landlock_ruleset *const ruleset,
>> -        struct landlock_object *const object,
>> +        struct landlock_object *const object_ptr,
>> +        const uintptr_t object_data,
>>           const struct landlock_layer (*const layers)[],
>> -        size_t num_layers)
>> +        size_t num_layers, u16 rule_type)
>>   {
>>       struct rb_node **walker_node;
>>       struct rb_node *parent_node = NULL;
>>       struct landlock_rule *new_rule;
>> +    uintptr_t object;
>> +    struct rb_root *root;
>>
>>       might_sleep();
>>       lockdep_assert_held(&ruleset->lock);
>> -    if (WARN_ON_ONCE(!object || !layers))
>> -        return -ENOENT;
> 
> You can leave this code here.

  But anyway in coming commits with network rules this code will be 
moved into case LANDLOCK_RULE_PATH_BENEATH: ....
> 
>> -    walker_node = &(ruleset->root.rb_node);
>> +    /* Choose rb_tree structure depending on a rule type */
>> +    switch (rule_type) {
>> +    case LANDLOCK_RULE_PATH_BENEATH:
>> +        if (WARN_ON_ONCE(!object_ptr || !layers))
>> +            return -ENOENT;
>> +        object = (uintptr_t)object_ptr;
>> +        root = &ruleset->root_inode;
>> +        break;
>> +    default:
>> +        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) {
>>               parent_node = *walker_node;
>> -            if (this->object < object)
>> +            if (this->object.data < object)
>>                   walker_node = &((*walker_node)->rb_right);
>>               else
>>                   walker_node = &((*walker_node)->rb_left);
>> @@ -207,11 +229,15 @@ 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:
> 
> Same here and for the following code, you should replace such 
> switch/case with an if (object_ptr).
>    What about coming commits with network rule_type support?
> 
>> +            new_rule = create_rule(object_ptr, 0, &this->layers, 
>> this->num_layers,
>> +                           &(*layers)[0], rule_type);
>> +            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);
> 
> Use the root variable here. Same for the following code and patches.

  What about your suggestion to use 2 rb_tress to support different 
rule_types:
	 1. root_inode - for filesystem objects
          2. root_net_port - for network port objects
????

> 
> 
>>           free_rule(this);
>>           return 0;
>>       }
>> @@ -220,11 +246,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, rule_type);
>> +        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;
>>   }
>> @@ -242,7 +272,9 @@ 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 u32 access)
>> +        struct landlock_object *const object_ptr,
>> +        const uintptr_t object_data,
>> +        const u32 access, const u16 rule_type)
>>   {
>>       struct landlock_layer layers[] = {{
>>           .access = access,
>> @@ -251,7 +283,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, &layers,
>> +               ARRAY_SIZE(layers), rule_type);
>>   }
>>
>>   static inline void get_hierarchy(struct landlock_hierarchy *const 
>> hierarchy)
>> @@ -297,7 +330,7 @@ static int merge_ruleset(struct landlock_ruleset 
>> *const dst,
>>
>>       /* Merges the @src tree. */
>>       rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
>> -            &src->root, node) {
>> +            &src->root_inode, node) {
>>           struct landlock_layer layers[] = {{
>>               .level = dst->num_layers,
>>           }};
>> @@ -311,8 +344,8 @@ 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,
>> -                ARRAY_SIZE(layers));
>> +        err = insert_rule(dst, walker_rule->object.ptr, 0, &layers,
>> +                ARRAY_SIZE(layers), LANDLOCK_RULE_PATH_BENEATH);
>>           if (err)
>>               goto out_unlock;
>>       }
>> @@ -323,6 +356,8 @@ static int merge_ruleset(struct landlock_ruleset 
>> *const dst,
>>       return err;
>>   }
>>
>> +
>> +
> 
> Useless lines.

   Got it. Thanks.
> 
> 
>>   static int inherit_ruleset(struct landlock_ruleset *const parent,
>>           struct landlock_ruleset *const child)
>>   {
>> @@ -339,9 +374,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,
>> +                &walker_rule->layers, walker_rule->num_layers,
>> +                LANDLOCK_RULE_PATH_BENEATH);
>>           if (err)
>>               goto out_unlock;
>>       }
>> @@ -372,7 +408,7 @@ 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,
>> +    rbtree_postorder_for_each_entry_safe(freeme, next, 
>> &ruleset->root_inode,
>>               node)
>>           free_rule(freeme);
>>       put_hierarchy(ruleset->hierarchy);
>> @@ -465,20 +501,28 @@ struct landlock_ruleset *landlock_merge_ruleset(
>>    */
>>   const struct landlock_rule *landlock_find_rule(
>>           const struct landlock_ruleset *const ruleset,
>> -        const struct landlock_object *const object)
>> +        const uintptr_t object_data, const u16 rule_type)
>>   {
>>       const struct rb_node *node;
>>
>> -    if (!object)
>> +    if (!object_data)
> 
> object_data can be 0. You need to add a test with such value.
> 
> We need to be sure that this change cannot affect the current FS code.

  I got it. I will refactor it.
> 
> 
>>           return NULL;
>> -    node = ruleset->root.rb_node;
>> +
>> +    switch (rule_type) {
>> +    case LANDLOCK_RULE_PATH_BENEATH:
>> +        node = ruleset->root_inode.rb_node;
>> +        break;
>> +    default:
>> +        return ERR_PTR(-EINVAL);
> 
> This is a bug. There is no check for such value. You need to check and 
> update all call sites to catch such errors. Same for all new use of 
> ERR_PTR().

Sorry, I did not get your point.
Do you mean I should check the correctness of rule_type in above 
function which calls landlock_find_rule() ??? Why can't I add such check 
here?

> 
> 
>> +    }
>> +
>>       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 bc87e5f787f7..088b8d95f653 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -50,15 +50,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 socket port object.
> 
> …or a raw data value (e.g. a network socket port).
> 
  Ok. I will mofdify this line
> 
>> This is used as a key for this ruleset element.
>> +     * This pointer is set once and never modified. It always points 
>> to an
> 
> s/This pointer/@object.ptr/

  Ok. I got it.
> 
> 
>> +     * 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
>> @@ -95,7 +97,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.
>> @@ -157,7 +159,9 @@ 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 u32 access);
>> +             struct landlock_object *const object_ptr,
>> +             const uintptr_t object_data,
>> +             const u32 access, const u16 rule_type);
>>
>>   struct landlock_ruleset *landlock_merge_ruleset(
>>           struct landlock_ruleset *const parent,
>> @@ -165,7 +169,7 @@ struct landlock_ruleset *landlock_merge_ruleset(
>>
>>   const struct landlock_rule *landlock_find_rule(
>>           const struct landlock_ruleset *const ruleset,
>> -        const struct landlock_object *const object);
>> +        const uintptr_t object_data, const u16 rule_type);
>>
>>   static inline void landlock_get_ruleset(struct landlock_ruleset 
>> *const ruleset)
>>   {
>> -- 
>> 2.25.1
>>
> .
Mickaël Salaün March 18, 2022, 6:33 p.m. UTC | #3
On 17/03/2022 15:29, Konstantin Meskhidze wrote:
> 
> 
> 3/16/2022 11:27 AM, Mickaël Salaün пишет:
>>
>> On 09/03/2022 14:44, Konstantin Meskhidze wrote:
>>> A new object union added to support a socket port
>>> rule type. To support it landlock_insert_rule() and
>>> landlock_find_rule() were refactored. Now adding
>>> or searching a rule in a ruleset depends on a
>>> rule_type argument provided in refactored
>>> functions mentioned above.
>>>
>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>> ---
>>>
>>> Changes since v3:
>>> * Split commit.
>>> * Refactoring landlock_insert_rule and landlock_find_rule functions.
>>> * Rename new_ruleset->root_inode.
>>>
>>> ---
>>>   security/landlock/fs.c      |   5 +-
>>>   security/landlock/ruleset.c | 108 +++++++++++++++++++++++++-----------
>>>   security/landlock/ruleset.h |  26 +++++----
>>>   3 files changed, 94 insertions(+), 45 deletions(-)
>>>
>>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>>> index 97f5c455f5a7..1497948d754f 100644
>>> --- a/security/landlock/fs.c
>>> +++ b/security/landlock/fs.c
>>> @@ -168,7 +168,7 @@ 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);
>>
>> For consistency, please use 80 columns everywhere.
> 
>    Ok. I got it.
>>
>>>       mutex_unlock(&ruleset->lock);
>>>       /*
>>>        * No need to check for an error because landlock_insert_rule()
>>> @@ -195,7 +195,8 @@ static inline u64 unmask_layers(
>>>       inode = d_backing_inode(path->dentry);
>>>       rcu_read_lock();
>>>       rule = landlock_find_rule(domain,
>>> -            rcu_dereference(landlock_inode(inode)->object));
>>> +            (uintptr_t)rcu_dereference(landlock_inode(inode)->object),
>>> +            LANDLOCK_RULE_PATH_BENEATH);
>>>       rcu_read_unlock();
>>>       if (!rule)
>>>           return layer_mask;
>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>> index a6212b752549..971685c48641 100644
>>> --- a/security/landlock/ruleset.c
>>> +++ b/security/landlock/ruleset.c
>>> @@ -34,7 +34,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
>>> @@ -81,10 +81,12 @@ static void build_check_rule(void)
>>>   }
>>>
>>>   static struct landlock_rule *create_rule(
>>> -        struct landlock_object *const object,
>>> +        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)
>>> +        const struct landlock_layer *const new_layer,
>>> +        const u16 rule_type)
>>>   {
>>>       struct landlock_rule *new_rule;
>>>       u32 new_num_layers;
>>> @@ -103,8 +105,16 @@ static struct landlock_rule *create_rule(
>>>       if (!new_rule)
>>>           return ERR_PTR(-ENOMEM);
>>>       RB_CLEAR_NODE(&new_rule->node);
>>> -    landlock_get_object(object);
>>> -    new_rule->object = object;
>>> +
>>> +    switch (rule_type) {
>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>> +        landlock_get_object(object_ptr);
>>> +        new_rule->object.ptr = object_ptr;
>>> +        break;
>>> +    default:
>>> +        return ERR_PTR(-EINVAL);
>>
>> This would lead to memory leak. You should at least add a 
>> WARN_ON_ONCE(1) here, but a proper solution would be to remove the use 
>> of rule_type and only rely on object_ptr and object_data values. You 
>> can also add a WARN_ON_ONCE(object_ptr && object_data).
>>
>>
>    But rule_type is needed here in coming commits to support network
>    rules. For LANDLOCK_RULE_PATH_BENEATH rule type landlock_get_object() 
> is used but for LANDLOCK_RULE_NET_SERVICE is not. Using rule type is 
> convenient for distinguising between fs and network rules.

rule_type is not required to infer if the rule use a pointer or raw 
data, even with the following commits, because you can rely on 
object_ptr being NULL or not. This would make create_rule() generic for 
pointer-based and data-based object, even if not-yet-existing rule 
types. It is less error-prone to only be able to infer something from 
one source (i.e. object_ptr and not rule_type).


>>> +    }
>>> +
>>>       new_rule->num_layers = new_num_layers;
>>>       /* Copies the original layer stack. */
>>>       memcpy(new_rule->layers, layers,
>>> @@ -120,7 +130,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);
>>>   }
>>>
>>> @@ -156,26 +166,38 @@ static void build_check_ruleset(void)
>>>    * access rights.
>>>    */
>>>   static int insert_rule(struct landlock_ruleset *const ruleset,
>>> -        struct landlock_object *const object,
>>> +        struct landlock_object *const object_ptr,
>>> +        const uintptr_t object_data,

Can you move rule_type here for this function and similar ones? It makes 
sense to group object-related arguments.


>>>           const struct landlock_layer (*const layers)[],
>>> -        size_t num_layers)
>>> +        size_t num_layers, u16 rule_type)
>>>   {
>>>       struct rb_node **walker_node;
>>>       struct rb_node *parent_node = NULL;
>>>       struct landlock_rule *new_rule;
>>> +    uintptr_t object;
>>> +    struct rb_root *root;
>>>
>>>       might_sleep();
>>>       lockdep_assert_held(&ruleset->lock);
>>> -    if (WARN_ON_ONCE(!object || !layers))
>>> -        return -ENOENT;
>>
>> You can leave this code here.
> 
>   But anyway in coming commits with network rules this code will be 
> moved into case LANDLOCK_RULE_PATH_BENEATH: ....

Yes, but without rule_type you don't need to duplicate this check, just 
to remove object_ptr from WARN_ON_ONCE() and replace the rule_type 
switch/case with if (object_ptr).

You can change to this:

--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -194,43 +194,49 @@ static void build_check_ruleset(void)
   */
  static int insert_rule(struct landlock_ruleset *const ruleset,
  		struct landlock_object *const object_ptr,
-		const uintptr_t object_data,
+		uintptr_t object_data, /* move @rule_type here */
  		const struct landlock_layer (*const layers)[],
-		size_t num_layers, u16 rule_type)
+		size_t num_layers, const enum landlock_rule_type rule_type)
  {
  	struct rb_node **walker_node;
  	struct rb_node *parent_node = NULL;
  	struct landlock_rule *new_rule;
-	uintptr_t object;
  	struct rb_root *root;

  	might_sleep();
  	lockdep_assert_held(&ruleset->lock);
-	/* Choose rb_tree structure depending on a rule type */
+
+	if (WARN_ON_ONCE(!layers))
+		return -ENOENT;
+	if (WARN_ON_ONCE(object_ptr && object_data))
+		return -EINVAL;
+
+	/* Chooses the rb_tree according to the rule type. */
  	switch (rule_type) {
  	case LANDLOCK_RULE_PATH_BENEATH:
-		if (WARN_ON_ONCE(!object_ptr || !layers))
+		if (WARN_ON_ONCE(!object_ptr))
  			return -ENOENT;
-		object = (uintptr_t)object_ptr;
+		object_data = (uintptr_t)object_ptr;
  		root = &ruleset->root_inode;
  		break;
  	case LANDLOCK_RULE_NET_SERVICE:
-		if (WARN_ON_ONCE(!object_data || !layers))
-			return -ENOENT;
-		object = object_data;
+		if (WARN_ON_ONCE(object_ptr))
+			return -EINVAL;
  		root = &ruleset->root_net_port;
  		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.data != object) {
+		if (this->object.data != object_data) {
  			parent_node = *walker_node;
-			if (this->object.data < object)
+			if (this->object.data < object_data)
  				walker_node = &((*walker_node)->rb_right);
  			else
  				walker_node = &((*walker_node)->rb_left);


This highlight an implicit error handling for a port value of 0. I'm not 
sure if this should be allowed or not though. If not, it should be an 
explicit service_port check in add_rule_net_service(). A data value of 
zero might be legitimate for this use case or not-yet-existing 
data-based rule types. Anyway, this kind of check is specific to the use 
case and should not be part of insert_rule().



>>
>>> -    walker_node = &(ruleset->root.rb_node);
>>> +    /* Choose rb_tree structure depending on a rule type */
>>> +    switch (rule_type) {
>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>> +        if (WARN_ON_ONCE(!object_ptr || !layers))
>>> +            return -ENOENT;
>>> +        object = (uintptr_t)object_ptr;
>>> +        root = &ruleset->root_inode;
>>> +        break;
>>> +    default:
>>> +        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) {
>>>               parent_node = *walker_node;
>>> -            if (this->object < object)
>>> +            if (this->object.data < object)
>>>                   walker_node = &((*walker_node)->rb_right);
>>>               else
>>>                   walker_node = &((*walker_node)->rb_left);
>>> @@ -207,11 +229,15 @@ 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:
>>
>> Same here and for the following code, you should replace such 
>> switch/case with an if (object_ptr).
>>    What about coming commits with network rule_type support?

This will still works.


>>
>>> +            new_rule = create_rule(object_ptr, 0, &this->layers, 
>>> this->num_layers,
>>> +                           &(*layers)[0], rule_type);
>>> +            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);
>>
>> Use the root variable here. Same for the following code and patches.
> 
>   What about your suggestion to use 2 rb_tress to support different 
> rule_types:
>       1. root_inode - for filesystem objects
>           2. root_net_port - for network port objects
> ????

I was talking about the root variable you declared a few line before. 
The conversion from ruleset->root to ruleset->root_inode is fine.


[...]

>>> @@ -465,20 +501,28 @@ struct landlock_ruleset *landlock_merge_ruleset(
>>>    */
>>>   const struct landlock_rule *landlock_find_rule(
>>>           const struct landlock_ruleset *const ruleset,
>>> -        const struct landlock_object *const object)
>>> +        const uintptr_t object_data, const u16 rule_type)
>>>   {
>>>       const struct rb_node *node;
>>>
>>> -    if (!object)
>>> +    if (!object_data)
>>
>> object_data can be 0. You need to add a test with such value.
>>
>> We need to be sure that this change cannot affect the current FS code.
> 
>   I got it. I will refactor it.

Well, 0 means a port 0, which might not be correct, but this check 
should not be performed by landlock_merge_ruleset().


>>
>>
>>>           return NULL;
>>> -    node = ruleset->root.rb_node;
>>> +
>>> +    switch (rule_type) {
>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>> +        node = ruleset->root_inode.rb_node;
>>> +        break;
>>> +    default:
>>> +        return ERR_PTR(-EINVAL);
>>
>> This is a bug. There is no check for such value. You need to check and 
>> update all call sites to catch such errors. Same for all new use of 
>> ERR_PTR().
> 
> Sorry, I did not get your point.
> Do you mean I should check the correctness of rule_type in above 
> function which calls landlock_find_rule() ??? Why can't I add such check 
> here?

landlock_find_rule() only returns NULL or a valid pointer, not an error.

[...]
Konstantin Meskhidze (A) March 22, 2022, 12:33 p.m. UTC | #4
3/18/2022 9:33 PM, Mickaël Salaün пишет:
> 
> On 17/03/2022 15:29, Konstantin Meskhidze wrote:
>>
>>
>> 3/16/2022 11:27 AM, Mickaël Salaün пишет:
>>>
>>> On 09/03/2022 14:44, Konstantin Meskhidze wrote:
>>>> A new object union added to support a socket port
>>>> rule type. To support it landlock_insert_rule() and
>>>> landlock_find_rule() were refactored. Now adding
>>>> or searching a rule in a ruleset depends on a
>>>> rule_type argument provided in refactored
>>>> functions mentioned above.
>>>>
>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>> ---
>>>>
>>>> Changes since v3:
>>>> * Split commit.
>>>> * Refactoring landlock_insert_rule and landlock_find_rule functions.
>>>> * Rename new_ruleset->root_inode.
>>>>
>>>> ---
>>>>   security/landlock/fs.c      |   5 +-
>>>>   security/landlock/ruleset.c | 108 
>>>> +++++++++++++++++++++++++-----------
>>>>   security/landlock/ruleset.h |  26 +++++----
>>>>   3 files changed, 94 insertions(+), 45 deletions(-)
>>>>
>>>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>>>> index 97f5c455f5a7..1497948d754f 100644
>>>> --- a/security/landlock/fs.c
>>>> +++ b/security/landlock/fs.c
>>>> @@ -168,7 +168,7 @@ 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);
>>>
>>> For consistency, please use 80 columns everywhere.
>>
>>    Ok. I got it.
>>>
>>>>       mutex_unlock(&ruleset->lock);
>>>>       /*
>>>>        * No need to check for an error because landlock_insert_rule()
>>>> @@ -195,7 +195,8 @@ static inline u64 unmask_layers(
>>>>       inode = d_backing_inode(path->dentry);
>>>>       rcu_read_lock();
>>>>       rule = landlock_find_rule(domain,
>>>> -            rcu_dereference(landlock_inode(inode)->object));
>>>> +            (uintptr_t)rcu_dereference(landlock_inode(inode)->object),
>>>> +            LANDLOCK_RULE_PATH_BENEATH);
>>>>       rcu_read_unlock();
>>>>       if (!rule)
>>>>           return layer_mask;
>>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>>> index a6212b752549..971685c48641 100644
>>>> --- a/security/landlock/ruleset.c
>>>> +++ b/security/landlock/ruleset.c
>>>> @@ -34,7 +34,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
>>>> @@ -81,10 +81,12 @@ static void build_check_rule(void)
>>>>   }
>>>>
>>>>   static struct landlock_rule *create_rule(
>>>> -        struct landlock_object *const object,
>>>> +        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)
>>>> +        const struct landlock_layer *const new_layer,
>>>> +        const u16 rule_type)
>>>>   {
>>>>       struct landlock_rule *new_rule;
>>>>       u32 new_num_layers;
>>>> @@ -103,8 +105,16 @@ static struct landlock_rule *create_rule(
>>>>       if (!new_rule)
>>>>           return ERR_PTR(-ENOMEM);
>>>>       RB_CLEAR_NODE(&new_rule->node);
>>>> -    landlock_get_object(object);
>>>> -    new_rule->object = object;
>>>> +
>>>> +    switch (rule_type) {
>>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>>> +        landlock_get_object(object_ptr);
>>>> +        new_rule->object.ptr = object_ptr;
>>>> +        break;
>>>> +    default:
>>>> +        return ERR_PTR(-EINVAL);
>>>
>>> This would lead to memory leak. You should at least add a 
>>> WARN_ON_ONCE(1) here, but a proper solution would be to remove the 
>>> use of rule_type and only rely on object_ptr and object_data values. 
>>> You can also add a WARN_ON_ONCE(object_ptr && object_data).
>>>
>>>
>>    But rule_type is needed here in coming commits to support network
>>    rules. For LANDLOCK_RULE_PATH_BENEATH rule type 
>> landlock_get_object() is used but for LANDLOCK_RULE_NET_SERVICE is 
>> not. Using rule type is convenient for distinguising between fs and 
>> network rules.
> 
> rule_type is not required to infer if the rule use a pointer or raw 
> data, even with the following commits, because you can rely on 
> object_ptr being NULL or not. This would make create_rule() generic for 
> pointer-based and data-based object, even if not-yet-existing rule 
> types. It is less error-prone to only be able to infer something from 
> one source (i.e. object_ptr and not rule_type).
> 
  Ok. I got you. Will be refactored.
> 
>>>> +    }
>>>> +
>>>>       new_rule->num_layers = new_num_layers;
>>>>       /* Copies the original layer stack. */
>>>>       memcpy(new_rule->layers, layers,
>>>> @@ -120,7 +130,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);
>>>>   }
>>>>
>>>> @@ -156,26 +166,38 @@ static void build_check_ruleset(void)
>>>>    * access rights.
>>>>    */
>>>>   static int insert_rule(struct landlock_ruleset *const ruleset,
>>>> -        struct landlock_object *const object,
>>>> +        struct landlock_object *const object_ptr,
>>>> +        const uintptr_t object_data,
> 
> Can you move rule_type here for this function and similar ones? It makes 
> sense to group object-related arguments.

  Just to group them together, not putting rule_type in the end?
> 
> 
>>>>           const struct landlock_layer (*const layers)[],
>>>> -        size_t num_layers)
>>>> +        size_t num_layers, u16 rule_type)
>>>>   {
>>>>       struct rb_node **walker_node;
>>>>       struct rb_node *parent_node = NULL;
>>>>       struct landlock_rule *new_rule;
>>>> +    uintptr_t object;
>>>> +    struct rb_root *root;
>>>>
>>>>       might_sleep();
>>>>       lockdep_assert_held(&ruleset->lock);
>>>> -    if (WARN_ON_ONCE(!object || !layers))
>>>> -        return -ENOENT;
>>>
>>> You can leave this code here.
>>
>>   But anyway in coming commits with network rules this code will be 
>> moved into case LANDLOCK_RULE_PATH_BENEATH: ....
> 
> Yes, but without rule_type you don't need to duplicate this check, just 
> to remove object_ptr from WARN_ON_ONCE() and replace the rule_type 
> switch/case with if (object_ptr).
> 
> You can change to this:
> 
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -194,43 +194,49 @@ static void build_check_ruleset(void)
>    */
>   static int insert_rule(struct landlock_ruleset *const ruleset,
>           struct landlock_object *const object_ptr,
> -        const uintptr_t object_data,
> +        uintptr_t object_data, /* move @rule_type here */
>           const struct landlock_layer (*const layers)[],
> -        size_t num_layers, u16 rule_type)
> +        size_t num_layers, const enum landlock_rule_type rule_type)
>   {
>       struct rb_node **walker_node;
>       struct rb_node *parent_node = NULL;
>       struct landlock_rule *new_rule;
> -    uintptr_t object;
>       struct rb_root *root;
> 
>       might_sleep();
>       lockdep_assert_held(&ruleset->lock);
> -    /* Choose rb_tree structure depending on a rule type */
> +
> +    if (WARN_ON_ONCE(!layers))
> +        return -ENOENT;
> +    if (WARN_ON_ONCE(object_ptr && object_data))
> +        return -EINVAL;
> +
> +    /* Chooses the rb_tree according to the rule type. */
>       switch (rule_type) {
>       case LANDLOCK_RULE_PATH_BENEATH:
> -        if (WARN_ON_ONCE(!object_ptr || !layers))
> +        if (WARN_ON_ONCE(!object_ptr))
>               return -ENOENT;
> -        object = (uintptr_t)object_ptr;
> +        object_data = (uintptr_t)object_ptr;
>           root = &ruleset->root_inode;
>           break;
>       case LANDLOCK_RULE_NET_SERVICE:
> -        if (WARN_ON_ONCE(!object_data || !layers))
> -            return -ENOENT;
> -        object = object_data;
> +        if (WARN_ON_ONCE(object_ptr))
> +            return -EINVAL;
>           root = &ruleset->root_net_port;
>           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.data != object) {
> +        if (this->object.data != object_data) {
>               parent_node = *walker_node;
> -            if (this->object.data < object)
> +            if (this->object.data < object_data)
>                   walker_node = &((*walker_node)->rb_right);
>               else
>                   walker_node = &((*walker_node)->rb_left);
> 
> 
> This highlight an implicit error handling for a port value of 0. I'm not 
> sure if this should be allowed or not though. If not, it should be an 
> explicit service_port check in add_rule_net_service(). A data value of 
> zero might be legitimate for this use case or not-yet-existing 
> data-based rule types. Anyway, this kind of check is specific to the use 
> case and should not be part of insert_rule().
> 
  Ok. I got it.
> 
> 
>>>
>>>> -    walker_node = &(ruleset->root.rb_node);
>>>> +    /* Choose rb_tree structure depending on a rule type */
>>>> +    switch (rule_type) {
>>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>>> +        if (WARN_ON_ONCE(!object_ptr || !layers))
>>>> +            return -ENOENT;
>>>> +        object = (uintptr_t)object_ptr;
>>>> +        root = &ruleset->root_inode;
>>>> +        break;
>>>> +    default:
>>>> +        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) {
>>>>               parent_node = *walker_node;
>>>> -            if (this->object < object)
>>>> +            if (this->object.data < object)
>>>>                   walker_node = &((*walker_node)->rb_right);
>>>>               else
>>>>                   walker_node = &((*walker_node)->rb_left);
>>>> @@ -207,11 +229,15 @@ 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:
>>>
>>> Same here and for the following code, you should replace such 
>>> switch/case with an if (object_ptr).
>>>    What about coming commits with network rule_type support?
> 
> This will still works.
> 
   Yep. Ok.
> 
>>>
>>>> +            new_rule = create_rule(object_ptr, 0, &this->layers, 
>>>> this->num_layers,
>>>> +                           &(*layers)[0], rule_type);
>>>> +            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);
>>>
>>> Use the root variable here. Same for the following code and patches.
>>
>>   What about your suggestion to use 2 rb_tress to support different 
>> rule_types:
>>       1. root_inode - for filesystem objects
>>           2. root_net_port - for network port objects
>> ????
> 
> I was talking about the root variable you declared a few line before. 
> The conversion from ruleset->root to ruleset->root_inode is fine.
> 
  Sorry. It was a misunderstanding. Got your point.
> 
> [...]
> 
>>>> @@ -465,20 +501,28 @@ struct landlock_ruleset *landlock_merge_ruleset(
>>>>    */
>>>>   const struct landlock_rule *landlock_find_rule(
>>>>           const struct landlock_ruleset *const ruleset,
>>>> -        const struct landlock_object *const object)
>>>> +        const uintptr_t object_data, const u16 rule_type)
>>>>   {
>>>>       const struct rb_node *node;
>>>>
>>>> -    if (!object)
>>>> +    if (!object_data)
>>>
>>> object_data can be 0. You need to add a test with such value.
>>>
>>> We need to be sure that this change cannot affect the current FS code.
>>
>>   I got it. I will refactor it.
> 
> Well, 0 means a port 0, which might not be correct, but this check 
> should not be performed by landlock_merge_ruleset().
> 
  Do you mean landlock_find_rule()?? Cause this check is not
  performed in landlock_merge_ruleset().

> 
>>>
>>>
>>>>           return NULL;
>>>> -    node = ruleset->root.rb_node;
>>>> +
>>>> +    switch (rule_type) {
>>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>>> +        node = ruleset->root_inode.rb_node;
>>>> +        break;
>>>> +    default:
>>>> +        return ERR_PTR(-EINVAL);
>>>
>>> This is a bug. There is no check for such value. You need to check 
>>> and update all call sites to catch such errors. Same for all new use 
>>> of ERR_PTR().
>>
>> Sorry, I did not get your point.
>> Do you mean I should check the correctness of rule_type in above 
>> function which calls landlock_find_rule() ??? Why can't I add such 
>> check here?
> 
> landlock_find_rule() only returns NULL or a valid pointer, not an error.

   What about incorrect rule_type?? Return NULL? Or final rule_checl 
must be in upper function?
> 
> [...]
> .
Mickaël Salaün March 22, 2022, 1:24 p.m. UTC | #5
On 22/03/2022 13:33, Konstantin Meskhidze wrote:
> 
> 
> 3/18/2022 9:33 PM, Mickaël Salaün пишет:
>>
>> On 17/03/2022 15:29, Konstantin Meskhidze wrote:
>>>
>>>
>>> 3/16/2022 11:27 AM, Mickaël Salaün пишет:
>>>>
>>>> On 09/03/2022 14:44, Konstantin Meskhidze wrote:
>>>>> A new object union added to support a socket port
>>>>> rule type. To support it landlock_insert_rule() and
>>>>> landlock_find_rule() were refactored. Now adding
>>>>> or searching a rule in a ruleset depends on a
>>>>> rule_type argument provided in refactored
>>>>> functions mentioned above.
>>>>>
>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>> ---

[...]

>>>>> @@ -156,26 +166,38 @@ static void build_check_ruleset(void)
>>>>>    * access rights.
>>>>>    */
>>>>>   static int insert_rule(struct landlock_ruleset *const ruleset,
>>>>> -        struct landlock_object *const object,
>>>>> +        struct landlock_object *const object_ptr,
>>>>> +        const uintptr_t object_data,
>>
>> Can you move rule_type here for this function and similar ones? It 
>> makes sense to group object-related arguments.
> 
>   Just to group them together, not putting rule_type in the end?

Yes

[...]

>>>>> @@ -465,20 +501,28 @@ struct landlock_ruleset *landlock_merge_ruleset(
>>>>>    */
>>>>>   const struct landlock_rule *landlock_find_rule(
>>>>>           const struct landlock_ruleset *const ruleset,
>>>>> -        const struct landlock_object *const object)
>>>>> +        const uintptr_t object_data, const u16 rule_type)
>>>>>   {
>>>>>       const struct rb_node *node;
>>>>>
>>>>> -    if (!object)
>>>>> +    if (!object_data)
>>>>
>>>> object_data can be 0. You need to add a test with such value.
>>>>
>>>> We need to be sure that this change cannot affect the current FS code.
>>>
>>>   I got it. I will refactor it.
>>
>> Well, 0 means a port 0, which might not be correct, but this check 
>> should not be performed by landlock_merge_ruleset().
>>
>   Do you mean landlock_find_rule()?? Cause this check is not
>   performed in landlock_merge_ruleset().

Yes, I was thinking about landlock_find_rule(). If you run your tests 
with the patch I proposed, you'll see that one of these tests will fail 
(when port equal 0). When creating a new network rule, 
add_rule_net_service() should check if the port value is valid. However, 
the above `if (!object_data)` is not correct anymore.

The remaining question is: should we need to accept 0 as a valid TCP 
port? Can it be used? How does the kernel handle it?

> 
>>
>>>>
>>>>
>>>>>           return NULL;
>>>>> -    node = ruleset->root.rb_node;
>>>>> +
>>>>> +    switch (rule_type) {
>>>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>>>> +        node = ruleset->root_inode.rb_node;
>>>>> +        break;
>>>>> +    default:
>>>>> +        return ERR_PTR(-EINVAL);
>>>>
>>>> This is a bug. There is no check for such value. You need to check 
>>>> and update all call sites to catch such errors. Same for all new use 
>>>> of ERR_PTR().
>>>
>>> Sorry, I did not get your point.
>>> Do you mean I should check the correctness of rule_type in above 
>>> function which calls landlock_find_rule() ??? Why can't I add such 
>>> check here?
>>
>> landlock_find_rule() only returns NULL or a valid pointer, not an error.
> 
>    What about incorrect rule_type?? Return NULL? Or final rule_checl 
> must be in upper function?

This case should never happen anyway. You should return NULL and call 
WARN_ON_ONCE(1) just before. The same kind of WARN_ON_ONCE(1) call 
should be part of all switch/cases of rule_type (except the two valid 
values of course).
Konstantin Meskhidze (A) March 23, 2022, 8:41 a.m. UTC | #6
3/22/2022 4:24 PM, Mickaël Salaün пишет:
> 
> On 22/03/2022 13:33, Konstantin Meskhidze wrote:
>>
>>
>> 3/18/2022 9:33 PM, Mickaël Salaün пишет:
>>>
>>> On 17/03/2022 15:29, Konstantin Meskhidze wrote:
>>>>
>>>>
>>>> 3/16/2022 11:27 AM, Mickaël Salaün пишет:
>>>>>
>>>>> On 09/03/2022 14:44, Konstantin Meskhidze wrote:
>>>>>> A new object union added to support a socket port
>>>>>> rule type. To support it landlock_insert_rule() and
>>>>>> landlock_find_rule() were refactored. Now adding
>>>>>> or searching a rule in a ruleset depends on a
>>>>>> rule_type argument provided in refactored
>>>>>> functions mentioned above.
>>>>>>
>>>>>> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
>>>>>> ---
> 
> [...]
> 
>>>>>> @@ -156,26 +166,38 @@ static void build_check_ruleset(void)
>>>>>>    * access rights.
>>>>>>    */
>>>>>>   static int insert_rule(struct landlock_ruleset *const ruleset,
>>>>>> -        struct landlock_object *const object,
>>>>>> +        struct landlock_object *const object_ptr,
>>>>>> +        const uintptr_t object_data,
>>>
>>> Can you move rule_type here for this function and similar ones? It 
>>> makes sense to group object-related arguments.
>>
>>   Just to group them together, not putting rule_type in the end?
> 
> Yes

   Ok. Got it.
> 
> [...]
> 
>>>>>> @@ -465,20 +501,28 @@ struct landlock_ruleset 
>>>>>> *landlock_merge_ruleset(
>>>>>>    */
>>>>>>   const struct landlock_rule *landlock_find_rule(
>>>>>>           const struct landlock_ruleset *const ruleset,
>>>>>> -        const struct landlock_object *const object)
>>>>>> +        const uintptr_t object_data, const u16 rule_type)
>>>>>>   {
>>>>>>       const struct rb_node *node;
>>>>>>
>>>>>> -    if (!object)
>>>>>> +    if (!object_data)
>>>>>
>>>>> object_data can be 0. You need to add a test with such value.
>>>>>
>>>>> We need to be sure that this change cannot affect the current FS code.
>>>>
>>>>   I got it. I will refactor it.
>>>
>>> Well, 0 means a port 0, which might not be correct, but this check 
>>> should not be performed by landlock_merge_ruleset().
>>>
>>   Do you mean landlock_find_rule()?? Cause this check is not
>>   performed in landlock_merge_ruleset().
> 
> Yes, I was thinking about landlock_find_rule(). If you run your tests 
> with the patch I proposed, you'll see that one of these tests will fail 
> (when port equal 0). When creating a new network rule, 
> add_rule_net_service() should check if the port value is valid. However, 
> the above `if (!object_data)` is not correct anymore.
> 
> The remaining question is: should we need to accept 0 as a valid TCP 
> port? Can it be used? How does the kernel handle it?

  I agree that must be a check for port 0 in add_rule_net_service(), 
cause unlike most port numbers, port 0 is a reserved port in TCP/IP 
networking, meaning that it should not be used in TCP or UDP messages.
Also network traffic sent across the internet to hosts listening on port 
0 might be generated from network attackers or accidentally by 
applications programmed incorrectly.
Source: https://www.lifewire.com/port-0-in-tcp-and-udp-818145

> 
>>
>>>
>>>>>
>>>>>
>>>>>>           return NULL;
>>>>>> -    node = ruleset->root.rb_node;
>>>>>> +
>>>>>> +    switch (rule_type) {
>>>>>> +    case LANDLOCK_RULE_PATH_BENEATH:
>>>>>> +        node = ruleset->root_inode.rb_node;
>>>>>> +        break;
>>>>>> +    default:
>>>>>> +        return ERR_PTR(-EINVAL);
>>>>>
>>>>> This is a bug. There is no check for such value. You need to check 
>>>>> and update all call sites to catch such errors. Same for all new 
>>>>> use of ERR_PTR().
>>>>
>>>> Sorry, I did not get your point.
>>>> Do you mean I should check the correctness of rule_type in above 
>>>> function which calls landlock_find_rule() ??? Why can't I add such 
>>>> check here?
>>>
>>> landlock_find_rule() only returns NULL or a valid pointer, not an error.
>>
>>    What about incorrect rule_type?? Return NULL? Or final rule_checl 
>> must be in upper function?
> 
> This case should never happen anyway. You should return NULL and call 
> WARN_ON_ONCE(1) just before. The same kind of WARN_ON_ONCE(1) call 
> should be part of all switch/cases of rule_type (except the two valid 
> values of course).

  Ok. I got it. Thanks.
> .
Mickaël Salaün April 12, 2022, 11:07 a.m. UTC | #7
On 23/03/2022 09:41, Konstantin Meskhidze wrote:
> 
> 
> 3/22/2022 4:24 PM, Mickaël Salaün пишет:
>>

[...]
>> The remaining question is: should we need to accept 0 as a valid TCP 
>> port? Can it be used? How does the kernel handle it?
> 
>   I agree that must be a check for port 0 in add_rule_net_service(), 
> cause unlike most port numbers, port 0 is a reserved port in TCP/IP 
> networking, meaning that it should not be used in TCP or UDP messages.
> Also network traffic sent across the internet to hosts listening on port 
> 0 might be generated from network attackers or accidentally by 
> applications programmed incorrectly.
> Source: https://www.lifewire.com/port-0-in-tcp-and-udp-818145

OK, so denying this port by default without a way to allow it should not 
be an issue. I guess an -EINVAL error would make sense when trying to 
allow this port. This should be documented in a comment (with a link to 
the RFC/section) and a dedicated test should check that behavior.

What is the behavior of firewalls (e.g. Netfiler) when trying to filter 
port 0?

This doesn't seem to be settle though: 
https://www.austingroupbugs.net/view.php?id=1068

Interesting article: 
https://z3r0trust.medium.com/socket-programming-the-bizarre-tcp-ip-port-0-saga-fcfbc0e0a276
Konstantin Meskhidze (A) April 26, 2022, 9:15 a.m. UTC | #8
4/12/2022 2:07 PM, Mickaël Salaün пишет:
> 
> On 23/03/2022 09:41, Konstantin Meskhidze wrote:
>>
>>
>> 3/22/2022 4:24 PM, Mickaël Salaün пишет:
>>>
> 
> [...]
>>> The remaining question is: should we need to accept 0 as a valid TCP 
>>> port? Can it be used? How does the kernel handle it?
>>
>>   I agree that must be a check for port 0 in add_rule_net_service(), 
>> cause unlike most port numbers, port 0 is a reserved port in TCP/IP 
>> networking, meaning that it should not be used in TCP or UDP messages.
>> Also network traffic sent across the internet to hosts listening on 
>> port 0 might be generated from network attackers or accidentally by 
>> applications programmed incorrectly.
>> Source: https://www.lifewire.com/port-0-in-tcp-and-udp-818145
> 
> OK, so denying this port by default without a way to allow it should not 
> be an issue. I guess an -EINVAL error would make sense when trying to 
> allow this port. This should be documented in a comment (with a link to 
> the RFC/section) and a dedicated test should check that behavior.
> 
> What is the behavior of firewalls (e.g. Netfiler) when trying to filter 
> port 0?

To be honest I don't know. I'm trying to check it.
>  
> This doesn't seem to be settle though: 
> https://www.austingroupbugs.net/view.php?id=1068
> 
> Interesting article: 
> https://z3r0trust.medium.com/socket-programming-the-bizarre-tcp-ip-port-0-saga-fcfbc0e0a276 
  Thanks. I will check.
> 
> .
diff mbox series

Patch

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 97f5c455f5a7..1497948d754f 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -168,7 +168,7 @@  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()
@@ -195,7 +195,8 @@  static inline u64 unmask_layers(
 	inode = d_backing_inode(path->dentry);
 	rcu_read_lock();
 	rule = landlock_find_rule(domain,
-			rcu_dereference(landlock_inode(inode)->object));
+			(uintptr_t)rcu_dereference(landlock_inode(inode)->object),
+			LANDLOCK_RULE_PATH_BENEATH);
 	rcu_read_unlock();
 	if (!rule)
 		return layer_mask;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index a6212b752549..971685c48641 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -34,7 +34,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
@@ -81,10 +81,12 @@  static void build_check_rule(void)
 }

 static struct landlock_rule *create_rule(
-		struct landlock_object *const object,
+		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)
+		const struct landlock_layer *const new_layer,
+		const u16 rule_type)
 {
 	struct landlock_rule *new_rule;
 	u32 new_num_layers;
@@ -103,8 +105,16 @@  static struct landlock_rule *create_rule(
 	if (!new_rule)
 		return ERR_PTR(-ENOMEM);
 	RB_CLEAR_NODE(&new_rule->node);
-	landlock_get_object(object);
-	new_rule->object = object;
+
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		landlock_get_object(object_ptr);
+		new_rule->object.ptr = object_ptr;
+		break;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+
 	new_rule->num_layers = new_num_layers;
 	/* Copies the original layer stack. */
 	memcpy(new_rule->layers, layers,
@@ -120,7 +130,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);
 }

@@ -156,26 +166,38 @@  static void build_check_ruleset(void)
  * access rights.
  */
 static int insert_rule(struct landlock_ruleset *const ruleset,
-		struct landlock_object *const object,
+		struct landlock_object *const object_ptr,
+		const uintptr_t object_data,
 		const struct landlock_layer (*const layers)[],
-		size_t num_layers)
+		size_t num_layers, u16 rule_type)
 {
 	struct rb_node **walker_node;
 	struct rb_node *parent_node = NULL;
 	struct landlock_rule *new_rule;
+	uintptr_t object;
+	struct rb_root *root;

 	might_sleep();
 	lockdep_assert_held(&ruleset->lock);
-	if (WARN_ON_ONCE(!object || !layers))
-		return -ENOENT;
-	walker_node = &(ruleset->root.rb_node);
+	/* Choose rb_tree structure depending on a rule type */
+	switch (rule_type) {
+	case LANDLOCK_RULE_PATH_BENEATH:
+		if (WARN_ON_ONCE(!object_ptr || !layers))
+			return -ENOENT;
+		object = (uintptr_t)object_ptr;
+		root = &ruleset->root_inode;
+		break;
+	default:
+		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) {
 			parent_node = *walker_node;
-			if (this->object < object)
+			if (this->object.data < object)
 				walker_node = &((*walker_node)->rb_right);
 			else
 				walker_node = &((*walker_node)->rb_left);
@@ -207,11 +229,15 @@  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], rule_type);
+			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;
 	}
@@ -220,11 +246,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, rule_type);
+		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;
 }
@@ -242,7 +272,9 @@  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 u32 access)
+		struct landlock_object *const object_ptr,
+		const uintptr_t object_data,
+		const u32 access, const u16 rule_type)
 {
 	struct landlock_layer layers[] = {{
 		.access = access,
@@ -251,7 +283,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, &layers,
+			   ARRAY_SIZE(layers), rule_type);
 }

 static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -297,7 +330,7 @@  static int merge_ruleset(struct landlock_ruleset *const dst,

 	/* Merges the @src tree. */
 	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
-			&src->root, node) {
+			&src->root_inode, node) {
 		struct landlock_layer layers[] = {{
 			.level = dst->num_layers,
 		}};
@@ -311,8 +344,8 @@  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,
-				ARRAY_SIZE(layers));
+		err = insert_rule(dst, walker_rule->object.ptr, 0, &layers,
+				ARRAY_SIZE(layers), LANDLOCK_RULE_PATH_BENEATH);
 		if (err)
 			goto out_unlock;
 	}
@@ -323,6 +356,8 @@  static int merge_ruleset(struct landlock_ruleset *const dst,
 	return err;
 }

+
+
 static int inherit_ruleset(struct landlock_ruleset *const parent,
 		struct landlock_ruleset *const child)
 {
@@ -339,9 +374,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,
+				&walker_rule->layers, walker_rule->num_layers,
+				LANDLOCK_RULE_PATH_BENEATH);
 		if (err)
 			goto out_unlock;
 	}
@@ -372,7 +408,7 @@  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,
+	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
 			node)
 		free_rule(freeme);
 	put_hierarchy(ruleset->hierarchy);
@@ -465,20 +501,28 @@  struct landlock_ruleset *landlock_merge_ruleset(
  */
 const struct landlock_rule *landlock_find_rule(
 		const struct landlock_ruleset *const ruleset,
-		const struct landlock_object *const object)
+		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:
+		return ERR_PTR(-EINVAL);
+	}
+
 	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 bc87e5f787f7..088b8d95f653 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -50,15 +50,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 socket port object. 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 (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
@@ -95,7 +97,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.
@@ -157,7 +159,9 @@  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 u32 access);
+			 struct landlock_object *const object_ptr,
+			 const uintptr_t object_data,
+			 const u32 access, const u16 rule_type);

 struct landlock_ruleset *landlock_merge_ruleset(
 		struct landlock_ruleset *const parent,
@@ -165,7 +169,7 @@  struct landlock_ruleset *landlock_merge_ruleset(

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

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