]> git.itanic.dy.fi Git - linux-stable/commitdiff
xfs: validate inode fork size against fork format
authorDave Chinner <dchinner@redhat.com>
Thu, 22 Sep 2022 15:47:28 +0000 (18:47 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 28 Sep 2022 09:10:29 +0000 (11:10 +0200)
commit 1eb70f54c445fcbb25817841e774adb3d912f3e8 upstream.

[backport for 5.10.y]

xfs_repair catches fork size/format mismatches, but the in-kernel
verifier doesn't, leading to null pointer failures when attempting
to perform operations on the fork. This can occur in the
xfs_dir_is_empty() where the in-memory fork format does not match
the size and so the fork data pointer is accessed incorrectly.

Note: this causes new failures in xfs/348 which is testing mode vs
ftype mismatches. We now detect a regular file that has been changed
to a directory or symlink mode as being corrupt because the data
fork is for a symlink or directory should be in local form when
there are only 3 bytes of data in the data fork. Hence the inode
verify for the regular file now fires w/ -EFSCORRUPTED because
the inode fork format does not match the format the corrupted mode
says it should be in.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Dave Chinner <david@fromorbit.com>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Acked-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/xfs/libxfs/xfs_inode_buf.c

index c667c63f2cb003cea791a158627b4bf76ca988c8..fa8aefe6b7ec312170d18e8a3767a77df4b6a19e 100644 (file)
@@ -358,19 +358,36 @@ xfs_dinode_verify_fork(
        int                     whichfork)
 {
        uint32_t                di_nextents = XFS_DFORK_NEXTENTS(dip, whichfork);
+       mode_t                  mode = be16_to_cpu(dip->di_mode);
+       uint32_t                fork_size = XFS_DFORK_SIZE(dip, mp, whichfork);
+       uint32_t                fork_format = XFS_DFORK_FORMAT(dip, whichfork);
 
-       switch (XFS_DFORK_FORMAT(dip, whichfork)) {
+       /*
+        * For fork types that can contain local data, check that the fork
+        * format matches the size of local data contained within the fork.
+        *
+        * For all types, check that when the size says the should be in extent
+        * or btree format, the inode isn't claiming it is in local format.
+        */
+       if (whichfork == XFS_DATA_FORK) {
+               if (S_ISDIR(mode) || S_ISLNK(mode)) {
+                       if (be64_to_cpu(dip->di_size) <= fork_size &&
+                           fork_format != XFS_DINODE_FMT_LOCAL)
+                               return __this_address;
+               }
+
+               if (be64_to_cpu(dip->di_size) > fork_size &&
+                   fork_format == XFS_DINODE_FMT_LOCAL)
+                       return __this_address;
+       }
+
+       switch (fork_format) {
        case XFS_DINODE_FMT_LOCAL:
                /*
-                * no local regular files yet
+                * No local regular files yet.
                 */
-               if (whichfork == XFS_DATA_FORK) {
-                       if (S_ISREG(be16_to_cpu(dip->di_mode)))
-                               return __this_address;
-                       if (be64_to_cpu(dip->di_size) >
-                                       XFS_DFORK_SIZE(dip, mp, whichfork))
-                               return __this_address;
-               }
+               if (S_ISREG(mode) && whichfork == XFS_DATA_FORK)
+                       return __this_address;
                if (di_nextents)
                        return __this_address;
                break;