2021-02-19 16:58:38

by Olga Kornievskaia

[permalink] [raw]
Subject: [PATCH v3 1/2] [security] Add new hook to compare new mount to an existing mount

From: Olga Kornievskaia <[email protected]>

Add a new hook that takes an existing super block and a new mount
with new options and determines if new options confict with an
existing mount or not.

A filesystem can use this new hook to determine if it can share
the an existing superblock with a new superblock for the new mount.

Signed-off-by: Olga Kornievskaia <[email protected]>
---
include/linux/lsm_hook_defs.h | 1 +
include/linux/lsm_hooks.h | 6 ++++
include/linux/security.h | 8 +++++
security/security.c | 7 +++++
security/selinux/hooks.c | 56 +++++++++++++++++++++++++++++++++++
5 files changed, 78 insertions(+)

diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 7aaa753b8608..1b12a5266a51 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -62,6 +62,7 @@ LSM_HOOK(int, 0, sb_alloc_security, 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)
LSM_HOOK(int, 0, sb_eat_lsm_opts, char *orig, void **mnt_opts)
+LSM_HOOK(int, 0, sb_mnt_opts_compat, struct super_block *sb, void *mnt_opts)
LSM_HOOK(int, 0, sb_remount, struct super_block *sb, void *mnt_opts)
LSM_HOOK(int, 0, sb_kern_mount, struct super_block *sb)
LSM_HOOK(int, 0, sb_show_options, struct seq_file *m, struct super_block *sb)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index a19adef1f088..e2519adccb74 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -142,6 +142,12 @@
* @orig the original mount data copied from userspace.
* @copy copied data which will be passed to the security module.
* Returns 0 if the copy was successful.
+ * @sb_mnt_opts_compat:
+ * Determine if the existing mount options are compatible with the new
+ * mount options being used.
+ * @sb superblock being compared
+ * @mnt_opts new mount options
+ * Return 0 if options are the compatible.
* @sb_remount:
* Extracts security system specific mount options and verifies no changes
* are being made to those options.
diff --git a/include/linux/security.h b/include/linux/security.h
index c35ea0ffccd9..50db3d5d1608 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -291,6 +291,7 @@ int security_sb_alloc(struct super_block *sb);
void security_sb_free(struct super_block *sb);
void security_free_mnt_opts(void **mnt_opts);
int security_sb_eat_lsm_opts(char *options, void **mnt_opts);
+int security_sb_mnt_opts_compat(struct super_block *sb, void *mnt_opts);
int security_sb_remount(struct super_block *sb, void *mnt_opts);
int security_sb_kern_mount(struct super_block *sb);
int security_sb_show_options(struct seq_file *m, struct super_block *sb);
@@ -635,6 +636,13 @@ static inline int security_sb_remount(struct super_block *sb,
return 0;
}

+static inline int security_sb_mnt_opts_compat(struct super_block *sb,
+ void *mnt_opts)
+{
+ return 0;
+}
+
+
static inline int security_sb_kern_mount(struct super_block *sb)
{
return 0;
diff --git a/security/security.c b/security/security.c
index 7b09cfbae94f..56cf5563efde 100644
--- a/security/security.c
+++ b/security/security.c
@@ -890,6 +890,13 @@ int security_sb_eat_lsm_opts(char *options, void **mnt_opts)
}
EXPORT_SYMBOL(security_sb_eat_lsm_opts);

+int security_sb_mnt_opts_compat(struct super_block *sb,
+ void *mnt_opts)
+{
+ return call_int_hook(sb_mnt_opts_compat, 0, sb, mnt_opts);
+}
+EXPORT_SYMBOL(security_sb_mnt_opts_compat);
+
int security_sb_remount(struct super_block *sb,
void *mnt_opts)
{
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 644b17ec9e63..afee3a222a0e 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2656,6 +2656,61 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts)
return rc;
}

