cifs: guard against hardlinking directories
authorJeff Layton <jlayton@redhat.com>
Tue, 11 May 2010 18:59:55 +0000 (14:59 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 26 May 2010 21:29:16 +0000 (14:29 -0700)
commit 3d69438031b00c601c991ab447cafb7d5c3c59a6 upstream.

When we made serverino the default, we trusted that the field sent by the
server in the "uniqueid" field was actually unique. It turns out that it
isn't reliably so.

Samba, in particular, will just put the st_ino in the uniqueid field when
unix extensions are enabled. When a share spans multiple filesystems, it's
quite possible that there will be collisions. This is a server bug, but
when the inodes in question are a directory (as is often the case) and
there is a collision with the root inode of the mount, the result is a
kernel panic on umount.

Fix this by checking explicitly for directory inodes with the same
uniqueid. If that is the case, then we can assume that using server inode
numbers will be a problem and that they should be disabled.

Fixes Samba bugzilla 7407

Signed-off-by: Jeff Layton <jlayton@redhat.com>
Reviewed-and-Tested-by: Suresh Jayaraman <sjayaraman@suse.de>
Signed-off-by: Steve French <sfrench@us.ibm.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
fs/cifs/cifsglob.h
fs/cifs/inode.c

index 5d0fde18039c6780ed8e0dcad3ea3fe7ca07a094..c4dbc633361f35a2df0f8cffe18870d7ef4f159b 100644 (file)
@@ -499,6 +499,7 @@ struct dfs_info3_param {
 #define CIFS_FATTR_DFS_REFERRAL                0x1
 #define CIFS_FATTR_DELETE_PENDING      0x2
 #define CIFS_FATTR_NEED_REVAL          0x4
+#define CIFS_FATTR_INO_COLLISION       0x8
 
 struct cifs_fattr {
        u32             cf_flags;
index cababd8a52df32097f3219327ef491254ced34f0..a104ca30f3503383ee192397d8147a4db112aaed 100644 (file)
@@ -610,6 +610,16 @@ cifs_find_inode(struct inode *inode, void *opaque)
        if (CIFS_I(inode)->uniqueid != fattr->cf_uniqueid)
                return 0;
 
+       /*
+        * uh oh -- it's a directory. We can't use it since hardlinked dirs are
+        * verboten. Disable serverino and return it as if it were found, the
+        * caller can discard it, generate a uniqueid and retry the find
+        */
+       if (S_ISDIR(inode->i_mode) && !list_empty(&inode->i_dentry)) {
+               fattr->cf_flags |= CIFS_FATTR_INO_COLLISION;
+               cifs_autodisable_serverino(CIFS_SB(inode->i_sb));
+       }
+
        return 1;
 }
 
@@ -629,15 +639,22 @@ cifs_iget(struct super_block *sb, struct cifs_fattr *fattr)
        unsigned long hash;
        struct inode *inode;
 
+retry_iget5_locked:
        cFYI(1, ("looking for uniqueid=%llu", fattr->cf_uniqueid));
 
        /* hash down to 32-bits on 32-bit arch */
        hash = cifs_uniqueid_to_ino_t(fattr->cf_uniqueid);
 
        inode = iget5_locked(sb, hash, cifs_find_inode, cifs_init_inode, fattr);
-
-       /* we have fattrs in hand, update the inode */
        if (inode) {
+               /* was there a problematic inode number collision? */
+               if (fattr->cf_flags & CIFS_FATTR_INO_COLLISION) {
+                       iput(inode);
+                       fattr->cf_uniqueid = iunique(sb, ROOT_I);
+                       fattr->cf_flags &= ~CIFS_FATTR_INO_COLLISION;
+                       goto retry_iget5_locked;
+               }
+
                cifs_fattr_to_inode(inode, fattr);
                if (sb->s_flags & MS_NOATIME)
                        inode->i_flags |= S_NOATIME | S_NOCMTIME;