* [PATCH v1 0/3] Refactor Landlock access mask management
@ 2024-10-01 14:12 Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 1/3] landlock: Refactor filesystem " Mickaël Salaün
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Mickaël Salaün @ 2024-10-01 14:12 UTC (permalink / raw)
To: Günther Noack, Mikhail Ivanov
Cc: Mickaël Salaün, Konstantin Meskhidze, Paul Moore,
Tahera Fahimi, linux-kernel, linux-security-module
Hi,
To simplify code for new access types [1], add 2 new helpers:
- landlock_merge_access_masks()
- landlock_filter_access_masks()
The last patch uses these helpers to optimize Landlock scope management
like with filesystem and network access checks.
[1] https://lore.kernel.org/r/3433b163-2371-e679-cc8a-e540a0218bca@huawei-partners.com
Regards,
Mickaël Salaün (3):
landlock: Refactor filesystem access mask management
landlock: Refactor network access mask management
landlock: Optimize scope enforcement
security/landlock/fs.c | 21 ++++-----------
security/landlock/net.c | 22 ++++------------
security/landlock/ruleset.h | 51 +++++++++++++++++++++++++++---------
security/landlock/syscalls.c | 2 +-
security/landlock/task.c | 22 +++++++++++++---
5 files changed, 68 insertions(+), 50 deletions(-)
base-commit: 9852d85ec9d492ebef56dc5f229416c925758edc
--
2.46.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 1/3] landlock: Refactor filesystem access mask management
2024-10-01 14:12 [PATCH v1 0/3] Refactor Landlock access mask management Mickaël Salaün
@ 2024-10-01 14:12 ` Mickaël Salaün
2024-10-05 16:57 ` Günther Noack
2024-10-01 14:12 ` [PATCH v1 2/3] landlock: Refactor network " Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 3/3] landlock: Optimize scope enforcement Mickaël Salaün
2 siblings, 1 reply; 7+ messages in thread
From: Mickaël Salaün @ 2024-10-01 14:12 UTC (permalink / raw)
To: Günther Noack, Mikhail Ivanov
Cc: Mickaël Salaün, Konstantin Meskhidze, Paul Moore,
Tahera Fahimi, linux-kernel, linux-security-module
Replace get_raw_handled_fs_accesses() with a generic
landlock_merge_access_masks(), and replace the get_fs_domain()
implementation with a call to the new landlock_filter_access_masks()
helper. These helpers will also be useful for other types of access.
Replace struct access_masks with union access_masks that includes a new
"all" field to simplify mask filtering.
Cc: Günther Noack <gnoack@google.com>
Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241001141234.397649-2-mic@digikod.net
---
security/landlock/fs.c | 21 ++++-----------
security/landlock/ruleset.h | 51 +++++++++++++++++++++++++++---------
security/landlock/syscalls.c | 2 +-
3 files changed, 44 insertions(+), 30 deletions(-)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 7d79fc8abe21..a2ef7d151c81 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -388,33 +388,22 @@ static bool is_nouser_or_private(const struct dentry *dentry)
unlikely(IS_PRIVATE(d_backing_inode(dentry))));
}
-static access_mask_t
-get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
-{
- access_mask_t access_dom = 0;
- size_t layer_level;
-
- for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
- access_dom |=
- landlock_get_raw_fs_access_mask(domain, layer_level);
- return access_dom;
-}
-
static access_mask_t
get_handled_fs_accesses(const struct landlock_ruleset *const domain)
{
/* Handles all initially denied by default access rights. */
- return get_raw_handled_fs_accesses(domain) |
+ return landlock_merge_access_masks(domain).fs |
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}
static const struct landlock_ruleset *
get_fs_domain(const struct landlock_ruleset *const domain)
{
- if (!domain || !get_raw_handled_fs_accesses(domain))
- return NULL;
+ const union access_masks all_fs = {
+ .fs = ~0,
+ };
- return domain;
+ return landlock_filter_access_masks(domain, all_fs);
}
static const struct landlock_ruleset *get_current_fs_domain(void)
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 61bdbc550172..a816042ca8f3 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -41,12 +41,19 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
/* Ruleset access masks. */
-struct access_masks {
- access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
- access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
- access_mask_t scope : LANDLOCK_NUM_SCOPE;
+union access_masks {
+ struct {
+ access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
+ access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
+ access_mask_t scope : LANDLOCK_NUM_SCOPE;
+ };
+ u32 all;
};
+/* Makes sure all fields are covered. */
+static_assert(sizeof(((union access_masks *)NULL)->all) ==
+ sizeof(union access_masks));
+
typedef u16 layer_mask_t;
/* Makes sure all layers can be checked. */
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
@@ -229,7 +236,7 @@ struct landlock_ruleset {
* layers are set once and never changed for the
* lifetime of the ruleset.
*/
- struct access_masks access_masks[];
+ union access_masks access_masks[];
};
};
};
@@ -260,6 +267,31 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
refcount_inc(&ruleset->usage);
}
+static inline union access_masks
+landlock_merge_access_masks(const struct landlock_ruleset *const domain)
+{
+ size_t layer_level;
+ union access_masks matches = {};
+
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
+ matches.all |= domain->access_masks[layer_level].all;
+
+ return matches;
+}
+
+static inline const struct landlock_ruleset *
+landlock_filter_access_masks(const struct landlock_ruleset *const domain,
+ const union access_masks masks)
+{
+ if (!domain)
+ return NULL;
+
+ if (landlock_merge_access_masks(domain).all & masks.all)
+ return domain;
+
+ return NULL;
+}
+
static inline void
landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
const access_mask_t fs_access_mask,
@@ -295,19 +327,12 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
ruleset->access_masks[layer_level].scope |= mask;
}
-static inline access_mask_t
-landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
-{
- return ruleset->access_masks[layer_level].fs;
-}
-
static inline access_mask_t
landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level)
{
/* Handles all initially denied by default access rights. */
- return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
+ return ruleset->access_masks[layer_level].fs |
LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index f5a0e7182ec0..c097d356fa45 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -329,7 +329,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
- mask = landlock_get_raw_fs_access_mask(ruleset, 0);
+ mask = ruleset->access_masks[0].fs;
if ((path_beneath_attr.allowed_access | mask) != mask)
return -EINVAL;
--
2.46.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v1 2/3] landlock: Refactor network access mask management
2024-10-01 14:12 [PATCH v1 0/3] Refactor Landlock access mask management Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 1/3] landlock: Refactor filesystem " Mickaël Salaün
@ 2024-10-01 14:12 ` Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 3/3] landlock: Optimize scope enforcement Mickaël Salaün
2 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2024-10-01 14:12 UTC (permalink / raw)
To: Günther Noack, Mikhail Ivanov
Cc: Mickaël Salaün, Konstantin Meskhidze, Paul Moore,
Tahera Fahimi, linux-kernel, linux-security-module
Replace the get_raw_handled_net_accesses() implementation with a call to
landlock_filter_access_masks().
Cc: Günther Noack <gnoack@google.com>
Cc: Konstantin Meskhidze <konstantin.meskhidze@huawei.com>
Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241001141234.397649-3-mic@digikod.net
---
security/landlock/net.c | 22 +++++-----------------
1 file changed, 5 insertions(+), 17 deletions(-)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index c8bcd29bde09..bc3d943a7118 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -39,26 +39,14 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
return err;
}
-static access_mask_t
-get_raw_handled_net_accesses(const struct landlock_ruleset *const domain)
-{
- access_mask_t access_dom = 0;
- size_t layer_level;
-
- for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
- access_dom |= landlock_get_net_access_mask(domain, layer_level);
- return access_dom;
-}
-
static const struct landlock_ruleset *get_current_net_domain(void)
{
- const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
-
- if (!dom || !get_raw_handled_net_accesses(dom))
- return NULL;
+ const union access_masks all_net = {
+ .net = ~0,
+ };
- return dom;
+ return landlock_filter_access_masks(landlock_get_current_domain(),
+ all_net);
}
static int current_check_access_socket(struct socket *const sock,
--
2.46.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v1 3/3] landlock: Optimize scope enforcement
2024-10-01 14:12 [PATCH v1 0/3] Refactor Landlock access mask management Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 1/3] landlock: Refactor filesystem " Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 2/3] landlock: Refactor network " Mickaël Salaün
@ 2024-10-01 14:12 ` Mickaël Salaün
2 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2024-10-01 14:12 UTC (permalink / raw)
To: Günther Noack, Mikhail Ivanov
Cc: Mickaël Salaün, Konstantin Meskhidze, Paul Moore,
Tahera Fahimi, linux-kernel, linux-security-module
Do not walk through the domain hierarchy when the required scope is not
supported by this domain. This is the same approach as for filesystem
and network restrictions.
Cc: Günther Noack <gnoack@google.com>
Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
Cc: Tahera Fahimi <fahimitahera@gmail.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20241001141234.397649-4-mic@digikod.net
---
security/landlock/task.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 4acbd7c40eee..02e3a0330b21 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -204,12 +204,22 @@ static bool is_abstract_socket(struct sock *const sock)
return false;
}
+static const struct landlock_ruleset *get_current_unix_scope_domain(void)
+{
+ const union access_masks unix_scope = {
+ .scope = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+ };
+
+ return landlock_filter_access_masks(landlock_get_current_domain(),
+ unix_scope);
+}
+
static int hook_unix_stream_connect(struct sock *const sock,
struct sock *const other,
struct sock *const newsk)
{
const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
+ get_current_unix_scope_domain();
/* Quick return for non-landlocked tasks. */
if (!dom)
@@ -225,7 +235,7 @@ static int hook_unix_may_send(struct socket *const sock,
struct socket *const other)
{
const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
+ get_current_unix_scope_domain();
if (!dom)
return 0;
@@ -243,6 +253,10 @@ static int hook_unix_may_send(struct socket *const sock,
return 0;
}
+static const union access_masks signal_scope = {
+ .scope = LANDLOCK_SCOPE_SIGNAL,
+};
+
static int hook_task_kill(struct task_struct *const p,
struct kernel_siginfo *const info, const int sig,
const struct cred *const cred)
@@ -256,6 +270,7 @@ static int hook_task_kill(struct task_struct *const p,
} else {
dom = landlock_get_current_domain();
}
+ dom = landlock_filter_access_masks(dom, signal_scope);
/* Quick return for non-landlocked tasks. */
if (!dom)
@@ -279,7 +294,8 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
/* Lock already held by send_sigio() and send_sigurg(). */
lockdep_assert_held(&fown->lock);
- dom = landlock_file(fown->file)->fown_domain;
+ dom = landlock_filter_access_masks(
+ landlock_file(fown->file)->fown_domain, signal_scope);
/* Quick return for unowned socket. */
if (!dom)
--
2.46.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v1 1/3] landlock: Refactor filesystem access mask management
2024-10-01 14:12 ` [PATCH v1 1/3] landlock: Refactor filesystem " Mickaël Salaün
@ 2024-10-05 16:57 ` Günther Noack
2024-10-07 13:00 ` Mickaël Salaün
0 siblings, 1 reply; 7+ messages in thread
From: Günther Noack @ 2024-10-05 16:57 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, Mikhail Ivanov, Konstantin Meskhidze,
Paul Moore, Tahera Fahimi, linux-kernel, linux-security-module
On Tue, Oct 01, 2024 at 04:12:32PM +0200, Mickaël Salaün wrote:
> Replace get_raw_handled_fs_accesses() with a generic
> landlock_merge_access_masks(), and replace the get_fs_domain()
> implementation with a call to the new landlock_filter_access_masks()
> helper. These helpers will also be useful for other types of access.
>
> Replace struct access_masks with union access_masks that includes a new
> "all" field to simplify mask filtering.
>
> Cc: Günther Noack <gnoack@google.com>
> Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> Link: https://lore.kernel.org/r/20241001141234.397649-2-mic@digikod.net
> ---
> security/landlock/fs.c | 21 ++++-----------
> security/landlock/ruleset.h | 51 +++++++++++++++++++++++++++---------
> security/landlock/syscalls.c | 2 +-
> 3 files changed, 44 insertions(+), 30 deletions(-)
>
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 7d79fc8abe21..a2ef7d151c81 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -388,33 +388,22 @@ static bool is_nouser_or_private(const struct dentry *dentry)
> unlikely(IS_PRIVATE(d_backing_inode(dentry))));
> }
>
> -static access_mask_t
> -get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
> -{
> - access_mask_t access_dom = 0;
> - size_t layer_level;
> -
> - for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> - access_dom |=
> - landlock_get_raw_fs_access_mask(domain, layer_level);
> - return access_dom;
> -}
> -
> static access_mask_t
> get_handled_fs_accesses(const struct landlock_ruleset *const domain)
> {
> /* Handles all initially denied by default access rights. */
> - return get_raw_handled_fs_accesses(domain) |
> + return landlock_merge_access_masks(domain).fs |
> LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> }
>
> static const struct landlock_ruleset *
> get_fs_domain(const struct landlock_ruleset *const domain)
> {
> - if (!domain || !get_raw_handled_fs_accesses(domain))
> - return NULL;
> + const union access_masks all_fs = {
> + .fs = ~0,
> + };
>
> - return domain;
> + return landlock_filter_access_masks(domain, all_fs);
> }
>
> static const struct landlock_ruleset *get_current_fs_domain(void)
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 61bdbc550172..a816042ca8f3 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -41,12 +41,19 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
> static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
>
> /* Ruleset access masks. */
> -struct access_masks {
> - access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> - access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> - access_mask_t scope : LANDLOCK_NUM_SCOPE;
> +union access_masks {
> + struct {
> + access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> + access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> + access_mask_t scope : LANDLOCK_NUM_SCOPE;
> + };
> + u32 all;
> };
More of a style remark:
I wonder whether it is worth turning this into a union.
If this is for performance, I do not think is buys you much. With
optimization enabled, it does not make much of a difference whether
you are doing the & on .all or whether you are doing it on the
individual fields. (I tried it out with gcc. The only difference is
that the & on the individual fields will at the end mask only the bits
that belong to these fields.)
At the same time, in most places where struct access_masks is used,
the union is not necessary and might add to the confusion.
>
> +/* Makes sure all fields are covered. */
> +static_assert(sizeof(((union access_masks *)NULL)->all) ==
> + sizeof(union access_masks));
> +
> typedef u16 layer_mask_t;
> /* Makes sure all layers can be checked. */
> static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> @@ -229,7 +236,7 @@ struct landlock_ruleset {
> * layers are set once and never changed for the
> * lifetime of the ruleset.
> */
> - struct access_masks access_masks[];
> + union access_masks access_masks[];
> };
> };
> };
> @@ -260,6 +267,31 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
> refcount_inc(&ruleset->usage);
> }
>
> +static inline union access_masks
> +landlock_merge_access_masks(const struct landlock_ruleset *const domain)
> +{
> + size_t layer_level;
> + union access_masks matches = {};
> +
> + for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> + matches.all |= domain->access_masks[layer_level].all;
> +
> + return matches;
> +}
> +
> +static inline const struct landlock_ruleset *
> +landlock_filter_access_masks(const struct landlock_ruleset *const domain,
> + const union access_masks masks)
With this function name, the return type of this function is
unintuitive to me. Judging by the name, I would have expected a
function that returns a "access_masks" value as well, similar to the
function one above (the remaining access rights after filtering)?
In the places where the result of this function is returned directly,
I find myself jumping back to the function implementation to
understand what this means.
As a constructive suggestion, how about calling this function
differently, e.g.
bool landlock_any_access_rights_handled(
const struct landlock_ruleset *const domain,
struct access_masks masks);
Then the callers who previously did
return landlock_filter_access_masks(dom, masks);
would now do
if (landlock_any_access_rights_handled(dom, masks))
return dom;
return NULL;
This is more verbose, but IMHO verbose code is not inherently bad,
if it is also clearer. And it's only two lines more.
> +{
> + if (!domain)
> + return NULL;
> +
> + if (landlock_merge_access_masks(domain).all & masks.all)
> + return domain;
> +
> + return NULL;
> +}
Function documentation for both functions would be good :)
> +
> static inline void
> landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
> const access_mask_t fs_access_mask,
> @@ -295,19 +327,12 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
> ruleset->access_masks[layer_level].scope |= mask;
> }
>
> -static inline access_mask_t
> -landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
> - const u16 layer_level)
> -{
> - return ruleset->access_masks[layer_level].fs;
> -}
> -
> static inline access_mask_t
> landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> const u16 layer_level)
> {
> /* Handles all initially denied by default access rights. */
> - return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
> + return ruleset->access_masks[layer_level].fs |
> LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> }
>
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index f5a0e7182ec0..c097d356fa45 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -329,7 +329,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
> return -ENOMSG;
>
> /* Checks that allowed_access matches the @ruleset constraints. */
> - mask = landlock_get_raw_fs_access_mask(ruleset, 0);
> + mask = ruleset->access_masks[0].fs;
> if ((path_beneath_attr.allowed_access | mask) != mask)
> return -EINVAL;
>
> --
> 2.46.1
>
Reviewed-by: Günther Noack <gnoack3000@gmail.com>
–Günther
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v1 1/3] landlock: Refactor filesystem access mask management
2024-10-05 16:57 ` Günther Noack
@ 2024-10-07 13:00 ` Mickaël Salaün
2024-10-10 9:10 ` Mickaël Salaün
0 siblings, 1 reply; 7+ messages in thread
From: Mickaël Salaün @ 2024-10-07 13:00 UTC (permalink / raw)
To: Günther Noack
Cc: Günther Noack, Mikhail Ivanov, Konstantin Meskhidze,
Paul Moore, Tahera Fahimi, linux-kernel, linux-security-module
On Sat, Oct 05, 2024 at 06:57:55PM +0200, Günther Noack wrote:
> On Tue, Oct 01, 2024 at 04:12:32PM +0200, Mickaël Salaün wrote:
> > Replace get_raw_handled_fs_accesses() with a generic
> > landlock_merge_access_masks(), and replace the get_fs_domain()
> > implementation with a call to the new landlock_filter_access_masks()
> > helper. These helpers will also be useful for other types of access.
> >
> > Replace struct access_masks with union access_masks that includes a new
> > "all" field to simplify mask filtering.
> >
> > Cc: Günther Noack <gnoack@google.com>
> > Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > Link: https://lore.kernel.org/r/20241001141234.397649-2-mic@digikod.net
> > ---
> > security/landlock/fs.c | 21 ++++-----------
> > security/landlock/ruleset.h | 51 +++++++++++++++++++++++++++---------
> > security/landlock/syscalls.c | 2 +-
> > 3 files changed, 44 insertions(+), 30 deletions(-)
> >
> > diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> > index 7d79fc8abe21..a2ef7d151c81 100644
> > --- a/security/landlock/fs.c
> > +++ b/security/landlock/fs.c
> > @@ -388,33 +388,22 @@ static bool is_nouser_or_private(const struct dentry *dentry)
> > unlikely(IS_PRIVATE(d_backing_inode(dentry))));
> > }
> >
> > -static access_mask_t
> > -get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
> > -{
> > - access_mask_t access_dom = 0;
> > - size_t layer_level;
> > -
> > - for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> > - access_dom |=
> > - landlock_get_raw_fs_access_mask(domain, layer_level);
> > - return access_dom;
> > -}
> > -
> > static access_mask_t
> > get_handled_fs_accesses(const struct landlock_ruleset *const domain)
> > {
> > /* Handles all initially denied by default access rights. */
> > - return get_raw_handled_fs_accesses(domain) |
> > + return landlock_merge_access_masks(domain).fs |
> > LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> > }
> >
> > static const struct landlock_ruleset *
> > get_fs_domain(const struct landlock_ruleset *const domain)
> > {
> > - if (!domain || !get_raw_handled_fs_accesses(domain))
> > - return NULL;
> > + const union access_masks all_fs = {
> > + .fs = ~0,
> > + };
> >
> > - return domain;
> > + return landlock_filter_access_masks(domain, all_fs);
> > }
> >
> > static const struct landlock_ruleset *get_current_fs_domain(void)
> > diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> > index 61bdbc550172..a816042ca8f3 100644
> > --- a/security/landlock/ruleset.h
> > +++ b/security/landlock/ruleset.h
> > @@ -41,12 +41,19 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
> > static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
> >
> > /* Ruleset access masks. */
> > -struct access_masks {
> > - access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> > - access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> > - access_mask_t scope : LANDLOCK_NUM_SCOPE;
> > +union access_masks {
> > + struct {
> > + access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> > + access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> > + access_mask_t scope : LANDLOCK_NUM_SCOPE;
> > + };
> > + u32 all;
> > };
>
> More of a style remark:
>
> I wonder whether it is worth turning this into a union.
>
> If this is for performance, I do not think is buys you much. With
> optimization enabled, it does not make much of a difference whether
> you are doing the & on .all or whether you are doing it on the
> individual fields. (I tried it out with gcc. The only difference is
> that the & on the individual fields will at the end mask only the bits
> that belong to these fields.)
This is not about performance but about maintainability and simplicity
(to avoid future changes/errors). Indeed, with this "all" field we
don't need to update (or forget to update) the
landlock_merge_access_masks() helper. This function can be simple and
generic to be used in the fs.c, net.c, and scope.c files.
>
> At the same time, in most places where struct access_masks is used,
> the union is not necessary and might add to the confusion.
I think it should not be an issue, and it leverages the advantages of
the previous access_masks_t with the ones of struct access_masks.
>
>
> >
> > +/* Makes sure all fields are covered. */
> > +static_assert(sizeof(((union access_masks *)NULL)->all) ==
> > + sizeof(union access_masks));
> > +
> > typedef u16 layer_mask_t;
> > /* Makes sure all layers can be checked. */
> > static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> > @@ -229,7 +236,7 @@ struct landlock_ruleset {
> > * layers are set once and never changed for the
> > * lifetime of the ruleset.
> > */
> > - struct access_masks access_masks[];
> > + union access_masks access_masks[];
> > };
> > };
> > };
> > @@ -260,6 +267,31 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
> > refcount_inc(&ruleset->usage);
> > }
> >
> > +static inline union access_masks
> > +landlock_merge_access_masks(const struct landlock_ruleset *const domain)
> > +{
> > + size_t layer_level;
> > + union access_masks matches = {};
> > +
> > + for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> > + matches.all |= domain->access_masks[layer_level].all;
> > +
> > + return matches;
> > +}
> > +
> > +static inline const struct landlock_ruleset *
> > +landlock_filter_access_masks(const struct landlock_ruleset *const domain,
> > + const union access_masks masks)
>
> With this function name, the return type of this function is
> unintuitive to me. Judging by the name, I would have expected a
> function that returns a "access_masks" value as well, similar to the
> function one above (the remaining access rights after filtering)?
Fair
>
> In the places where the result of this function is returned directly,
> I find myself jumping back to the function implementation to
> understand what this means.
>
> As a constructive suggestion, how about calling this function
> differently, e.g.
>
> bool landlock_any_access_rights_handled(
> const struct landlock_ruleset *const domain,
> struct access_masks masks);
>
> Then the callers who previously did
>
> return landlock_filter_access_masks(dom, masks);
>
> would now do
>
> if (landlock_any_access_rights_handled(dom, masks))
> return dom;
> return NULL;
I'm not sure if you're suggesting to return an union access_masks or a
landlock_ruleset pointer. Returning a ruleset/domain simplifies the
work of callers so I'd prefer to keep that.
The "_any_access_rights_handled" doesn't have a verb, and it's not clear
to me if it would return the handled access rights or something else.
What about renaming it landlock_mask_ruleset(dom, access_masks) instead?
For now, the variables named "domain" points to struct landlock_ruleset,
but they will eventually point to a future struct landlock_domain. So,
I prefer to keep the name "ruleset" in helpers dealing with struct
landlock_ruleset. We'll need to change these helpers when we'll switch
to landlock_domain anyway.
>
> This is more verbose, but IMHO verbose code is not inherently bad,
> if it is also clearer. And it's only two lines more.
>
> > +{
> > + if (!domain)
> > + return NULL;
> > +
> > + if (landlock_merge_access_masks(domain).all & masks.all)
> > + return domain;
> > +
> > + return NULL;
> > +}
>
> Function documentation for both functions would be good :)
Indeed :)
>
> > +
> > static inline void
> > landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
> > const access_mask_t fs_access_mask,
> > @@ -295,19 +327,12 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
> > ruleset->access_masks[layer_level].scope |= mask;
> > }
> >
> > -static inline access_mask_t
> > -landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
> > - const u16 layer_level)
> > -{
> > - return ruleset->access_masks[layer_level].fs;
> > -}
> > -
> > static inline access_mask_t
> > landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
> > const u16 layer_level)
> > {
> > /* Handles all initially denied by default access rights. */
> > - return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
> > + return ruleset->access_masks[layer_level].fs |
> > LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> > }
> >
> > diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> > index f5a0e7182ec0..c097d356fa45 100644
> > --- a/security/landlock/syscalls.c
> > +++ b/security/landlock/syscalls.c
> > @@ -329,7 +329,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
> > return -ENOMSG;
> >
> > /* Checks that allowed_access matches the @ruleset constraints. */
> > - mask = landlock_get_raw_fs_access_mask(ruleset, 0);
> > + mask = ruleset->access_masks[0].fs;
> > if ((path_beneath_attr.allowed_access | mask) != mask)
> > return -EINVAL;
> >
> > --
> > 2.46.1
> >
>
> Reviewed-by: Günther Noack <gnoack3000@gmail.com>
>
> –Günther
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v1 1/3] landlock: Refactor filesystem access mask management
2024-10-07 13:00 ` Mickaël Salaün
@ 2024-10-10 9:10 ` Mickaël Salaün
0 siblings, 0 replies; 7+ messages in thread
From: Mickaël Salaün @ 2024-10-10 9:10 UTC (permalink / raw)
To: Günther Noack
Cc: Günther Noack, Mikhail Ivanov, Konstantin Meskhidze,
Paul Moore, Tahera Fahimi, linux-kernel, linux-security-module,
Matthieu Buffet
On Mon, Oct 07, 2024 at 03:00:34PM +0200, Mickaël Salaün wrote:
> On Sat, Oct 05, 2024 at 06:57:55PM +0200, Günther Noack wrote:
> > On Tue, Oct 01, 2024 at 04:12:32PM +0200, Mickaël Salaün wrote:
> > > Replace get_raw_handled_fs_accesses() with a generic
> > > landlock_merge_access_masks(), and replace the get_fs_domain()
> > > implementation with a call to the new landlock_filter_access_masks()
> > > helper. These helpers will also be useful for other types of access.
> > >
> > > Replace struct access_masks with union access_masks that includes a new
> > > "all" field to simplify mask filtering.
> > >
> > > Cc: Günther Noack <gnoack@google.com>
> > > Cc: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> > > Signed-off-by: Mickaël Salaün <mic@digikod.net>
> > > Link: https://lore.kernel.org/r/20241001141234.397649-2-mic@digikod.net
> > > ---
> > > security/landlock/fs.c | 21 ++++-----------
> > > security/landlock/ruleset.h | 51 +++++++++++++++++++++++++++---------
> > > security/landlock/syscalls.c | 2 +-
> > > 3 files changed, 44 insertions(+), 30 deletions(-)
> > >
> > > diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> > > index 7d79fc8abe21..a2ef7d151c81 100644
> > > --- a/security/landlock/fs.c
> > > +++ b/security/landlock/fs.c
> > > @@ -388,33 +388,22 @@ static bool is_nouser_or_private(const struct dentry *dentry)
> > > unlikely(IS_PRIVATE(d_backing_inode(dentry))));
> > > }
> > >
> > > -static access_mask_t
> > > -get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
> > > -{
> > > - access_mask_t access_dom = 0;
> > > - size_t layer_level;
> > > -
> > > - for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> > > - access_dom |=
> > > - landlock_get_raw_fs_access_mask(domain, layer_level);
> > > - return access_dom;
> > > -}
> > > -
> > > static access_mask_t
> > > get_handled_fs_accesses(const struct landlock_ruleset *const domain)
> > > {
> > > /* Handles all initially denied by default access rights. */
> > > - return get_raw_handled_fs_accesses(domain) |
> > > + return landlock_merge_access_masks(domain).fs |
> > > LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
> > > }
> > >
> > > static const struct landlock_ruleset *
> > > get_fs_domain(const struct landlock_ruleset *const domain)
> > > {
> > > - if (!domain || !get_raw_handled_fs_accesses(domain))
> > > - return NULL;
> > > + const union access_masks all_fs = {
> > > + .fs = ~0,
> > > + };
> > >
> > > - return domain;
> > > + return landlock_filter_access_masks(domain, all_fs);
> > > }
> > >
> > > static const struct landlock_ruleset *get_current_fs_domain(void)
> > > diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> > > index 61bdbc550172..a816042ca8f3 100644
> > > --- a/security/landlock/ruleset.h
> > > +++ b/security/landlock/ruleset.h
> > > @@ -41,12 +41,19 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
> > > static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
> > >
> > > /* Ruleset access masks. */
> > > -struct access_masks {
> > > - access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> > > - access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> > > - access_mask_t scope : LANDLOCK_NUM_SCOPE;
> > > +union access_masks {
> > > + struct {
> > > + access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
> > > + access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
> > > + access_mask_t scope : LANDLOCK_NUM_SCOPE;
> > > + };
> > > + u32 all;
> > > };
> >
> > More of a style remark:
> >
> > I wonder whether it is worth turning this into a union.
> >
> > If this is for performance, I do not think is buys you much. With
> > optimization enabled, it does not make much of a difference whether
> > you are doing the & on .all or whether you are doing it on the
> > individual fields. (I tried it out with gcc. The only difference is
> > that the & on the individual fields will at the end mask only the bits
> > that belong to these fields.)
>
> This is not about performance but about maintainability and simplicity
> (to avoid future changes/errors). Indeed, with this "all" field we
> don't need to update (or forget to update) the
> landlock_merge_access_masks() helper. This function can be simple and
> generic to be used in the fs.c, net.c, and scope.c files.
>
> >
> > At the same time, in most places where struct access_masks is used,
> > the union is not necessary and might add to the confusion.
>
> I think it should not be an issue, and it leverages the advantages of
> the previous access_masks_t with the ones of struct access_masks.
>
> >
> >
> > >
> > > +/* Makes sure all fields are covered. */
> > > +static_assert(sizeof(((union access_masks *)NULL)->all) ==
> > > + sizeof(union access_masks));
> > > +
> > > typedef u16 layer_mask_t;
> > > /* Makes sure all layers can be checked. */
> > > static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> > > @@ -229,7 +236,7 @@ struct landlock_ruleset {
> > > * layers are set once and never changed for the
> > > * lifetime of the ruleset.
> > > */
> > > - struct access_masks access_masks[];
> > > + union access_masks access_masks[];
> > > };
> > > };
> > > };
> > > @@ -260,6 +267,31 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
> > > refcount_inc(&ruleset->usage);
> > > }
> > >
> > > +static inline union access_masks
> > > +landlock_merge_access_masks(const struct landlock_ruleset *const domain)
> > > +{
> > > + size_t layer_level;
> > > + union access_masks matches = {};
> > > +
> > > + for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
> > > + matches.all |= domain->access_masks[layer_level].all;
> > > +
> > > + return matches;
> > > +}
> > > +
> > > +static inline const struct landlock_ruleset *
> > > +landlock_filter_access_masks(const struct landlock_ruleset *const domain,
> > > + const union access_masks masks)
> >
> > With this function name, the return type of this function is
> > unintuitive to me. Judging by the name, I would have expected a
> > function that returns a "access_masks" value as well, similar to the
> > function one above (the remaining access rights after filtering)?
>
> Fair
>
> >
> > In the places where the result of this function is returned directly,
> > I find myself jumping back to the function implementation to
> > understand what this means.
> >
> > As a constructive suggestion, how about calling this function
> > differently, e.g.
> >
> > bool landlock_any_access_rights_handled(
> > const struct landlock_ruleset *const domain,
> > struct access_masks masks);
> >
> > Then the callers who previously did
> >
> > return landlock_filter_access_masks(dom, masks);
> >
> > would now do
> >
> > if (landlock_any_access_rights_handled(dom, masks))
> > return dom;
> > return NULL;
>
> I'm not sure if you're suggesting to return an union access_masks or a
> landlock_ruleset pointer. Returning a ruleset/domain simplifies the
> work of callers so I'd prefer to keep that.
>
> The "_any_access_rights_handled" doesn't have a verb, and it's not clear
> to me if it would return the handled access rights or something else.
>
> What about renaming it landlock_mask_ruleset(dom, access_masks) instead?
Thinking more about it, using "mask" could mean that the access_masks
argument will indeed mask and we'll get the oposite. What about
landlock_match_ruleset()?
>
> For now, the variables named "domain" points to struct landlock_ruleset,
> but they will eventually point to a future struct landlock_domain. So,
> I prefer to keep the name "ruleset" in helpers dealing with struct
> landlock_ruleset. We'll need to change these helpers when we'll switch
> to landlock_domain anyway.
>
> >
> > This is more verbose, but IMHO verbose code is not inherently bad,
> > if it is also clearer. And it's only two lines more.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2024-10-10 9:10 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-10-01 14:12 [PATCH v1 0/3] Refactor Landlock access mask management Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 1/3] landlock: Refactor filesystem " Mickaël Salaün
2024-10-05 16:57 ` Günther Noack
2024-10-07 13:00 ` Mickaël Salaün
2024-10-10 9:10 ` Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 2/3] landlock: Refactor network " Mickaël Salaün
2024-10-01 14:12 ` [PATCH v1 3/3] landlock: Optimize scope enforcement Mickaël Salaün
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.