NFS: Don't ignore suid/sgid bit changes after a successful write
authorTrond Myklebust <trond.myklebust@primarydata.com>
Sun, 13 Apr 2014 15:11:31 +0000 (11:11 -0400)
committerTrond Myklebust <trond.myklebust@primarydata.com>
Wed, 16 Apr 2014 03:24:43 +0000 (23:24 -0400)
If we suspect that the server may have cleared the suid/sgid bit,
then mark the inode for revalidation.

Reported-by: Kinglong Mee <kinglongmee@gmail.com>
Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
fs/nfs/write.c

index 9a3b6a4cd6b9581a037f2508e5b6d4aca565cd93..cd7c651f9b84fc0afc0d707c866baa2403a3308f 100644 (file)
@@ -1353,6 +1353,30 @@ static const struct rpc_call_ops nfs_write_common_ops = {
        .rpc_release = nfs_writeback_release_common,
 };
 
+/*
+ * Special version of should_remove_suid() that ignores capabilities.
+ */
+static int nfs_should_remove_suid(const struct inode *inode)
+{
+       umode_t mode = inode->i_mode;
+       int kill = 0;
+
+       /* suid always must be killed */
+       if (unlikely(mode & S_ISUID))
+               kill = ATTR_KILL_SUID;
+
+       /*
+        * sgid without any exec bits is just a mandatory locking mark; leave
+        * it alone.  If some exec bits are set, it's a real sgid; kill it.
+        */
+       if (unlikely((mode & S_ISGID) && (mode & S_IXGRP)))
+               kill |= ATTR_KILL_SGID;
+
+       if (unlikely(kill && S_ISREG(mode)))
+               return kill;
+
+       return 0;
+}
 
 /*
  * This function is called when the WRITE call is complete.
@@ -1401,9 +1425,16 @@ void nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
                }
        }
 #endif
-       if (task->tk_status < 0)
+       if (task->tk_status < 0) {
                nfs_set_pgio_error(data->header, task->tk_status, argp->offset);
-       else if (resp->count < argp->count) {
+               return;
+       }
+
+       /* Deal with the suid/sgid bit corner case */
+       if (nfs_should_remove_suid(inode))
+               nfs_mark_for_revalidate(inode);
+
+       if (resp->count < argp->count) {
                static unsigned long    complain;
 
                /* This a short write! */