Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selinux: make security_sb_clone_mnt_opts return an error on context mismatch

I had the following problem reported a while back. If you mount the
same filesystem twice using NFSv4 with different contexts, then the
second context= option is ignored. For instance:

# mount server:/export /mnt/test1
# mount server:/export /mnt/test2 -o context=system_u:object_r:tmp_t:s0
# ls -dZ /mnt/test1
drwxrwxrwt. root root system_u:object_r:nfs_t:s0 /mnt/test1
# ls -dZ /mnt/test2
drwxrwxrwt. root root system_u:object_r:nfs_t:s0 /mnt/test2

When we call into SELinux to set the context of a "cloned" superblock,
it will currently just bail out when it notices that we're reusing an
existing superblock. Since the existing superblock is already set up and
presumably in use, we can't go overwriting its context with the one from
the "original" sb. Because of this, the second context= option in this
case cannot take effect.

This patch fixes this by turning security_sb_clone_mnt_opts into an int
return operation. When it finds that the "new" superblock that it has
been handed is already set up, it checks to see whether the contexts on
the old superblock match it. If it does, then it will just return
success, otherwise it'll return -EBUSY and emit a printk to tell the
admin why the second mount failed.

Note that this patch may cause casualties. The NFSv4 code relies on
being able to walk down to an export from the pseudoroot. If you mount
filesystems that are nested within one another with different contexts,
then this patch will make those mounts fail in new and "exciting" ways.

For instance, suppose that /export is a separate filesystem on the
server:

# mount server:/ /mnt/test1
# mount salusa:/export /mnt/test2 -o context=system_u:object_r:tmp_t:s0
mount.nfs: an incorrect mount option was specified

...with the printk in the ring buffer. Because we *might* eventually
walk down to /mnt/test1/export, the mount is denied due to this patch.
The second mount needs the pseudoroot superblock, but that's already
present with the wrong context.

OTOH, if we mount these in the reverse order, then both mounts work,
because the pseudoroot superblock created when mounting /export is
discarded once that mount is done. If we then however try to walk into
that directory, the automount fails for the similar reasons:

# cd /mnt/test1/scratch/
-bash: cd: /mnt/test1/scratch: Device or resource busy

The story I've gotten from the SELinux folks that I've talked to is that
this is desirable behavior. In SELinux-land, mounting the same data
under different contexts is wrong -- there can be only one.

Cc: Steve Dickson <steved@redhat.com>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Acked-by: Eric Paris <eparis@redhat.com>
Signed-off-by: James Morris <james.l.morris@oracle.com>

authored by

Jeff Layton and committed by
James Morris
094f7b69 9b0271d8

