public inbox for linux-crypto@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] crypto: zstd - fix segmented acomp streaming paths
@ 2026-03-09  8:20 Wesley Atwell
  2026-03-19 22:43 ` Giovanni Cabiddu
  0 siblings, 1 reply; 3+ messages in thread
From: Wesley Atwell @ 2026-03-09  8:20 UTC (permalink / raw)
  To: herbert, davem, terrelln, dsterba, giovanni.cabiddu,
	suman.kumar.chakraborty
  Cc: linux-crypto, linux-kernel, Wesley Atwell

The zstd acomp implementation does not correctly handle segmented
source and destination walks.

The compression path advances the destination walk by the full
segment length rather than the bytes actually produced, and it only
calls zstd_end_stream() once even though the streaming API requires it
to be called until it returns 0.  With segmented destinations this can
leave buffered output behind and misaccount the walk progress.

The decompression path has the same destination accounting issue, and
it stops when the source walk is exhausted even if
zstd_decompress_stream() has not yet reported that the frame is fully
decoded and flushed.  That can report success too early for segmented
requests and incomplete frames.

Fix both streaming paths by advancing destination segments by actual
output bytes, refilling destination segments as needed, draining
zstd_end_stream() until completion, and continuing to flush buffered
decompression output after the source walk is exhausted.  Return
-EINVAL if decompression cannot finish once the input has been fully
consumed.

Fixes: f5ad93ffb541 ("crypto: zstd - convert to acomp")
Assisted-by: Codex:GPT-5
Signed-off-by: Wesley Atwell <atwellwea@gmail.com>
---
Local validation:
- built bzImage with CONFIG_CRYPTO_SELFTESTS=y and CONFIG_CRYPTO_SELFTESTS_FULL=y
- exercised segmented zstd acomp requests using temporary local testmgr scaffolding
- booted under virtme and verified zstd-generic selftest passed in /proc/crypto

 crypto/zstd.c | 228 ++++++++++++++++++++++++++++++++++----------------
 1 file changed, 156 insertions(+), 72 deletions(-)

diff --git a/crypto/zstd.c b/crypto/zstd.c
index 556f5d2bdd5f..3e19da1fed22 100644
--- a/crypto/zstd.c
+++ b/crypto/zstd.c
@@ -94,18 +94,30 @@ static int zstd_compress_one(struct acomp_req *req, struct zstd_ctx *ctx,
 	return 0;
 }
 
+static int zstd_acomp_next_dst(struct acomp_walk *walk, zstd_out_buffer *outbuf)
+{
+	unsigned int dcur = acomp_walk_next_dst(walk);
+
+	if (!dcur)
+		return -ENOSPC;
+
+	outbuf->pos = 0;
+	outbuf->dst = walk->dst.virt.addr;
+	outbuf->size = dcur;
+
+	return 0;
+}
+
 static int zstd_compress(struct acomp_req *req)
 {
 	struct crypto_acomp_stream *s;
-	unsigned int pos, scur, dcur;
+	unsigned int scur;
 	unsigned int total_out = 0;
-	bool data_available = true;
 	zstd_out_buffer outbuf;
 	struct acomp_walk walk;
 	zstd_in_buffer inbuf;
 	struct zstd_ctx *ctx;
-	size_t pending_bytes;
-	size_t num_bytes;
+	size_t remaining;
 	int ret;
 
 	s = crypto_acomp_lock_stream_bh(&zstd_streams);
@@ -115,66 +127,87 @@ static int zstd_compress(struct acomp_req *req)
 	if (ret)
 		goto out;
 
+	ret = zstd_acomp_next_dst(&walk, &outbuf);
+	if (ret)
+		goto out;
+
 	ctx->cctx = zstd_init_cstream(&ctx->params, 0, ctx->wksp, ctx->wksp_size);
 	if (!ctx->cctx) {
 		ret = -EINVAL;
 		goto out;
 	}
 
-	do {
-		dcur = acomp_walk_next_dst(&walk);
-		if (!dcur) {
-			ret = -ENOSPC;
+	for (;;) {
+		scur = acomp_walk_next_src(&walk);
+		if (outbuf.size == req->dlen && scur == req->slen) {
+			ret = zstd_compress_one(req, ctx, walk.src.virt.addr,
+						walk.dst.virt.addr, &total_out);
+			if (!ret) {
+				acomp_walk_done_src(&walk, scur);
+				acomp_walk_done_dst(&walk, total_out);
+			}
 			goto out;
 		}
 
-		outbuf.pos = 0;
-		outbuf.dst = (u8 *)walk.dst.virt.addr;
-		outbuf.size = dcur;
+		if (!scur)
+			break;
+
+		inbuf.pos = 0;
+		inbuf.src = walk.src.virt.addr;
+		inbuf.size = scur;
 
 		do {
-			scur = acomp_walk_next_src(&walk);
-			if (dcur == req->dlen && scur == req->slen) {
-				ret = zstd_compress_one(req, ctx, walk.src.virt.addr,
-							walk.dst.virt.addr, &total_out);
-				acomp_walk_done_src(&walk, scur);
-				acomp_walk_done_dst(&walk, dcur);
+			remaining = zstd_compress_stream(ctx->cctx, &outbuf, &inbuf);
+			if (zstd_is_error(remaining)) {
+				ret = -EIO;
 				goto out;
 			}
 
-			if (scur) {
-				inbuf.pos = 0;
-				inbuf.src = walk.src.virt.addr;
-				inbuf.size = scur;
-			} else {
-				data_available = false;
-				break;
-			}
+			if (outbuf.pos != outbuf.size)
+				continue;
 
-			num_bytes = zstd_compress_stream(ctx->cctx, &outbuf, &inbuf);
-			if (ZSTD_isError(num_bytes)) {
-				ret = -EIO;
+			total_out += outbuf.pos;
+			acomp_walk_done_dst(&walk, outbuf.pos);
+
+			ret = zstd_acomp_next_dst(&walk, &outbuf);
+			if (ret)
 				goto out;
+		} while (inbuf.pos != inbuf.size);
+
+		acomp_walk_done_src(&walk, inbuf.pos);
+	}
+
+	for (;;) {
+		remaining = zstd_end_stream(ctx->cctx, &outbuf);
+		if (zstd_is_error(remaining)) {
+			ret = -EIO;
+			goto out;
+		}
+
+		if (outbuf.pos == outbuf.size) {
+			total_out += outbuf.pos;
+			acomp_walk_done_dst(&walk, outbuf.pos);
+
+			if (!remaining) {
+				outbuf.pos = 0;
+				break;
 			}
 
-			pending_bytes = zstd_flush_stream(ctx->cctx, &outbuf);
-			if (ZSTD_isError(pending_bytes)) {
-				ret = -EIO;
+			ret = zstd_acomp_next_dst(&walk, &outbuf);
+			if (ret)
 				goto out;
-			}
-			acomp_walk_done_src(&walk, inbuf.pos);
-		} while (dcur != outbuf.pos);
 
-		total_out += outbuf.pos;
-		acomp_walk_done_dst(&walk, dcur);
-	} while (data_available);
+			continue;
+		}
 
-	pos = outbuf.pos;
-	num_bytes = zstd_end_stream(ctx->cctx, &outbuf);
-	if (ZSTD_isError(num_bytes))
-		ret = -EIO;
-	else
-		total_out += (outbuf.pos - pos);
+		if (!remaining)
+			break;
+	}
+
+	if (outbuf.pos) {
+		total_out += outbuf.pos;
+		acomp_walk_done_dst(&walk, outbuf.pos);
+	}
 
 out:
 	if (ret)
@@ -209,12 +242,12 @@ static int zstd_decompress(struct acomp_req *req)
 {
 	struct crypto_acomp_stream *s;
 	unsigned int total_out = 0;
-	unsigned int scur, dcur;
+	unsigned int scur;
 	zstd_out_buffer outbuf;
 	struct acomp_walk walk;
 	zstd_in_buffer inbuf;
 	struct zstd_ctx *ctx;
-	size_t pending_bytes;
+	size_t remaining = 1;
 	int ret;
 
 	s = crypto_acomp_lock_stream_bh(&zstd_streams);
@@ -224,54 +257,105 @@ static int zstd_decompress(struct acomp_req *req)
 	if (ret)
 		goto out;
 
+	ret = zstd_acomp_next_dst(&walk, &outbuf);
+	if (ret)
+		goto out;
+
 	ctx->dctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ctx->wksp_size);
 	if (!ctx->dctx) {
 		ret = -EINVAL;
 		goto out;
 	}
 
-	do {
+	for (;;) {
 		scur = acomp_walk_next_src(&walk);
-		if (scur) {
-			inbuf.pos = 0;
-			inbuf.size = scur;
-			inbuf.src = walk.src.virt.addr;
-		} else {
-			break;
+		if (outbuf.size == req->dlen && scur == req->slen) {
+			ret = zstd_decompress_one(req, ctx, walk.src.virt.addr,
+						  walk.dst.virt.addr, &total_out);
+			if (!ret) {
+				acomp_walk_done_src(&walk, scur);
+				acomp_walk_done_dst(&walk, total_out);
+			}
+			goto out;
 		}
 
+		if (!scur)
+			break;
+
+		inbuf.pos = 0;
+		inbuf.size = scur;
+		inbuf.src = walk.src.virt.addr;
+
 		do {
-			dcur = acomp_walk_next_dst(&walk);
-			if (dcur == req->dlen && scur == req->slen) {
-				ret = zstd_decompress_one(req, ctx, walk.src.virt.addr,
-							  walk.dst.virt.addr, &total_out);
-				acomp_walk_done_dst(&walk, dcur);
-				acomp_walk_done_src(&walk, scur);
+			remaining = zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf);
+			if (zstd_is_error(remaining)) {
+				ret = -EIO;
 				goto out;
 			}
 
-			if (!dcur) {
-				ret = -ENOSPC;
-				goto out;
+			if (outbuf.pos != outbuf.size)
+				continue;
+
+			total_out += outbuf.pos;
+			acomp_walk_done_dst(&walk, outbuf.pos);
+
+			if (!remaining) {
+				outbuf.pos = 0;
+				break;
 			}
 
-			outbuf.pos = 0;
-			outbuf.dst = (u8 *)walk.dst.virt.addr;
-			outbuf.size = dcur;
+			ret = zstd_acomp_next_dst(&walk, &outbuf);
+			if (ret)
+				goto out;
+		} while (inbuf.pos != scur);
 
-			pending_bytes = zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf);
-			if (ZSTD_isError(pending_bytes)) {
-				ret = -EIO;
+		acomp_walk_done_src(&walk, inbuf.pos);
+	}
+
+	inbuf.pos = 0;
+	inbuf.size = 0;
+	inbuf.src = NULL;
+
+	/* Drain any buffered output after the source walk is exhausted. */
+	while (remaining) {
+		size_t pos = outbuf.pos;
+
+		remaining = zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf);
+		if (zstd_is_error(remaining)) {
+			ret = -EIO;
+			goto out;
+		}
+
+		if (outbuf.pos == pos) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (outbuf.pos != outbuf.size) {
+			if (remaining) {
+				ret = -EINVAL;
 				goto out;
 			}
+			break;
+		}
 
-			total_out += outbuf.pos;
+		total_out += outbuf.pos;
+		acomp_walk_done_dst(&walk, outbuf.pos);
 
-			acomp_walk_done_dst(&walk, outbuf.pos);
-		} while (inbuf.pos != scur);
+		if (!remaining) {
+			outbuf.pos = 0;
+			break;
+		}
 
-		acomp_walk_done_src(&walk, scur);
-	} while (ret == 0);
+		ret = zstd_acomp_next_dst(&walk, &outbuf);
+		if (ret)
+			goto out;
+	}
+
+	if (outbuf.pos) {
+		total_out += outbuf.pos;
+		acomp_walk_done_dst(&walk, outbuf.pos);
+	}
 
 out:
 	if (ret)

base-commit: 1f318b96cc84d7c2ab792fcc0bfd42a7ca890681
-- 
2.34.1


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

end of thread, other threads:[~2026-03-20 20:44 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09  8:20 [PATCH] crypto: zstd - fix segmented acomp streaming paths Wesley Atwell
2026-03-19 22:43 ` Giovanni Cabiddu
2026-03-20 20:44   ` Wesley Atwell

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