diff options
author | Scott Mayhew <smayhew@redhat.com> | 2017-06-05 11:45:04 -0400 |
---|---|---|
committer | Paul Moore <paul@paul-moore.com> | 2017-06-09 16:17:47 -0400 |
commit | 0b4d3452b8b4a5309b4445b900e3cec022cca95a (patch) | |
tree | 3c758827bc8bab1cd36f9c551840cdae00636e97 | |
parent | b4958c892e02241b9bd121f3397b76225ff6f4a3 (diff) |
security/selinux: allow security_sb_clone_mnt_opts to enable/disable native labeling behavior
When an NFSv4 client performs a mount operation, it first mounts the
NFSv4 root and then does path walk to the exported path and performs a
submount on that, cloning the security mount options from the root's
superblock to the submount's superblock in the process.
Unless the NFS server has an explicit fsid=0 export with the
"security_label" option, the NFSv4 root superblock will not have
SBLABEL_MNT set, and neither will the submount superblock after cloning
the security mount options. As a result, setxattr's of security labels
over NFSv4.2 will fail. In a similar fashion, NFSv4.2 mounts mounted
with the context= mount option will not show the correct labels because
the nfs_server->caps flags of the cloned superblock will still have
NFS_CAP_SECURITY_LABEL set.
Allowing the NFSv4 client to enable or disable SECURITY_LSM_NATIVE_LABELS
behavior will ensure that the SBLABEL_MNT flag has the correct value
when the client traverses from an exported path without the
"security_label" option to one with the "security_label" option and
vice versa. Similarly, checking to see if SECURITY_LSM_NATIVE_LABELS is
set upon return from security_sb_clone_mnt_opts() and clearing
NFS_CAP_SECURITY_LABEL if necessary will allow the correct labels to
be displayed for NFSv4.2 mounts mounted with the context= mount option.
Resolves: https://github.com/SELinuxProject/selinux-kernel/issues/35
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
Reviewed-by: Stephen Smalley <sds@tycho.nsa.gov>
Tested-by: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Paul Moore <paul@paul-moore.com>
-rw-r--r-- | fs/nfs/super.c | 17 | ||||
-rw-r--r-- | include/linux/lsm_hooks.h | 4 | ||||
-rw-r--r-- | include/linux/security.h | 8 | ||||
-rw-r--r-- | security/security.c | 7 | ||||
-rw-r--r-- | security/selinux/hooks.c | 35 |
5 files changed, 63 insertions, 8 deletions
diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 2f3822a4a7d5..b8e073546507 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c | |||
@@ -2544,10 +2544,25 @@ EXPORT_SYMBOL_GPL(nfs_set_sb_security); | |||
2544 | int nfs_clone_sb_security(struct super_block *s, struct dentry *mntroot, | 2544 | int nfs_clone_sb_security(struct super_block *s, struct dentry *mntroot, |
2545 | struct nfs_mount_info *mount_info) | 2545 | struct nfs_mount_info *mount_info) |
2546 | { | 2546 | { |
2547 | int error; | ||
2548 | unsigned long kflags = 0, kflags_out = 0; | ||
2549 | |||
2547 | /* clone any lsm security options from the parent to the new sb */ | 2550 | /* clone any lsm security options from the parent to the new sb */ |
2548 | if (d_inode(mntroot)->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) | 2551 | if (d_inode(mntroot)->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) |
2549 | return -ESTALE; | 2552 | return -ESTALE; |
2550 | return security_sb_clone_mnt_opts(mount_info->cloned->sb, s); | 2553 | |
2554 | if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL) | ||
2555 | kflags |= SECURITY_LSM_NATIVE_LABELS; | ||
2556 | |||
2557 | error = security_sb_clone_mnt_opts(mount_info->cloned->sb, s, kflags, | ||
2558 | &kflags_out); | ||
2559 | if (error) | ||
2560 | return error; | ||
2561 | |||
2562 | if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL && | ||
2563 | !(kflags_out & SECURITY_LSM_NATIVE_LABELS)) | ||
2564 | NFS_SB(s)->caps &= ~NFS_CAP_SECURITY_LABEL; | ||
2565 | return 0; | ||
2551 | } | 2566 | } |
2552 | EXPORT_SYMBOL_GPL(nfs_clone_sb_security); | 2567 | EXPORT_SYMBOL_GPL(nfs_clone_sb_security); |
2553 | 2568 | ||
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 68d91e423bca..3cc9d77c7527 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h | |||
@@ -1409,7 +1409,9 @@ union security_list_options { | |||
1409 | unsigned long kern_flags, | 1409 | unsigned long kern_flags, |
1410 | unsigned long *set_kern_flags); | 1410 | unsigned long *set_kern_flags); |
1411 | int (*sb_clone_mnt_opts)(const struct super_block *oldsb, | 1411 | int (*sb_clone_mnt_opts)(const struct super_block *oldsb, |
1412 | struct super_block *newsb); | 1412 | struct super_block *newsb, |
1413 | unsigned long kern_flags, | ||
1414 | unsigned long *set_kern_flags); | ||
1413 | int (*sb_parse_opts_str)(char *options, struct security_mnt_opts *opts); | 1415 | int (*sb_parse_opts_str)(char *options, struct security_mnt_opts *opts); |
1414 | int (*dentry_init_security)(struct dentry *dentry, int mode, | 1416 | int (*dentry_init_security)(struct dentry *dentry, int mode, |
1415 | const struct qstr *name, void **ctx, | 1417 | const struct qstr *name, void **ctx, |
diff --git a/include/linux/security.h b/include/linux/security.h index 549cb828a888..b44e954815ce 100644 --- a/include/linux/security.h +++ b/include/linux/security.h | |||
@@ -249,7 +249,9 @@ int security_sb_set_mnt_opts(struct super_block *sb, | |||
249 | unsigned long kern_flags, | 249 | unsigned long kern_flags, |
250 | unsigned long *set_kern_flags); | 250 | unsigned long *set_kern_flags); |
251 | int security_sb_clone_mnt_opts(const struct super_block *oldsb, | 251 | int security_sb_clone_mnt_opts(const struct super_block *oldsb, |
252 | struct super_block *newsb); | 252 | struct super_block *newsb, |
253 | unsigned long kern_flags, | ||
254 | unsigned long *set_kern_flags); | ||
253 | int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); | 255 | int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); |
254 | int security_dentry_init_security(struct dentry *dentry, int mode, | 256 | int security_dentry_init_security(struct dentry *dentry, int mode, |
255 | const struct qstr *name, void **ctx, | 257 | const struct qstr *name, void **ctx, |
@@ -605,7 +607,9 @@ static inline int security_sb_set_mnt_opts(struct super_block *sb, | |||
605 | } | 607 | } |
606 | 608 | ||
607 | static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb, | 609 | static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb, |
608 | struct super_block *newsb) | 610 | struct super_block *newsb, |
611 | unsigned long kern_flags, | ||
612 | unsigned long *set_kern_flags) | ||
609 | { | 613 | { |
610 | return 0; | 614 | return 0; |
611 | } | 615 | } |
diff --git a/security/security.c b/security/security.c index 714433e3e9a2..30132378d103 100644 --- a/security/security.c +++ b/security/security.c | |||
@@ -420,9 +420,12 @@ int security_sb_set_mnt_opts(struct super_block *sb, | |||
420 | EXPORT_SYMBOL(security_sb_set_mnt_opts); | 420 | EXPORT_SYMBOL(security_sb_set_mnt_opts); |
421 | 421 | ||
422 | int security_sb_clone_mnt_opts(const struct super_block *oldsb, | 422 | int security_sb_clone_mnt_opts(const struct super_block *oldsb, |
423 | struct super_block *newsb) | 423 | struct super_block *newsb, |
424 | unsigned long kern_flags, | ||
425 | unsigned long *set_kern_flags) | ||
424 | { | 426 | { |
425 | return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb); | 427 | return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb, |
428 | kern_flags, set_kern_flags); | ||
426 | } | 429 | } |
427 | EXPORT_SYMBOL(security_sb_clone_mnt_opts); | 430 | EXPORT_SYMBOL(security_sb_clone_mnt_opts); |
428 | 431 | ||
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 9926adbd50a9..9cc042df10d1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c | |||
@@ -525,8 +525,16 @@ static int sb_finish_set_opts(struct super_block *sb) | |||
525 | } | 525 | } |
526 | 526 | ||
527 | sbsec->flags |= SE_SBINITIALIZED; | 527 | sbsec->flags |= SE_SBINITIALIZED; |
528 | |||
529 | /* | ||
530 | * Explicitly set or clear SBLABEL_MNT. It's not sufficient to simply | ||
531 | * leave the flag untouched because sb_clone_mnt_opts might be handing | ||
532 | * us a superblock that needs the flag to be cleared. | ||
533 | */ | ||
528 | if (selinux_is_sblabel_mnt(sb)) | 534 | if (selinux_is_sblabel_mnt(sb)) |
529 | sbsec->flags |= SBLABEL_MNT; | 535 | sbsec->flags |= SBLABEL_MNT; |
536 | else | ||
537 | sbsec->flags &= ~SBLABEL_MNT; | ||
530 | 538 | ||
531 | /* Initialize the root inode. */ | 539 | /* Initialize the root inode. */ |
532 | rc = inode_doinit_with_dentry(root_inode, root); | 540 | rc = inode_doinit_with_dentry(root_inode, root); |
@@ -959,8 +967,11 @@ mismatch: | |||
959 | } | 967 | } |
960 | 968 | ||
961 | static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, | 969 | static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, |
962 | struct super_block *newsb) | 970 | struct super_block *newsb, |
971 | unsigned long kern_flags, | ||
972 | unsigned long *set_kern_flags) | ||
963 | { | 973 | { |
974 | int rc = 0; | ||
964 | const struct superblock_security_struct *oldsbsec = oldsb->s_security; | 975 | const struct superblock_security_struct *oldsbsec = oldsb->s_security; |
965 | struct superblock_security_struct *newsbsec = newsb->s_security; | 976 | struct superblock_security_struct *newsbsec = newsb->s_security; |
966 | 977 | ||
@@ -975,6 +986,13 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, | |||
975 | if (!ss_initialized) | 986 | if (!ss_initialized) |
976 | return 0; | 987 | return 0; |
977 | 988 | ||
989 | /* | ||
990 | * Specifying internal flags without providing a place to | ||
991 | * place the results is not allowed. | ||
992 | */ | ||
993 | if (kern_flags && !set_kern_flags) | ||
994 | return -EINVAL; | ||
995 | |||
978 | /* how can we clone if the old one wasn't set up?? */ | 996 | /* how can we clone if the old one wasn't set up?? */ |
979 | BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); | 997 | BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); |
980 | 998 | ||
@@ -990,6 +1008,18 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, | |||
990 | newsbsec->def_sid = oldsbsec->def_sid; | 1008 | newsbsec->def_sid = oldsbsec->def_sid; |
991 | newsbsec->behavior = oldsbsec->behavior; | 1009 | newsbsec->behavior = oldsbsec->behavior; |
992 | 1010 | ||
1011 | if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && | ||
1012 | !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { | ||
1013 | rc = security_fs_use(newsb); | ||
1014 | if (rc) | ||
1015 | goto out; | ||
1016 | } | ||
1017 | |||
1018 | if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !set_context) { | ||
1019 | newsbsec->behavior = SECURITY_FS_USE_NATIVE; | ||
1020 | *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; | ||
1021 | } | ||
1022 | |||
993 | if (set_context) { | 1023 | if (set_context) { |
994 | u32 sid = oldsbsec->mntpoint_sid; | 1024 | u32 sid = oldsbsec->mntpoint_sid; |
995 | 1025 | ||
@@ -1009,8 +1039,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, | |||
1009 | } | 1039 | } |
1010 | 1040 | ||
1011 | sb_finish_set_opts(newsb); | 1041 | sb_finish_set_opts(newsb); |
1042 | out: | ||
1012 | mutex_unlock(&newsbsec->lock); | 1043 | mutex_unlock(&newsbsec->lock); |
1013 | return 0; | 1044 | return rc; |
1014 | } | 1045 | } |
1015 | 1046 | ||
1016 | static int selinux_parse_opts_str(char *options, | 1047 | static int selinux_parse_opts_str(char *options, |