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