Add LSM hooks for use by the superblock configuration context code. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- include/linux/lsm_hooks.h | 39 ++++++++++ include/linux/security.h | 28 +++++++ security/security.c | 25 +++++++ security/selinux/hooks.c | 169 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 080f34e66017..c2bbd9e92b0a 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -75,6 +75,34 @@ * should enable secure mode. * @bprm contains the linux_binprm structure. * + * Security hooks for mount using fd context. + * + * @sb_config_alloc: + * Allocate and attach a security structure to sc->security. This pointer + * is initialised to NULL by the caller. + * @sc indicates the new superblock configuration context. + * @src_sb indicates the source superblock of a submount. + * @sb_config_dup: + * Allocate and attach a security structure to sc->security. This pointer + * is initialised to NULL by the caller. + * @sc indicates the new superblock configuration context. + * @src_sc indicates the original superblock configuration context. + * @sb_config_free: + * Clean up a superblock configuration context. + * @sc indicates the superblock configuration context. + * @sb_config_parse_option: + * Userspace provided an option to configure a superblock. The LSM may + * reject it with an error and may use it for itself, in which case it + * should return 1; otherwise it should return 0 to pass it on to the + * filesystem. + * @sc indicates the superblock configuration context. + * @p indicates the option in "key[=val]" form. + * @sb_get_tree: + * Assign the security to a newly created superblock. + * @sc indicates the superblock configuration context. + * @sc->root indicates the root that will be mounted. + * @sc->root->d_sb points to the superblock. + * * Security hooks for filesystem operations. * * @sb_alloc_security: @@ -1372,6 +1400,12 @@ union security_list_options { void (*bprm_committing_creds)(struct linux_binprm *bprm); void (*bprm_committed_creds)(struct linux_binprm *bprm); + int (*sb_config_alloc)(struct sb_config *sc, struct super_block *src_sb); + int (*sb_config_dup)(struct sb_config *sc, struct sb_config *src_sc); + void (*sb_config_free)(struct sb_config *sc); + int (*sb_config_parse_option)(struct sb_config *sc, char *opt); + int (*sb_get_tree)(struct sb_config *sc); + int (*sb_alloc_security)(struct super_block *sb); void (*sb_free_security)(struct super_block *sb); int (*sb_copy_data)(char *orig, char *copy); @@ -1683,6 +1717,11 @@ struct security_hook_heads { struct list_head bprm_secureexec; struct list_head bprm_committing_creds; struct list_head bprm_committed_creds; + struct list_head sb_config_alloc; + struct list_head sb_config_dup; + struct list_head sb_config_free; + struct list_head sb_config_parse_option; + struct list_head sb_get_tree; struct list_head sb_alloc_security; struct list_head sb_free_security; struct list_head sb_copy_data; diff --git a/include/linux/security.h b/include/linux/security.h index af675b576645..d1dfb6abd4f7 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -55,6 +55,7 @@ struct msg_queue; struct xattr; struct xfrm_sec_ctx; struct mm_struct; +struct sb_config; /* If capable should audit the security request */ #define SECURITY_CAP_NOAUDIT 0 @@ -224,6 +225,11 @@ int security_bprm_check(struct linux_binprm *bprm); void security_bprm_committing_creds(struct linux_binprm *bprm); void security_bprm_committed_creds(struct linux_binprm *bprm); int security_bprm_secureexec(struct linux_binprm *bprm); +int security_sb_config_alloc(struct sb_config *sc, struct super_block *sb); +int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc); +void security_sb_config_free(struct sb_config *sc); +int security_sb_config_parse_option(struct sb_config *sc, char *opt); +int security_sb_get_tree(struct sb_config *sc); int security_sb_alloc(struct super_block *sb); void security_sb_free(struct super_block *sb); int security_sb_copy_data(char *orig, char *copy); @@ -520,6 +526,28 @@ static inline int security_bprm_secureexec(struct linux_binprm *bprm) return cap_bprm_secureexec(bprm); } +static inline int security_sb_config_alloc(struct sb_config *sc, + struct super_block *src_sb) +{ + return 0; +} +static inline int security_sb_config_dup(struct sb_config *sc, + struct sb_config *src_sc) +{ + return 0; +} +static inline void security_sb_config_free(struct sb_config *sc) +{ +} +static inline int security_sb_config_parse_option(struct sb_config *sc, char *opt) +{ + return 0; +} +static inline int security_sb_get_tree(struct sb_config *sc) +{ + return 0; +} + static inline int security_sb_alloc(struct super_block *sb) { return 0; diff --git a/security/security.c b/security/security.c index b9fea3999cf8..951f28487719 100644 --- a/security/security.c +++ b/security/security.c @@ -316,6 +316,31 @@ int security_bprm_secureexec(struct linux_binprm *bprm) return call_int_hook(bprm_secureexec, 0, bprm); } +int security_sb_config_alloc(struct sb_config *sc, struct super_block *src_sb) +{ + return call_int_hook(sb_config_alloc, 0, sc, src_sb); +} + +int security_sb_config_dup(struct sb_config *sc, struct sb_config *src_sc) +{ + return call_int_hook(sb_config_dup, 0, sc, src_sc); +} + +void security_sb_config_free(struct sb_config *sc) +{ + call_void_hook(sb_config_free, sc); +} + +int security_sb_config_parse_option(struct sb_config *sc, char *opt) +{ + return call_int_hook(sb_config_parse_option, 0, sc, opt); +} + +int security_sb_get_tree(struct sb_config *sc) +{ + return call_int_hook(sb_get_tree, 0, sc); +} + int security_sb_alloc(struct super_block *sb) { return call_int_hook(sb_alloc_security, 0, sb); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e67a526d1f30..420bfa955fb4 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -47,6 +47,7 @@ #include <linux/fdtable.h> #include <linux/namei.h> #include <linux/mount.h> +#include <linux/sb_config.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv6.h> #include <linux/tty.h> @@ -2826,6 +2827,168 @@ static int selinux_umount(struct vfsmount *mnt, int flags) FILESYSTEM__UNMOUNT, NULL); } +/* fsopen mount context operations */ + +static int selinux_sb_config_alloc(struct sb_config *sc, + struct super_block *src_sb) +{ + struct security_mnt_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return -ENOMEM; + + sc->security = opts; + return 0; +} + +static int selinux_sb_config_dup(struct sb_config *sc, + struct sb_config *src_sc) +{ + const struct security_mnt_opts *src = src_sc->security; + struct security_mnt_opts *opts; + int i, n; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return -ENOMEM; + sc->security = opts; + + if (!src || !src->num_mnt_opts) + return 0; + n = opts->num_mnt_opts = src->num_mnt_opts; + + if (src->mnt_opts) { + opts->mnt_opts = kcalloc(n, sizeof(char *), GFP_KERNEL); + if (!opts->mnt_opts) + return -ENOMEM; + + for (i = 0; i < n; i++) { + if (src->mnt_opts[i]) { + opts->mnt_opts[i] = kstrdup(src->mnt_opts[i], + GFP_KERNEL); + if (!opts->mnt_opts[i]) + return -ENOMEM; + } + } + } + + if (src->mnt_opts_flags) { + opts->mnt_opts_flags = kmemdup(src->mnt_opts_flags, + n * sizeof(int), GFP_KERNEL); + if (!opts->mnt_opts_flags) + return -ENOMEM; + } + + return 0; +} + +static void selinux_sb_config_free(struct sb_config *sc) +{ + struct security_mnt_opts *opts = sc->security; + + security_free_mnt_opts(opts); + sc->security = NULL; +} + +static int selinux_sb_config_parse_option(struct sb_config *sc, char *opt) +{ + struct security_mnt_opts *opts = sc->security; + substring_t args[MAX_OPT_ARGS]; + unsigned int have; + char *c, **oo; + int token, ctx, i, *of; + + token = match_token(opt, tokens, args); + if (token == Opt_error) + return 0; /* Doesn't belong to us. */ + + have = 0; + for (i = 0; i < opts->num_mnt_opts; i++) + have |= 1 << opts->mnt_opts_flags[i]; + if (have & (1 << token)) + return invalf("SELinux: Duplicate mount options"); + + switch (token) { + case Opt_context: + if (have & (1 << Opt_defcontext)) + goto incompatible; + ctx = CONTEXT_MNT; + goto copy_context_string; + + case Opt_fscontext: + ctx = FSCONTEXT_MNT; + goto copy_context_string; + + case Opt_rootcontext: + ctx = ROOTCONTEXT_MNT; + goto copy_context_string; + + case Opt_defcontext: + if (have & (1 << Opt_context)) + goto incompatible; + ctx = DEFCONTEXT_MNT; + goto copy_context_string; + + case Opt_labelsupport: + return 1; + + default: + return invalf("SELinux: Unknown mount option"); + } + +copy_context_string: + if (opts->num_mnt_opts > 3) + return invalf("SELinux: Too many options"); + + of = krealloc(opts->mnt_opts_flags, + (opts->num_mnt_opts + 1) * sizeof(int), GFP_KERNEL); + if (!of) + return -ENOMEM; + of[opts->num_mnt_opts] = 0; + opts->mnt_opts_flags = of; + + oo = krealloc(opts->mnt_opts, + (opts->num_mnt_opts + 1) * sizeof(char *), GFP_KERNEL); + if (!oo) + return -ENOMEM; + oo[opts->num_mnt_opts] = NULL; + opts->mnt_opts = oo; + + c = match_strdup(&args[0]); + if (!c) + return -ENOMEM; + opts->mnt_opts[opts->num_mnt_opts] = c; + opts->mnt_opts_flags[opts->num_mnt_opts] = ctx; + opts->num_mnt_opts++; + return 1; + +incompatible: + return invalf("SELinux: Incompatible mount options"); +} + +static int selinux_sb_get_tree(struct sb_config *sc) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + int rc; + + rc = selinux_set_mnt_opts(sc->root->d_sb, sc->security, 0, NULL); + if (rc) + return rc; + + /* Allow all mounts performed by the kernel */ + if (sc->ms_flags & MS_KERNMOUNT) + return 0; + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = sc->root; + rc = superblock_has_perm(cred, sc->root->d_sb, FILESYSTEM__MOUNT, &ad); + if (rc < 0) + errorf("SELinux: Mount of superblock not permitted"); + return rc; +} + /* inode security operations */ static int selinux_inode_alloc_security(struct inode *inode) @@ -6154,6 +6317,12 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), LSM_HOOK_INIT(bprm_secureexec, selinux_bprm_secureexec), + LSM_HOOK_INIT(sb_config_alloc, selinux_sb_config_alloc), + LSM_HOOK_INIT(sb_config_dup, selinux_sb_config_dup), + LSM_HOOK_INIT(sb_config_free, selinux_sb_config_free), + LSM_HOOK_INIT(sb_config_parse_option, selinux_sb_config_parse_option), + LSM_HOOK_INIT(sb_get_tree, selinux_sb_get_tree), + LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security), LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), LSM_HOOK_INIT(sb_copy_data, selinux_sb_copy_data), -- To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html