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 >
