Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.

Commit 0aca739

Browse files
Darrick J. Wonggregkh
Darrick J. Wong
authored andcommittedOct 22, 2024
xfs: allow symlinks with short remote targets
commit 38de567 upstream. An internal user complained about log recovery failing on a symlink ("Bad dinode after recovery") with the following (excerpted) format: core.magic = 0x494e core.mode = 0120777 core.version = 3 core.format = 2 (extents) core.nlinkv2 = 1 core.nextents = 1 core.size = 297 core.nblocks = 1 core.naextents = 0 core.forkoff = 0 core.aformat = 2 (extents) u3.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,12,1,0] This is a symbolic link with a 297-byte target stored in a disk block, which is to say this is a symlink with a remote target. The forkoff is 0, which is to say that there's 512 - 176 == 336 bytes in the inode core to store the data fork. Eventually, testing of generic/388 failed with the same inode corruption message during inode recovery. In writing a debugging patch to call xfs_dinode_verify on dirty inode log items when we're committing transactions, I observed that xfs/298 can reproduce the problem quite quickly. xfs/298 creates a symbolic link, adds some extended attributes, then deletes them all. The test failure occurs when the final removexattr also deletes the attr fork because that does not convert the remote symlink back into a shortform symlink. That is how we trip this test. The only reason why xfs/298 only triggers with the debug patch added is that it deletes the symlink, so the final iflush shows the inode as free. I wrote a quick fstest to emulate the behavior of xfs/298, except that it leaves the symlinks on the filesystem after inducing the "corrupt" state. Kernels going back at least as far as 4.18 have written out symlink inodes in this manner and prior to 1eb70f5 they did not object to reading them back in. Because we've been writing out inodes this way for quite some time, the only way to fix this is to relax the check for symbolic links. Directories don't have this problem because di_size is bumped to blocksize during the sf->data conversion. Fixes: 1eb70f5 ("xfs: validate inode fork size against fork format") Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Chandan Babu R <chandanbabu@kernel.org> Signed-off-by: Catherine Hoang <catherine.hoang@oracle.com> Acked-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 4c99f30 commit 0aca739

File tree

1 file changed

+24
-4
lines changed

1 file changed

+24
-4
lines changed
 

‎fs/xfs/libxfs/xfs_inode_buf.c

+24-4
Original file line numberDiff line numberDiff line change
@@ -366,17 +366,37 @@ xfs_dinode_verify_fork(
366366
/*
367367
* For fork types that can contain local data, check that the fork
368368
* format matches the size of local data contained within the fork.
369-
*
370-
* For all types, check that when the size says the should be in extent
371-
* or btree format, the inode isn't claiming it is in local format.
372369
*/
373370
if (whichfork == XFS_DATA_FORK) {
374-
if (S_ISDIR(mode) || S_ISLNK(mode)) {
371+
/*
372+
* A directory small enough to fit in the inode must be stored
373+
* in local format. The directory sf <-> extents conversion
374+
* code updates the directory size accordingly.
375+
*/
376+
if (S_ISDIR(mode)) {
377+
if (be64_to_cpu(dip->di_size) <= fork_size &&
378+
fork_format != XFS_DINODE_FMT_LOCAL)
379+
return __this_address;
380+
}
381+
382+
/*
383+
* A symlink with a target small enough to fit in the inode can
384+
* be stored in extents format if xattrs were added (thus
385+
* converting the data fork from shortform to remote format)
386+
* and then removed.
387+
*/
388+
if (S_ISLNK(mode)) {
375389
if (be64_to_cpu(dip->di_size) <= fork_size &&
390+
fork_format != XFS_DINODE_FMT_EXTENTS &&
376391
fork_format != XFS_DINODE_FMT_LOCAL)
377392
return __this_address;
378393
}
379394

395+
/*
396+
* For all types, check that when the size says the fork should
397+
* be in extent or btree format, the inode isn't claiming to be
398+
* in local format.
399+
*/
380400
if (be64_to_cpu(dip->di_size) > fork_size &&
381401
fork_format == XFS_DINODE_FMT_LOCAL)
382402
return __this_address;

0 commit comments

Comments
 (0)
This repository has been archived.