Re: [PATCH] fs: btrfs: fix zstd decompression of sector-padded extents
Qu Wenruo <[email protected]> Sat, 1 Aug 2026 08:07:42 +0930
| Newsgroups | org.kernel.vger.linux-btrfs,org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <[email protected]> |
=E5=9C=A8 2026/8/1 07:56, Cole Munz =E5=86=99=E9=81=93: > Hi Qu, >=20 >> The ram_bytes described the decompressed size of a compressed extent, >> except inlined extents, the ram_bytes should always be fs block aligned= . >> >> I didn't see how things can go "larger than the extent's ram_bytes". >=20 > You're right, and the commit message overstates it. For regular extents > ram_bytes is block aligned and matches the frame's content size, exactly= as > you describe. The case that actually fails is the one you carved out: > compressed inline extents. >=20 > For the inline path, current mainline compresses the whole first block: >=20 > run_delalloc_inline(): > cb =3D btrfs_compress_bio(inode, 0, blocksize, compress_type, ...); >=20 > so the page is zero-padded past EOF and the resulting zstd frame declare= s > frameContentSize =3D blocksize. But the extent item gets the unaligned s= ize: >=20 > __cow_file_range_inline(inode, i_size, compressed_size, ...) -> > insert_inline_extent() -> btrfs_set_file_extent_ram_bytes(leaf, ei,= size); >=20 > So a 1900-byte file stored as a compressed inline extent has ram_bytes 1= 900 > while the frame decodes to 4096. The kernel side never notices because > fs/btrfs/zstd.c zstd_decompress() streams into out_buf and copies out at= most > destlen. U-Boot's decompress_zstd() is the one-shot zstd_decompress_dctx= (), > and btrfs_read_extent_inline() sizes the destination with > dsize =3D btrfs_file_extent_ram_bytes(), so the frame fails the whole-fr= ame > capacity check with dstSize_tooSmall - error code 70, which matches the > "failed to decompress: 70" in the Armbian reports. Thanks a lot! Now I see where the problem is. >=20 > One data point from testing while chasing this: an image built with > mkfs.btrfs --rootdir --compress zstd (btrfs-progs 7.1) does NOT reproduc= e. > For a 1900-byte file, progs writes an inline extent with ram_bytes 1900 = whose > frame also decodes to exactly 1900: >=20 > item 8 key (257 EXTENT_DATA 0) itemoff 15316 itemsize 423 > generation 6 type 0 (inline) > inline extent data size 402 ram_bytes 1900 compression 3 (zstd) >=20 > so mkfs-built images boot fine, and the failure only shows up on files > (re)written at runtime through the kernel - which fits the Armbian patte= rn of > /boot scripts and overlays breaking after a package update touched them. >=20 > I'll send a v2 with the commit message rewritten to name compressed inli= ne > extents as the failing case instead of the hand-wave about sector paddin= g. If > you'd like the on-disk evidence too, I can loop-mount a scratch fs with > compress=3Dzstd, write an unaligned file, and include the dump-tree outp= ut plus > the frame header read from the leaf in the v2 cover. Another thing is, since this bug only affects inlined extents, I'd=20 prefer to have the fix located inside btrfs_read_extent_inline(). So that we allocate a full block for decompression, then only copy=20 ram_bytes back to the destination. With a mention about the limit during dbuf allocation inside=20 btrfs_read_extent_inline(). Thanks a lot of the explanation and fix, Qu >=20 > The fix itself stays in btrfs's decompress_zstd() rather than lib/zstd > because for FIT/ximg/ubifs an undersized destination really does mean co= rrupt > input; only btrfs hands the decompressor a destination smaller than the = frame > on purpose. If you'd rather see it shaped differently - say, only engagi= ng > the bounce path for inline extents - happy to do that in v2 as well. >=20 > Thanks, > Cole >=20