Re: [RFC PATCH v3 3/3] devguard: added device guard for mknod in non-initial userns

From: Michael Weiß
Date: Wed Dec 27 2023 - 09:33:25 EST


On 23.12.23 00:39, Paul Moore wrote:
> On Mon, Dec 18, 2023 at 7:30 AM Christian Brauner <brauner@xxxxxxxxxx> wrote:
>> I'm not generally opposed to kfuncs ofc but here it just seems a bit
>> pointless. What we want is to keep SB_I_{NODEV,MANAGED_DEVICES} confined
>> to alloc_super(). The only central place it's raised where we control
>> all locking and logic. So it doesn't even have to appear in any
>> security_*() hooks.
>>
>> diff --git a/security/security.c b/security/security.c
>> index 088a79c35c26..bf440d15615d 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -1221,6 +1221,33 @@ int security_sb_alloc(struct super_block *sb)
>> return rc;
>> }
>>
>> +/*
>> + * security_sb_device_access() - Let LSMs handle device access
>> + * @sb: filesystem superblock
>> + *
>> + * Let an LSM take over device access management for this superblock.
>> + *
>> + * Return: Returns 1 if LSMs handle device access, 0 if none does and -ERRNO on
>> + * failure.
>> + */
>> +int security_sb_device_access(struct super_block *sb)
>> +{
>> + int thisrc;
>> + int rc = LSM_RET_DEFAULT(sb_device_access);
>> + struct security_hook_list *hp;
>> +
>> + hlist_for_each_entry(hp, &security_hook_heads.sb_device_access, list) {
>> + thisrc = hp->hook.sb_device_access(sb);
>> + if (thisrc < 0)
>> + return thisrc;
>> + /* At least one LSM claimed device access management. */
>> + if (thisrc == 1)
>> + rc = 1;
>> + }
>> +
>> + return rc;
>> +}
>
> I worry that this hook, and the way it is plumbed into alloc_super()
> below, brings us back to the problem of authoritative LSM hooks which
> is something I can't support at this point in time. The same can be
> said for a LSM directly flipping bits in the superblock struct.
>
> The LSM should not grant any additional privilege, either directly in
> the LSM code, or indirectly via the caller; the LSM should only
> restrict operations which would have otherwise been allowed.
>
> The LSM should also refrain from modifying any kernel data structures
> that do not belong directly to the LSM. A LSM caller may modify
> kernel data structures that it owns based on the result of the LSM
> hook, so long as those modifications do not grant additional privilege
> as described above.

Hi Paul, what would you think about if we do it as shown in the
patch below (untested)?

I have adapted Christians patch slightly in a way that we do let
all LSMs agree on if device access management should be done or not.
Similar to the security_task_prctl() hook.

The new default behavior in alloc_super() is that the
SB_I_MANANGED_DEVICES flag is set if no error is returned by the
security hook, otherwise the old semantic which raises SB_I_NODEV
is used if an LSM does not agree on device management for the
superblock.

So a LSM can only be used to restrict access to the superblock
but not to do more privileged operations, since the
MANAGED_DEVICES would be allowed by default.

The LSM default value is set to -EOPNOSUPP to decide if an LSM has
implemented the hook inside of fs/super.c for backward compatibility.

