On Wed, Mar 18, 2026 at 2:44 PM Song Liu <[email protected]> wrote:
>
> Add six new LSM hooks for mount operations:
>
> - mount_bind(from, to, recurse): bind mount with pre-resolved
>   struct path for source and destination.
> - mount_new(fc, mp, mnt_flags, flags, data): new mount, called after
>   mount options are parsed. The flags and data parameters carry the
>   original mount(2) flags and data for LSMs that need them (AppArmor,
>   Tomoyo).
> - mount_remount(fc, mp, mnt_flags, flags, data): filesystem remount,
>   called after mount options are parsed into the fs_context.
> - mount_reconfigure(mp, mnt_flags, flags): mount flag reconfiguration
>   (MS_REMOUNT|MS_BIND path).
> - mount_move(from, to): move mount with pre-resolved paths.
> - mount_change_type(mp, ms_flags): propagation type changes.
>
> These replace the monolithic security_sb_mount() which conflates
> multiple distinct operations into a single hook, and suffers from
> TOCTOU issues where LSMs re-resolve string-based dev_name via
> kern_path().
>
> The mount_move hook is added alongside the existing move_mount hook.
> During the transition, LSMs register for both hooks. The move_mount
> hook will be removed once all LSMs have been converted.
>
> Some LSMs, such as apparmor and tomoyo, audit the original input passed
> in the mount syscall. To keep the same behavior, argument data and flags
> are passed in do_* functions. These can be removed if these LSMs no
> longer need these information.
>
> All new hooks are registered as sleepable BPF LSM hooks.
>
> Code generated with the assistance of Claude, reviewed by human.
>
> Signed-off-by: Song Liu <[email protected]>

Reviewed-by: Stephen Smalley <[email protected]>
Tested-by: Stephen Smalley <[email protected] # for selinux only