+46 -13
+1 -2
fs/nfs/super.c
··· 2380 2380 struct nfs_mount_info *mount_info) 2381 2381 { 2382 2382 /* clone any lsm security options from the parent to the new sb */ 2383 - security_sb_clone_mnt_opts(mount_info->cloned->sb, s); 2384 2383 if (mntroot->d_inode->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) 2385 2384 return -ESTALE; 2386 - return 0; 2385 + return security_sb_clone_mnt_opts(mount_info->cloned->sb, s); 2387 2386 } 2388 2387 EXPORT_SYMBOL_GPL(nfs_clone_sb_security); 2389 2388
+6 -4
include/linux/security.h
··· 1436 1436 struct path *new_path); 1437 1437 int (*sb_set_mnt_opts) (struct super_block *sb, 1438 1438 struct security_mnt_opts *opts); 1439 - void (*sb_clone_mnt_opts) (const struct super_block *oldsb, 1439 + int (*sb_clone_mnt_opts) (const struct super_block *oldsb, 1440 1440 struct super_block *newsb); 1441 1441 int (*sb_parse_opts_str) (char *options, struct security_mnt_opts *opts); 1442 1442 ··· 1721 1721 int security_sb_umount(struct vfsmount *mnt, int flags); 1722 1722 int security_sb_pivotroot(struct path *old_path, struct path *new_path); 1723 1723 int security_sb_set_mnt_opts(struct super_block *sb, struct security_mnt_opts *opts); 1724 - void security_sb_clone_mnt_opts(const struct super_block *oldsb, 1724 + int security_sb_clone_mnt_opts(const struct super_block *oldsb, 1725 1725 struct super_block *newsb); 1726 1726 int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); 1727 1727 ··· 2011 2011 return 0; 2012 2012 } 2013 2013 2014 - static inline void security_sb_clone_mnt_opts(const struct super_block *oldsb, 2014 + static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb, 2015 2015 struct super_block *newsb) 2016 - { } 2016 + { 2017 + return 0; 2018 + } 2017 2019 2018 2020 static inline int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts) 2019 2021 {
+2 -1
security/capability.c
··· 98 98 return 0; 99 99 } 100 100 101 - static void cap_sb_clone_mnt_opts(const struct super_block *oldsb, 101 + static int cap_sb_clone_mnt_opts(const struct super_block *oldsb, 102 102 struct super_block *newsb) 103 103 { 104 + return 0; 104 105 } 105 106 106 107 static int cap_sb_parse_opts_str(char *options, struct security_mnt_opts *opts)
+2 -2
security/security.c
··· 299 299 } 300 300 EXPORT_SYMBOL(security_sb_set_mnt_opts); 301 301 302 - void security_sb_clone_mnt_opts(const struct super_block *oldsb, 302 + int security_sb_clone_mnt_opts(const struct super_block *oldsb, 303 303 struct super_block *newsb) 304 304 { 305 - security_ops->sb_clone_mnt_opts(oldsb, newsb); 305 + return security_ops->sb_clone_mnt_opts(oldsb, newsb); 306 306 } 307 307 EXPORT_SYMBOL(security_sb_clone_mnt_opts); 308 308
+35 -4
security/selinux/hooks.c
··· 750 750 goto out; 751 751 } 752 752 753 - static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, 753 + static int selinux_cmp_sb_context(const struct super_block *oldsb, 754 + const struct super_block *newsb) 755 + { 756 + struct superblock_security_struct *old = oldsb->s_security; 757 + struct superblock_security_struct *new = newsb->s_security; 758 + char oldflags = old->flags & SE_MNTMASK; 759 + char newflags = new->flags & SE_MNTMASK; 760 + 761 + if (oldflags != newflags) 762 + goto mismatch; 763 + if ((oldflags & FSCONTEXT_MNT) && old->sid != new->sid) 764 + goto mismatch; 765 + if ((oldflags & CONTEXT_MNT) && old->mntpoint_sid != new->mntpoint_sid) 766 + goto mismatch; 767 + if ((oldflags & DEFCONTEXT_MNT) && old->def_sid != new->def_sid) 768 + goto mismatch; 769 + if (oldflags & ROOTCONTEXT_MNT) { 770 + struct inode_security_struct *oldroot = oldsb->s_root->d_inode->i_security; 771 + struct inode_security_struct *newroot = newsb->s_root->d_inode->i_security; 772 + if (oldroot->sid != newroot->sid) 773 + goto mismatch; 774 + } 775 + return 0; 776 + mismatch: 777 + printk(KERN_WARNING "SELinux: mount invalid. Same superblock, " 778 + "different security settings for (dev %s, " 779 + "type %s)\n", newsb->s_id, newsb->s_type->name); 780 + return -EBUSY; 781 + } 782 + 783 + static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, 754 784 struct super_block *newsb) 755 785 { 756 786 const struct superblock_security_struct *oldsbsec = oldsb->s_security; ··· 795 765 * mount options. thus we can safely deal with this superblock later 796 766 */ 797 767 if (!ss_initialized) 798 - return; 768 + return 0; 799 769 800 770 /* how can we clone if the old one wasn't set up?? */ 801 771 BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); 802 772 803 - /* if fs is reusing a sb, just let its options stand... */ 773 + /* if fs is reusing a sb, make sure that the contexts match */ 804 774 if (newsbsec->flags & SE_SBINITIALIZED) 805 - return; 775 + return selinux_cmp_sb_context(oldsb, newsb); 806 776 807 777 mutex_lock(&newsbsec->lock); 808 778 ··· 835 805 836 806 sb_finish_set_opts(newsb); 837 807 mutex_unlock(&newsbsec->lock); 808 + return 0; 838 809 } 839 810 840 811 static int selinux_parse_opts_str(char *options,