When mounting an NFS export that is a mountpoint on the host, doing the
same mount a second time leads to a security_sb_set_mnt_opts() call on
an already intialized superblock, which leaves the
SECURITY_LSM_NATIVE_LABELS flag unset even if it's provided by the FS.
NFS then obediently clears NFS_CAP_SECURITY_LABEL from its server
capability set, leading to any newly created inodes for this superblock
to end up without labels.

To fix this, make sure to return the SECURITY_LSM_NATIVE_LABELS flag
when security_sb_set_mnt_opts() is called on an already initialized
superblock with matching security options.

While there, also do a sanity check to ensure that
SECURITY_LSM_NATIVE_LABELS is set in kflags if and only if
sbsec->behavior == SECURITY_FS_USE_NATIVE.

Minimal reproducer:
    # systemctl start nfs-server
    # exportfs -o rw,no_root_squash,security_label localhost:/
    # mount -t nfs -o "nfsvers=4.2" localhost:/etc /mnt
    # mount -t nfs -o "nfsvers=4.2" localhost:/etc /mnt
    # ls -lZ /mnt
    [all labels are system_u:object_r:unlabeled_t:s0]

Signed-off-by: Ondrej Mosnacek <omosn...@redhat.com>
 security/selinux/hooks.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1daf7bec4bb0..b8efb14a1d1a 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -741,7 +741,24 @@ static int selinux_set_mnt_opts(struct super_block *sb,
                /* previously mounted with options, but not on this attempt? */
                if ((sbsec->flags & SE_MNTMASK) && !opts)
                        goto out_double_mount;
+               /*
+                * If we are checking an already initialized mount and the
+                * options match, make sure to return back the
+                * SECURITY_LSM_NATIVE_LABELS flag if applicable. If the
+                * superblock has the NATIVE behavior set and the FS is not
+                * signaling its support (or vice versa), then it is a
+                * programmer error, so emit a WARNING and return -EINVAL.
+                */
                rc = 0;
+               if (sbsec->behavior == SECURITY_FS_USE_NATIVE) {
+                       if (WARN_ON(!(kern_flags & SECURITY_LSM_NATIVE_LABELS)))
+                               rc = -EINVAL;
+                       else
+                               *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS;
+               } else if (WARN_ON(kern_flags & SECURITY_LSM_NATIVE_LABELS)) {
+                       rc = -EINVAL;
+               }
                goto out;

Reply via email to