> ---
>  fs/namespace.c                |  35 ++++++++++--
>  include/linux/lsm_hook_defs.h |  12 ++++
>  include/linux/security.h      |  50 +++++++++++++++++
>  kernel/bpf/bpf_lsm.c          |   7 +++
>  security/security.c           | 101 ++++++++++++++++++++++++++++++++++
>  5 files changed, 199 insertions(+), 6 deletions(-)
>
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 854f4fc66469..de33070e514a 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -2875,6 +2875,10 @@ static int do_change_type(const struct path *path, int 
> ms_flags)
>         if (!type)
>                 return -EINVAL;
>
> +       err = security_mount_change_type(path, ms_flags);
> +       if (err)
> +               return err;
> +
>         guard(namespace_excl)();
>
>         err = may_change_propagation(mnt);
> @@ -3007,6 +3011,10 @@ static int do_loopback(const struct path *path, const 
> char *old_name,
>         if (err)
>                 return err;
>
> +       err = security_mount_bind(&old_path, path, recurse);
> +       if (err)
> +               return err;
> +
>         if (mnt_ns_loop(old_path.dentry))
>                 return -EINVAL;
>
> @@ -3319,7 +3327,8 @@ static void mnt_warn_timestamp_expiry(const struct path 
> *mountpoint,
>   * superblock it refers to.  This is triggered by specifying 
> MS_REMOUNT|MS_BIND
>   * to mount(2).
>   */
> -static int do_reconfigure_mnt(const struct path *path, unsigned int 
> mnt_flags)
> +static int do_reconfigure_mnt(const struct path *path, unsigned int 
> mnt_flags,
> +                             unsigned long flags)
>  {
>         struct super_block *sb = path->mnt->mnt_sb;
>         struct mount *mnt = real_mount(path->mnt);
> @@ -3334,6 +3343,10 @@ static int do_reconfigure_mnt(const struct path *path, 
> unsigned int mnt_flags)
>         if (!can_change_locked_flags(mnt, mnt_flags))
>                 return -EPERM;
>
> +       ret = security_mount_reconfigure(path, mnt_flags, flags);
> +       if (ret)
> +               return ret;
> +
>         /*
>          * We're only checking whether the superblock is read-only not
>          * changing it, so only take down_read(&sb->s_umount).
> @@ -3357,7 +3370,7 @@ static int do_reconfigure_mnt(const struct path *path, 
> unsigned int mnt_flags)
>   * on it - tough luck.
>   */
>  static int do_remount(const struct path *path, int sb_flags,
> -                     int mnt_flags, void *data)
> +                     int mnt_flags, void *data, unsigned long flags)
>  {
>         int err;
>         struct super_block *sb = path->mnt->mnt_sb;
> @@ -3384,6 +3397,9 @@ static int do_remount(const struct path *path, int 
> sb_flags,
>         fc->oldapi = true;
>
>         err = parse_monolithic_mount_data(fc, data);
> +       if (!err)
> +               err = security_mount_remount(fc, path, mnt_flags, flags,
> +                                           data);
>         if (!err) {
>                 down_write(&sb->s_umount);
>                 err = -EPERM;
> @@ -3713,6 +3729,10 @@ static int do_move_mount_old(const struct path *path, 
> const char *old_name)
>         if (err)
>                 return err;
>
> +       err = security_mount_move(&old_path, path);
> +       if (err)
> +               return err;
> +
>         return do_move_mount(&old_path, path, 0);
>  }
>
> @@ -3791,7 +3811,7 @@ static int do_new_mount_fc(struct fs_context *fc, const 
> struct path *mountpoint,
>   */
>  static int do_new_mount(const struct path *path, const char *fstype,
>                         int sb_flags, int mnt_flags,
> -                       const char *name, void *data)
> +                       const char *name, void *data, unsigned long flags)
>  {
>         struct file_system_type *type;
>         struct fs_context *fc;
> @@ -3835,6 +3855,9 @@ static int do_new_mount(const struct path *path, const 
> char *fstype,
>                 err = parse_monolithic_mount_data(fc, data);
>         if (!err && !mount_capable(fc))
>                 err = -EPERM;
> +
> +       if (!err)
> +               err = security_mount_new(fc, path, mnt_flags, flags, data);
>         if (!err)
>                 err = do_new_mount_fc(fc, path, mnt_flags);
>
> @@ -4146,9 +4169,9 @@ int path_mount(const char *dev_name, const struct path 
> *path,
>                             SB_I_VERSION);
>
>         if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
> -               return do_reconfigure_mnt(path, mnt_flags);
> +               return do_reconfigure_mnt(path, mnt_flags, flags);
>         if (flags & MS_REMOUNT)
> -               return do_remount(path, sb_flags, mnt_flags, data_page);
> +               return do_remount(path, sb_flags, mnt_flags, data_page, 
> flags);
>         if (flags & MS_BIND)
>                 return do_loopback(path, dev_name, flags & MS_REC);
>         if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
> @@ -4157,7 +4180,7 @@ int path_mount(const char *dev_name, const struct path 
> *path,
>                 return do_move_mount_old(path, dev_name);
>
>         return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
> -                           data_page);
> +                           data_page, flags);
>  }
>
>  int do_mount(const char *dev_name, const char __user *dir_name,
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 8c42b4bde09c..6bb67059fb43 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -81,6 +81,18 @@ LSM_HOOK(int, 0, sb_clone_mnt_opts, const struct 
> super_block *oldsb,
>          unsigned long *set_kern_flags)
>  LSM_HOOK(int, 0, move_mount, const struct path *from_path,
>          const struct path *to_path)
> +LSM_HOOK(int, 0, mount_bind, const struct path *from, const struct path *to,
> +        bool recurse)
> +LSM_HOOK(int, 0, mount_new, struct fs_context *fc, const struct path *mp,
> +        int mnt_flags, unsigned long flags, void *data)
> +LSM_HOOK(int, 0, mount_remount, struct fs_context *fc,
> +        const struct path *mp, int mnt_flags, unsigned long flags,
> +        void *data)
> +LSM_HOOK(int, 0, mount_reconfigure, const struct path *mp,
> +        unsigned int mnt_flags, unsigned long flags)
> +LSM_HOOK(int, 0, mount_move, const struct path *from_path,
> +        const struct path *to_path)
> +LSM_HOOK(int, 0, mount_change_type, const struct path *mp, int ms_flags)
>  LSM_HOOK(int, -EOPNOTSUPP, dentry_init_security, struct dentry *dentry,
>          int mode, const struct qstr *name, const char **xattr_name,
>          struct lsm_context *cp)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 83a646d72f6f..6e31de9b3d68 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -385,6 +385,17 @@ int security_sb_clone_mnt_opts(const struct super_block 
> *oldsb,
>                                 unsigned long kern_flags,
>                                 unsigned long *set_kern_flags);
>  int security_move_mount(const struct path *from_path, const struct path 
> *to_path);
> +int security_mount_bind(const struct path *from, const struct path *to,
> +                       bool recurse);
> +int security_mount_new(struct fs_context *fc, const struct path *mp,
> +                      int mnt_flags, unsigned long flags, void *data);
> +int security_mount_remount(struct fs_context *fc, const struct path *mp,
> +                          int mnt_flags, unsigned long flags, void *data);
> +int security_mount_reconfigure(const struct path *mp, unsigned int mnt_flags,
> +                              unsigned long flags);
> +int security_mount_move(const struct path *from_path,
> +                       const struct path *to_path);
> +int security_mount_change_type(const struct path *mp, int ms_flags);
>  int security_dentry_init_security(struct dentry *dentry, int mode,
>                                   const struct qstr *name,
>                                   const char **xattr_name,
> @@ -847,6 +858,45 @@ static inline int security_move_mount(const struct path 
> *from_path,
>         return 0;
>  }
>
> +static inline int security_mount_bind(const struct path *from,
> +                                     const struct path *to, bool recurse)
> +{
> +       return 0;
> +}
> +
> +static inline int security_mount_new(struct fs_context *fc,
> +                                    const struct path *mp, int mnt_flags,
> +                                    unsigned long flags, void *data)
> +{
> +       return 0;
> +}
> +
> +static inline int security_mount_remount(struct fs_context *fc,
> +                                        const struct path *mp, int mnt_flags,
> +                                        unsigned long flags, void *data)
> +{
> +       return 0;
> +}
> +
> +static inline int security_mount_reconfigure(const struct path *mp,
> +                                            unsigned int mnt_flags,
> +                                            unsigned long flags)
> +{
> +       return 0;
> +}
> +
> +static inline int security_mount_move(const struct path *from_path,
> +                                     const struct path *to_path)
> +{
> +       return 0;
> +}
> +
> +static inline int security_mount_change_type(const struct path *mp,
> +                                            int ms_flags)
> +{
> +       return 0;
> +}
> +
>  static inline int security_path_notify(const struct path *path, u64 mask,
>                                 unsigned int obj_type)
>  {
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 0c4a0c8e6f70..65235d70ee23 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -383,6 +383,13 @@ BTF_ID(func, bpf_lsm_task_prctl)
>  BTF_ID(func, bpf_lsm_task_setscheduler)
>  BTF_ID(func, bpf_lsm_task_to_inode)
>  BTF_ID(func, bpf_lsm_userns_create)
> +BTF_ID(func, bpf_lsm_move_mount)
> +BTF_ID(func, bpf_lsm_mount_bind)
> +BTF_ID(func, bpf_lsm_mount_new)
> +BTF_ID(func, bpf_lsm_mount_remount)
> +BTF_ID(func, bpf_lsm_mount_reconfigure)
> +BTF_ID(func, bpf_lsm_mount_move)
> +BTF_ID(func, bpf_lsm_mount_change_type)
>  BTF_SET_END(sleepable_lsm_hooks)
>
>  BTF_SET_START(untrusted_lsm_hooks)
> diff --git a/security/security.c b/security/security.c
> index 67af9228c4e9..356ef228d5de 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -1156,6 +1156,107 @@ int security_move_mount(const struct path *from_path,
>         return call_int_hook(move_mount, from_path, to_path);
>  }
>
> +/**
> + * security_mount_bind() - Check permissions for a bind mount
> + * @from: source path
> + * @to: destination mount point
> + * @recurse: whether this is a recursive bind mount
> + *
> + * Check permission before a bind mount is performed. Called with the
> + * source path already resolved, eliminating TOCTOU issues with
> + * string-based dev_name in security_sb_mount().
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mount_bind(const struct path *from, const struct path *to,
> +                       bool recurse)
> +{
> +       return call_int_hook(mount_bind, from, to, recurse);
> +}
> +
> +/**
> + * security_mount_new() - Check permissions for a new mount
> + * @fc: filesystem context with parsed options
> + * @mp: mount point path
> + * @mnt_flags: mount flags (MNT_*)
> + * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
> + * @data: filesystem specific data (used by AppArmor)
> + *
> + * Check permission before a new filesystem is mounted. Called after
> + * mount options are parsed, providing access to the fs_context.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mount_new(struct fs_context *fc, const struct path *mp,
> +                      int mnt_flags, unsigned long flags, void *data)
> +{
> +       return call_int_hook(mount_new, fc, mp, mnt_flags, flags, data);
> +}
> +
> +/**
> + * security_mount_remount() - Check permissions for a remount
> + * @fc: filesystem context with parsed options
> + * @mp: mount point path
> + * @mnt_flags: mount flags (MNT_*)
> + * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
> + * @data: filesystem specific data (used by AppArmor)
> + *
> + * Check permission before a filesystem is remounted. Called after
> + * mount options are parsed, providing access to the fs_context.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mount_remount(struct fs_context *fc, const struct path *mp,
> +                          int mnt_flags, unsigned long flags, void *data)
> +{
> +       return call_int_hook(mount_remount, fc, mp, mnt_flags, flags, data);
> +}
> +
> +/**
> + * security_mount_reconfigure() - Check permissions for mount reconfiguration
> + * @mp: mount point path
> + * @mnt_flags: new mount flags (MNT_*)
> + * @flags: original mount flags (MS_*, used by AppArmor/Tomoyo)
> + *
> + * Check permission before mount flags are reconfigured (MS_REMOUNT|MS_BIND).
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mount_reconfigure(const struct path *mp, unsigned int mnt_flags,
> +                              unsigned long flags)
> +{
> +       return call_int_hook(mount_reconfigure, mp, mnt_flags, flags);
> +}
> +
> +/**
> + * security_mount_move() - Check permissions for moving a mount
> + * @from_path: source mount path
> + * @to_path: destination mount point path
> + *
> + * Check permission before a mount is moved.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mount_move(const struct path *from_path,
> +                       const struct path *to_path)
> +{
> +       return call_int_hook(mount_move, from_path, to_path);
> +}
> +
> +/**
> + * security_mount_change_type() - Check permissions for propagation changes
> + * @mp: mount point path
> + * @ms_flags: propagation flags (MS_SHARED, MS_PRIVATE, etc.)
> + *
> + * Check permission before mount propagation type is changed.
> + *
> + * Return: Returns 0 if permission is granted.
> + */
> +int security_mount_change_type(const struct path *mp, int ms_flags)
> +{
> +       return call_int_hook(mount_change_type, mp, ms_flags);
> +}
> +
>  /**
>   * security_path_notify() - Check if setting a watch is allowed
>   * @path: file path
> --
> 2.52.0
>

Reply via email to