>
>> diff --git a/fs/super.c b/fs/super.c
>> index 076392396e72..2295c0f76e56 100644
>> --- a/fs/super.c
>> +++ b/fs/super.c
>> @@ -325,7 +325,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
>> {
>> struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER);
>> static const struct super_operations default_op;
>> - int i;
>> + int err, i;
>>
>> if (!s)
>> return NULL;
>> @@ -362,8 +362,16 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
>> }
>> s->s_bdi = &noop_backing_dev_info;
>> s->s_flags = flags;
>> - if (s->s_user_ns != &init_user_ns)
>> +
>> + err = security_sb_device_access(s);
>> + if (err < 0)
>> + goto fail;
>> +
>> + if (err)
>> + s->s_iflags |= SB_I_MANAGED_DEVICES;
>> + else if (s->s_user_ns != &init_user_ns)
>> s->s_iflags |= SB_I_NODEV;
>> +
>> INIT_HLIST_NODE(&s->s_instances);
>> INIT_HLIST_BL_HEAD(&s->s_roots);
>> mutex_init(&s->s_sync_lock);
>
---
fs/namespace.c | 11 +++++++----
fs/super.c | 12 ++++++++++--
include/linux/fs.h | 1 +
include/linux/lsm_hook_defs.h | 1 +
include/linux/security.h | 6 ++++++
security/security.c | 26 ++++++++++++++++++++++++++
6 files changed, 51 insertions(+), 6 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index fbf0e596fcd3..e87cc0320091 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4887,7 +4887,6 @@ static bool mnt_already_visible(struct mnt_namespace *ns,

static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
{
- const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
struct mnt_namespace *ns = current->nsproxy->mnt_ns;
unsigned long s_iflags;

@@ -4899,9 +4898,13 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
if (!(s_iflags & SB_I_USERNS_VISIBLE))
return false;

- if ((s_iflags & required_iflags) != required_iflags) {
- WARN_ONCE(1, "Expected s_iflags to contain 0x%lx\n",
- required_iflags);
+ if (!(s_iflags & SB_I_NOEXEC)) {
+ WARN_ONCE(1, "Expected s_iflags to contain SB_I_NOEXEC\n");
+ return true;
+ }
+
+ if (!(s_iflags & (SB_I_NODEV | SB_I_MANAGED_DEVICES))) {
+ WARN_ONCE(1, "Expected s_iflags to contain device access mask\n");
return true;
}

diff --git a/fs/super.c b/fs/super.c
index 076392396e72..6510168d51ce 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -325,7 +325,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
{
struct super_block *s = kzalloc(sizeof(struct super_block), GFP_USER);
static const struct super_operations default_op;
- int i;
+ int i, err;

if (!s)
return NULL;
@@ -362,8 +362,16 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
}
s->s_bdi = &noop_backing_dev_info;
s->s_flags = flags;
- if (s->s_user_ns != &init_user_ns)
+
+ err = security_sb_device_access(s);
+ if (err < 0 && err != -EOPNOTSUPP)
+ goto fail;
+
+ if (err && s->s_user_ns != &init_user_ns)
s->s_iflags |= SB_I_NODEV;
+ else
+ s->s_iflags |= SB_I_MANAGED_DEVICES;
+
INIT_HLIST_NODE(&s->s_instances);
INIT_HLIST_BL_HEAD(&s->s_roots);
mutex_init(&s->s_sync_lock);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 98b7a7a8c42e..6ca0fe922478 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1164,6 +1164,7 @@ extern int send_sigurg(struct fown_struct *fown);
#define SB_I_USERNS_VISIBLE 0x00000010 /* fstype already mounted */
#define SB_I_IMA_UNVERIFIABLE_SIGNATURE 0x00000020
#define SB_I_UNTRUSTED_MOUNTER 0x00000040
+#define SB_I_MANAGED_DEVICES 0x00000080

#define SB_I_SKIP_SYNC 0x00000100 /* Skip superblock at global sync */
#define SB_I_PERSB_BDI 0x00000200 /* has a per-sb bdi */
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index ff217a5ce552..1dc13b7e9a4a 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -60,6 +60,7 @@ LSM_HOOK(int, 0, fs_context_dup, struct fs_context *fc,
LSM_HOOK(int, -ENOPARAM, fs_context_parse_param, struct fs_context *fc,
struct fs_parameter *param)
LSM_HOOK(int, 0, sb_alloc_security, struct super_block *sb)
+LSM_HOOK(int, -EOPNOTSUPP, sb_device_access, struct super_block *sb)
LSM_HOOK(void, LSM_RET_VOID, sb_delete, struct super_block *sb)
LSM_HOOK(void, LSM_RET_VOID, sb_free_security, struct super_block *sb)
LSM_HOOK(void, LSM_RET_VOID, sb_free_mnt_opts, void *mnt_opts)
diff --git a/include/linux/security.h b/include/linux/security.h
index 1d1df326c881..79f2b201c7bd 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -298,6 +298,7 @@ int security_fs_context_submount(struct fs_context *fc, struct super_block *refe
int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc);
int security_fs_context_parse_param(struct fs_context *fc, struct fs_parameter *param);
int security_sb_alloc(struct super_block *sb);
+int security_sb_device_access(struct super_block *sb);
void security_sb_delete(struct super_block *sb);
void security_sb_free(struct super_block *sb);
void security_free_mnt_opts(void **mnt_opts);
@@ -652,6 +653,11 @@ static inline int security_sb_alloc(struct super_block *sb)
return 0;
}

+static inline int security_sb_device_access(struct super_block *sb)
+{
+ return -EOPNOTSUPP;
+}
+
static inline void security_sb_delete(struct super_block *sb)
{ }

diff --git a/security/security.c b/security/security.c
index dcb3e7014f9b..054ee19edef0 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1221,6 +1221,32 @@ int security_sb_alloc(struct super_block *sb)
return rc;
}

+/*
+ * security_sb_device_access() - handle device access on this sb
+ * @sb: filesystem superblock
+ *
+ * Let LSMs decide if device access management is allowed for this superblock.
+ *
+ * Return: Returns 0 if LSMs handle device access, -EOPNOTSUPP if none does and
+ * -ERRNO on any other failure.
+ */
+int security_sb_device_access(struct super_block *sb)
+{
+ int thisrc;
+ int rc = LSM_RET_DEFAULT(sb_device_access);
+ struct security_hook_list *hp;
+
+ hlist_for_each_entry(hp, &security_hook_heads.sb_device_access, list) {
+ thisrc = hp->hook.sb_device_access(sb);
+ if (thisrc != LSM_RET_DEFAULT(sb_device_access)) {
+ rc = thisrc;
+ if (thisrc != 0)
+ break;
+ }
+ }
+ return rc;
+}
+
/**
* security_sb_delete() - Release super_block LSM associated objects
* @sb: filesystem superblock
--