+static int selinux_sb_mnt_opts_compat(struct super_block *sb, void *mnt_opts)
+{
+ struct selinux_mnt_opts *opts = mnt_opts;
+ struct superblock_security_struct *sbsec = sb->s_security;
+ u32 sid;
+ int rc;
+
+ /*
+ * Superblock not initialized (i.e. no options) - reject if any
+ * options specified, otherwise accept.
+ */
+ if (!(sbsec->flags & SE_SBINITIALIZED))
+ return opts ? 1 : 0;
+
+ /*
+ * Superblock initialized and no options specified - reject if
+ * superblock has any options set, otherwise accept.
+ */
+ if (!opts)
+ return (sbsec->flags & SE_MNTMASK) ? 1 : 0;
+
+ if (opts->fscontext) {
+ rc = parse_sid(sb, opts->fscontext, &sid);
+ if (rc)
+ return 1;
+ if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid))
+ return 1;
+ }
+ if (opts->context) {
+ rc = parse_sid(sb, opts->context, &sid);
+ if (rc)
+ return 1;
+ if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid))
+ return 1;
+ }
+ if (opts->rootcontext) {
+ struct inode_security_struct *root_isec;
+
+ root_isec = backing_inode_security(sb->s_root);
+ rc = parse_sid(sb, opts->rootcontext, &sid);
+ if (rc)
+ return 1;
+ if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid))
+ return 1;
+ }
+ if (opts->defcontext) {
+ rc = parse_sid(sb, opts->defcontext, &sid);
+ if (rc)
+ return 1;
+ if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid))
+ return 1;
+ }
+ return 0;
+}
+
static int selinux_sb_remount(struct super_block *sb, void *mnt_opts)
{
struct selinux_mnt_opts *opts = mnt_opts;
@@ -6984,6 +7039,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {

LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts),
+ LSM_HOOK_INIT(sb_mnt_opts_compat, selinux_sb_mnt_opts_compat),
LSM_HOOK_INIT(sb_remount, selinux_sb_remount),
LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
--
2.27.0


2021-02-19 16:58:45

by Olga Kornievskaia

[permalink] [raw]
Subject: [PATCH v3 2/2] NFSv4 account for selinux security context when deciding to share superblock

From: Olga Kornievskaia <[email protected]>

Keep track of whether or not there were LSM security context
options passed during mount (ie creation of the superblock).
Then, while deciding if the superblock can be shared for the new
mount, check if the newly passed in LSM security context options
are compatible with the existing superblock's ones by calling
security_sb_mnt_opts_compat().

Previously, with selinux enabled, NFS wasn't able to do the
following 2mounts:
mount -o vers=4.2,sec=sys,context=system_u:object_r:root_t:s0
<serverip>:/ /mnt
mount -o vers=4.2,sec=sys,context=system_u:object_r:swapfile_t:s0
<serverip>:/scratch /scratch

2nd mount would fail with "mount.nfs: an incorrect mount option was
specified" and var log messages would have:
"SElinux: mount invalid. Same superblock, different security
settings for.."

Signed-off-by: Olga Kornievskaia <[email protected]>
---
fs/nfs/fs_context.c | 3 +++
fs/nfs/internal.h | 1 +
fs/nfs/super.c | 4 ++++
include/linux/nfs_fs_sb.h | 1 +
4 files changed, 9 insertions(+)

diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c
index 06894bcdea2d..8067f055d842 100644
--- a/fs/nfs/fs_context.c
+++ b/fs/nfs/fs_context.c
@@ -448,6 +448,9 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
if (opt < 0)
return ctx->sloppy ? 1 : opt;

+ if (fc->security)
+ ctx->has_sec_mnt_opts = 1;
+
switch (opt) {
case Opt_source:
if (fc->source)
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 62d3189745cd..08f4f34e8cf5 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -96,6 +96,7 @@ struct nfs_fs_context {
char *fscache_uniq;
unsigned short protofamily;
unsigned short mountfamily;
+ bool has_sec_mnt_opts;

struct {
union {
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 4034102010f0..0a2d252cf90f 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -1058,6 +1058,7 @@ static void nfs_fill_super(struct super_block *sb, struct nfs_fs_context *ctx)
&sb->s_blocksize_bits);

nfs_super_set_maxbytes(sb, server->maxfilesize);
+ server->has_sec_mnt_opts = ctx->has_sec_mnt_opts;
}

static int nfs_compare_mount_options(const struct super_block *s, const struct nfs_server *b,
@@ -1174,6 +1175,9 @@ static int nfs_compare_super(struct super_block *sb, struct fs_context *fc)
return 0;
if (!nfs_compare_userns(old, server))
return 0;
+ if ((old->has_sec_mnt_opts || fc->security) &&
+ security_sb_mnt_opts_compat(sb, fc->security))
+ return 0;
return nfs_compare_mount_options(sb, server, fc);
}

diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 38e60ec742df..3f0acada5794 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -254,6 +254,7 @@ struct nfs_server {

/* User namespace info */
const struct cred *cred;
+ bool has_sec_mnt_opts;
};

/* Server capabilities */
--
2.27.0

2021-02-19 20:30:39

by Casey Schaufler

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] [security] Add new hook to compare new mount to an existing mount

On 2/19/2021 8:57 AM, Olga Kornievskaia wrote:
> From: Olga Kornievskaia <[email protected]>
>
> Add a new hook that takes an existing super block and a new mount
> with new options and determines if new options confict with an
> existing mount or not.
>
> A filesystem can use this new hook to determine if it can share
> the an existing superblock with a new superblock for the new mount.
>
> Signed-off-by: Olga Kornievskaia <[email protected]>
> ---
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/lsm_hooks.h | 6 ++++
> include/linux/security.h | 8 +++++
> security/security.c | 7 +++++
> security/selinux/hooks.c | 56 +++++++++++++++++++++++++++++++++++
> 5 files changed, 78 insertions(+)
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 7aaa753b8608..1b12a5266a51 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -62,6 +62,7 @@ LSM_HOOK(int, 0, sb_alloc_security, 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)
> LSM_HOOK(int, 0, sb_eat_lsm_opts, char *orig, void **mnt_opts)
> +LSM_HOOK(int, 0, sb_mnt_opts_compat, struct super_block *sb, void *mnt_opts)
> LSM_HOOK(int, 0, sb_remount, struct super_block *sb, void *mnt_opts)
> LSM_HOOK(int, 0, sb_kern_mount, struct super_block *sb)
> LSM_HOOK(int, 0, sb_show_options, struct seq_file *m, struct super_block *sb)
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index a19adef1f088..e2519adccb74 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -142,6 +142,12 @@
> * @orig the original mount data copied from userspace.
> * @copy copied data which will be passed to the security module.
> * Returns 0 if the copy was successful.
> + * @sb_mnt_opts_compat:
> + * Determine if the existing mount options are compatible with the new
> + * mount options being used.
> + * @sb superblock being compared
> + * @mnt_opts new mount options
> + * Return 0 if options are the compatible.

s/the //

> * @sb_remount:
> * Extracts security system specific mount options and verifies no changes
> * are being made to those options.
> diff --git a/include/linux/security.h b/include/linux/security.h
> index c35ea0ffccd9..50db3d5d1608 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -291,6 +291,7 @@ int security_sb_alloc(struct super_block *sb);
> void security_sb_free(struct super_block *sb);
> void security_free_mnt_opts(void **mnt_opts);
> int security_sb_eat_lsm_opts(char *options, void **mnt_opts);
> +int security_sb_mnt_opts_compat(struct super_block *sb, void *mnt_opts);
> int security_sb_remount(struct super_block *sb, void *mnt_opts);
> int security_sb_kern_mount(struct super_block *sb);
> int security_sb_show_options(struct seq_file *m, struct super_block *sb);
> @@ -635,6 +636,13 @@ static inline int security_sb_remount(struct super_block *sb,
> return 0;
> }
>
> +static inline int security_sb_mnt_opts_compat(struct super_block *sb,
> + void *mnt_opts)
> +{
> + return 0;
> +}
> +
> +
> static inline int security_sb_kern_mount(struct super_block *sb)
> {
> return 0;
> diff --git a/security/security.c b/security/security.c
> index 7b09cfbae94f..56cf5563efde 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -890,6 +890,13 @@ int security_sb_eat_lsm_opts(char *options, void **mnt_opts)
> }
> EXPORT_SYMBOL(security_sb_eat_lsm_opts);
>
> +int security_sb_mnt_opts_compat(struct super_block *sb,
> + void *mnt_opts)
> +{
> + return call_int_hook(sb_mnt_opts_compat, 0, sb, mnt_opts);
> +}
> +EXPORT_SYMBOL(security_sb_mnt_opts_compat);
> +
> int security_sb_remount(struct super_block *sb,
> void *mnt_opts)
> {
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 644b17ec9e63..afee3a222a0e 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2656,6 +2656,61 @@ static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts)
> return rc;
> }
>
> +static int selinux_sb_mnt_opts_compat(struct super_block *sb, void *mnt_opts)
> +{
> + struct selinux_mnt_opts *opts = mnt_opts;
> + struct superblock_security_struct *sbsec = sb->s_security;
> + u32 sid;
> + int rc;
> +
> + /*
> + * Superblock not initialized (i.e. no options) - reject if any
> + * options specified, otherwise accept.
> + */
> + if (!(sbsec->flags & SE_SBINITIALIZED))
> + return opts ? 1 : 0;
> +
> + /*
> + * Superblock initialized and no options specified - reject if
> + * superblock has any options set, otherwise accept.
> + */
> + if (!opts)
> + return (sbsec->flags & SE_MNTMASK) ? 1 : 0;
> +
> + if (opts->fscontext) {
> + rc = parse_sid(sb, opts->fscontext, &sid);
> + if (rc)
> + return 1;
> + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid))
> + return 1;
> + }
> + if (opts->context) {
> + rc = parse_sid(sb, opts->context, &sid);
> + if (rc)
> + return 1;
> + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid))
> + return 1;
> + }
> + if (opts->rootcontext) {
> + struct inode_security_struct *root_isec;
> +
> + root_isec = backing_inode_security(sb->s_root);
> + rc = parse_sid(sb, opts->rootcontext, &sid);
> + if (rc)
> + return 1;
> + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid))
> + return 1;
> + }
> + if (opts->defcontext) {
> + rc = parse_sid(sb, opts->defcontext, &sid);
> + if (rc)
> + return 1;
> + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid))
> + return 1;
> + }
> + return 0;
> +}
> +
> static int selinux_sb_remount(struct super_block *sb, void *mnt_opts)
> {
> struct selinux_mnt_opts *opts = mnt_opts;
> @@ -6984,6 +7039,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
>
> LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
> LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts),
> + LSM_HOOK_INIT(sb_mnt_opts_compat, selinux_sb_mnt_opts_compat),
> LSM_HOOK_INIT(sb_remount, selinux_sb_remount),
> LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
> LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),