Linux Btrfs filesystem development
 help / color / mirror / Atom feed
* [PATCH] fs: btrfs: fix zstd decompression of sector-padded extents
@ 2026-07-31 20:07 Cole Munz
  2026-07-31 21:38 ` Qu Wenruo
  2026-08-01  0:55 ` [PATCH v2] fs: btrfs: fix zstd decompression of compressed inline extents Cole Munz
  0 siblings, 2 replies; 6+ messages in thread
From: Cole Munz @ 2026-07-31 20:07 UTC (permalink / raw)
  To: Marek Behún, Tom Rini; +Cc: Qu Wenruo, Simon Glass, u-boot, linux-btrfs

Btrfs pads compressed extents up to a sector boundary, so a file whose
size isn't sector-aligned still gets compressed as a frame whose
declared content size is the sector-rounded length, larger than the
extent's ram_bytes. zstd_decompress_dctx()'s one-shot API requires the
destination buffer to cover the whole frame and fails with
ZSTD_error_dstSize_tooSmall otherwise, even though
btrfs_read_extent_reg() and btrfs_read_extent_inline() already
zero-fill any short tail. The file then reads back truncated or the
read fails outright, which is what breaks fdt apply on zstd-compressed
overlays.

Decompress into a bounce buffer sized to the frame when the declared
content size exceeds ram_bytes, and copy out only what the caller
asked for. Also trim the input with zstd_find_frame_compressed_size()
first, since the on-disk extent may carry trailing sector padding
after the frame.

The fix lives here rather than in the shared lib/zstd/zstd.c wrapper
on purpose: for FIT images, ximg and ubifs an undersized destination
really does mean a corrupt input and should keep failing hard. Armbian
carries the same caller-side fix for this exact failure on RK3399 and
ODROID-N2 boards (armbian/build#9651, #10208), where it showed up as
"zstd_decompress: failed to decompress: 70".

Fixes: 918adf8e0733 ("btrfs: Use U-Boot API for decompression")
Signed-off-by: Cole Munz <Munzzyy1@proton.me>
---
 fs/btrfs/compression.c | 76 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 71 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index c69524d38ecc..017e61242a68 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -6,7 +6,7 @@
  */
 
 #include "btrfs.h"
-#include <abuf.h>
+#include <limits.h>
 #include <log.h>
 #include <malloc.h>
 #include <linux/lzo.h>
@@ -137,12 +137,78 @@ static u32 decompress_zlib(const u8 *_cbuf, u32 clen, u8 *dbuf, u32 dlen)
 
 static u32 decompress_zstd(const u8 *cbuf, u32 clen, u8 *dbuf, u32 dlen)
 {
-	struct abuf in, out;
+	zstd_dctx *ctx;
+	zstd_frame_header fh;
+	size_t wsize, len, dst_capacity = dlen;
+	void *workspace;
+	u8 *dst = dbuf, *bounce = NULL;
+	u32 ret;
+
+	wsize = zstd_dctx_workspace_bound();
+	workspace = malloc(wsize);
+	if (!workspace)
+		return -1;
+
+	ctx = zstd_init_dctx(workspace, wsize);
+	if (!ctx) {
+		ret = -1;
+		goto out;
+	}
 
-	abuf_init_set(&in, (u8 *)cbuf, clen);
-	abuf_init_set(&out, dbuf, dlen);
+	/*
+	 * Compressed extents are padded up to a sector boundary, so clen may
+	 * include trailing junk after the actual zstd frame.
+	 */
+	len = zstd_find_frame_compressed_size(cbuf, clen);
+	if (zstd_is_error(len)) {
+		ret = -1;
+		goto out;
+	}
 
-	return zstd_decompress(&in, &out);
+	/*
+	 * Btrfs compresses whole sectors, so a file whose size is not a
+	 * multiple of the sector size still yields a frame whose content
+	 * size is the sector-rounded length, which can be larger than dlen
+	 * (ram_bytes from the extent item). zstd_decompress_dctx() requires
+	 * its output buffer to cover the whole frame and would otherwise
+	 * fail with ZSTD_error_dstSize_tooSmall, even though the extra bytes
+	 * are just padding the caller is going to discard: both
+	 * btrfs_read_extent_reg() and btrfs_read_extent_inline() already
+	 * zero-fill any tail beyond what we return here. Decompress into a
+	 * bounce buffer sized to the frame when that happens.
+	 */
+	if (!zstd_get_frame_header(&fh, cbuf, len) &&
+	    fh.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN &&
+	    fh.frameContentSize > dlen) {
+		if (fh.frameContentSize > SIZE_MAX) {
+			ret = -1;
+			goto out;
+		}
+		bounce = malloc(fh.frameContentSize);
+		if (!bounce) {
+			ret = -1;
+			goto out;
+		}
+		dst = bounce;
+		dst_capacity = fh.frameContentSize;
+	}
+
+	len = zstd_decompress_dctx(ctx, dst, dst_capacity, cbuf, len);
+	if (zstd_is_error(len)) {
+		ret = -1;
+		goto out;
+	}
+
+	if (bounce) {
+		memcpy(dbuf, bounce, dlen);
+		ret = dlen;
+	} else {
+		ret = len;
+	}
+out:
+	free(bounce);
+	free(workspace);
+	return ret;
 }
 
 u32 btrfs_decompress(u8 type, const char *c, u32 clen, char *d, u32 dlen)
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-01  1:13 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 20:07 [PATCH] fs: btrfs: fix zstd decompression of sector-padded extents Cole Munz
2026-07-31 21:38 ` Qu Wenruo
2026-07-31 22:26   ` Cole Munz
2026-07-31 22:37     ` Qu Wenruo
2026-08-01  0:55 ` [PATCH v2] fs: btrfs: fix zstd decompression of compressed inline extents Cole Munz
2026-08-01  1:13   ` Qu Wenruo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox