]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
xfs: fix log CRC mismatches between i386 and other architectures
authorChristoph Hellwig <hch@lst.de>
Mon, 20 Oct 2025 12:49:20 +0000 (08:49 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 29 Oct 2025 13:04:39 +0000 (14:04 +0100)
[ Upstream commit e747883c7d7306acb4d683038d881528fbfbe749 ]

When mounting file systems with a log that was dirtied on i386 on
other architectures or vice versa, log recovery is unhappy:

[   11.068052] XFS (vdb): Torn write (CRC failure) detected at log block 0x2. Truncating head block from 0xc.

This is because the CRCs generated by i386 and other architectures
always diff.  The reason for that is that sizeof(struct xlog_rec_header)
returns different values for i386 vs the rest (324 vs 328), because the
struct is not sizeof(uint64_t) aligned, and i386 has odd struct size
alignment rules.

This issue goes back to commit 13cdc853c519 ("Add log versioning, and new
super block field for the log stripe") in the xfs-import tree, which
adds log v2 support and the h_size field that causes the unaligned size.
At that time it only mattered for the crude debug only log header
checksum, but with commit 0e446be44806 ("xfs: add CRC checks to the log")
it became a real issue for v5 file system, because now there is a proper
CRC, and regular builds actually expect it match.

Fix this by allowing checksums with and without the padding.

Fixes: 0e446be44806 ("xfs: add CRC checks to the log")
Cc: <stable@vger.kernel.org> # v3.8
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
[ Adjust context and file names ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/xfs/libxfs/xfs_log_format.h
fs/xfs/xfs_log.c
fs/xfs/xfs_log_priv.h
fs/xfs/xfs_log_recover.c
fs/xfs/xfs_ondisk.h

index 269573c828085fc3cb4f5685a1caf86d9fe1d061..e267fc9d3108f4a0504cf525a485d9f4175e7df8 100644 (file)
@@ -171,12 +171,40 @@ typedef struct xlog_rec_header {
        __be32    h_prev_block; /* block number to previous LR          :  4 */
        __be32    h_num_logops; /* number of log operations in this LR  :  4 */
        __be32    h_cycle_data[XLOG_HEADER_CYCLE_SIZE / BBSIZE];
-       /* new fields */
+
+       /* fields added by the Linux port: */
        __be32    h_fmt;        /* format of log record                 :  4 */
        uuid_t    h_fs_uuid;    /* uuid of FS                           : 16 */
+
+       /* fields added for log v2: */
        __be32    h_size;       /* iclog size                           :  4 */
+
+       /*
+        * When h_size added for log v2 support, it caused structure to have
+        * a different size on i386 vs all other architectures because the
+        * sum of the size ofthe  member is not aligned by that of the largest
+        * __be64-sized member, and i386 has really odd struct alignment rules.
+        *
+        * Due to the way the log headers are placed out on-disk that alone is
+        * not a problem becaue the xlog_rec_header always sits alone in a
+        * BBSIZEs area, and the rest of that area is padded with zeroes.
+        * But xlog_cksum used to calculate the checksum based on the structure
+        * size, and thus gives different checksums for i386 vs the rest.
+        * We now do two checksum validation passes for both sizes to allow
+        * moving v5 file systems with unclean logs between i386 and other
+        * (little-endian) architectures.
+        */
+       __u32     h_pad0;
 } xlog_rec_header_t;
 
+#ifdef __i386__
+#define XLOG_REC_SIZE          offsetofend(struct xlog_rec_header, h_size)
+#define XLOG_REC_SIZE_OTHER    sizeof(struct xlog_rec_header)
+#else
+#define XLOG_REC_SIZE          sizeof(struct xlog_rec_header)
+#define XLOG_REC_SIZE_OTHER    offsetofend(struct xlog_rec_header, h_size)
+#endif /* __i386__ */
+
 typedef struct xlog_rec_ext_header {
        __be32    xh_cycle;     /* write cycle of log                   : 4 */
        __be32    xh_cycle_data[XLOG_HEADER_CYCLE_SIZE / BBSIZE]; /*    : 256 */
index ce6b303484cf196d77268690d31c00dd8dd24cdd..f4af42b3887d7fa54d4654b99ef49ed273bad4a4 100644 (file)
@@ -1804,13 +1804,13 @@ xlog_cksum(
        struct xlog             *log,
        struct xlog_rec_header  *rhead,
        char                    *dp,
-       int                     size)
+       unsigned int            hdrsize,
+       unsigned int            size)
 {
        uint32_t                crc;
 
        /* first generate the crc for the record header ... */
-       crc = xfs_start_cksum_update((char *)rhead,
-                             sizeof(struct xlog_rec_header),
+       crc = xfs_start_cksum_update((char *)rhead, hdrsize,
                              offsetof(struct xlog_rec_header, h_crc));
 
        /* ... then for additional cycle data for v2 logs ... */
@@ -2074,7 +2074,7 @@ xlog_sync(
 
        /* calculcate the checksum */
        iclog->ic_header.h_crc = xlog_cksum(log, &iclog->ic_header,
-                                           iclog->ic_datap, size);
+                       iclog->ic_datap, XLOG_REC_SIZE, size);
        /*
         * Intentionally corrupt the log record CRC based on the error injection
         * frequency, if defined. This facilitates testing log recovery in the
index 1bd2963e8fbd10c0adcc02887382030e79ff37ae..3a73e39f017eba314e16a0193d53b6b9579aa802 100644 (file)
@@ -498,8 +498,8 @@ xlog_recover_finish(
 extern void
 xlog_recover_cancel(struct xlog *);
 
-extern __le32   xlog_cksum(struct xlog *log, struct xlog_rec_header *rhead,
-                           char *dp, int size);
+__le32  xlog_cksum(struct xlog *log, struct xlog_rec_header *rhead,
+               char *dp, unsigned int hdrsize, unsigned int size);
 
 extern struct kmem_cache *xfs_log_ticket_cache;
 struct xlog_ticket *xlog_ticket_alloc(struct xlog *log, int unit_bytes,
index 6542c8d34a65bccb920be7a94f98fe377428715a..20e4c3c3b9e7224b4f96841f3a59b797dc5827e2 100644 (file)
@@ -2854,9 +2854,24 @@ xlog_recover_process(
        int                     pass,
        struct list_head        *buffer_list)
 {
-       __le32                  expected_crc = rhead->h_crc, crc;
+       __le32                  expected_crc = rhead->h_crc, crc, other_crc;
 
-       crc = xlog_cksum(log, rhead, dp, be32_to_cpu(rhead->h_len));
+       crc = xlog_cksum(log, rhead, dp, XLOG_REC_SIZE,
+                       be32_to_cpu(rhead->h_len));
+
+       /*
+        * Look at the end of the struct xlog_rec_header definition in
+        * xfs_log_format.h for the glory details.
+        */
+       if (expected_crc && crc != expected_crc) {
+               other_crc = xlog_cksum(log, rhead, dp, XLOG_REC_SIZE_OTHER,
+                               be32_to_cpu(rhead->h_len));
+               if (other_crc == expected_crc) {
+                       xfs_notice_once(log->l_mp,
+       "Fixing up incorrect CRC due to padding.");
+                       crc = other_crc;
+               }
+       }
 
        /*
         * Nothing else to do if this is a CRC verification pass. Just return
index 9737b5a9f405e03717d2ae2f4cc68c4bb77e921f..cbeec878ca96fae5c524eeb4846c687e15771e5f 100644 (file)
@@ -142,6 +142,8 @@ xfs_check_ondisk_structs(void)
        XFS_CHECK_STRUCT_SIZE(struct xfs_rud_log_format,        16);
        XFS_CHECK_STRUCT_SIZE(struct xfs_map_extent,            32);
        XFS_CHECK_STRUCT_SIZE(struct xfs_phys_extent,           16);
+       XFS_CHECK_STRUCT_SIZE(struct xlog_rec_header,           328);
+       XFS_CHECK_STRUCT_SIZE(struct xlog_rec_ext_header,       260);
 
        XFS_CHECK_OFFSET(struct xfs_bui_log_format, bui_extents,        16);
        XFS_CHECK_OFFSET(struct xfs_cui_log_format, cui_extents,        16);