gfs2: be careful with inode refresh
authorAl Viro <viro@zeniv.linux.org.uk>
Fri, 12 Feb 2021 18:22:38 +0000 (13:22 -0500)
committerAl Viro <viro@zeniv.linux.org.uk>
Sat, 13 Mar 2021 03:15:19 +0000 (22:15 -0500)
1) gfs2_dinode_in() should *not* touch ->i_rdev on live inodes; even
"zero and immediately reread the same value from dinode" is broken -
have it overlap with ->release() of char device and you can get all
kinds of bogus behaviour.

2) mismatch on inode type on live inodes should be treated as fs
corruption rather than blindly setting ->i_mode.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/gfs2/glops.c

index 8e32d56..ef0b583 100644 (file)
@@ -394,18 +394,24 @@ static int gfs2_dinode_in(struct gfs2_inode *ip, const void *buf)
        const struct gfs2_dinode *str = buf;
        struct timespec64 atime;
        u16 height, depth;
+       umode_t mode = be32_to_cpu(str->di_mode);
+       bool is_new = ip->i_inode.i_flags & I_NEW;
 
        if (unlikely(ip->i_no_addr != be64_to_cpu(str->di_num.no_addr)))
                goto corrupt;
+       if (unlikely(!is_new && inode_wrong_type(&ip->i_inode, mode)))
+               goto corrupt;
        ip->i_no_formal_ino = be64_to_cpu(str->di_num.no_formal_ino);
-       ip->i_inode.i_mode = be32_to_cpu(str->di_mode);
-       ip->i_inode.i_rdev = 0;
-       switch (ip->i_inode.i_mode & S_IFMT) {
-       case S_IFBLK:
-       case S_IFCHR:
-               ip->i_inode.i_rdev = MKDEV(be32_to_cpu(str->di_major),
-                                          be32_to_cpu(str->di_minor));
-               break;
+       ip->i_inode.i_mode = mode;
+       if (is_new) {
+               ip->i_inode.i_rdev = 0;
+               switch (mode & S_IFMT) {
+               case S_IFBLK:
+               case S_IFCHR:
+                       ip->i_inode.i_rdev = MKDEV(be32_to_cpu(str->di_major),
+                                                  be32_to_cpu(str->di_minor));
+                       break;
+               }
        }
 
        i_uid_write(&ip->i_inode, be32_to_cpu(str->di_uid));