From nobody Mon Apr 13 05:38:08 2026 Received: from mail-ot1-f52.google.com (mail-ot1-f52.google.com [209.85.210.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F85C36AB51 for ; Mon, 9 Mar 2026 08:21:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773044499; cv=none; b=s4y2x/TYhB1KFnJPaEGOfE7I4ZUTA4tlMGJVaW+S8cLBq0tijX7iSsToUAx0MNICKm5rtMjp2sZxFwku5odnzLSnZQ/8LXlLuANghpBSLnPgrmSHUF1BO3E0/At8dso6TpQ/4xjuN4KkDYdfZiitH1wcQlIuM0X5TZAFquoG4ws= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773044499; c=relaxed/simple; bh=ae8oC6M24RmGbgaPYEriiD8mhYaapi3fMrA0MRSkwkU=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=TeHss58hVSALvL1nB7Yl9saT4vtsYmeyRBjtF3c6f92X09p+2uf8+AELsQjYogPSxzuSWr2RHGAbJuIoJceQKaYDs9STPE6NCfIbNBCEphunPZqs5TPyg4F9hlwlgYw7cnrYCxYtQZWPlSGC1w+MDy16epEL4+3/tgenV83bgY8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=HlQPjY6X; arc=none smtp.client-ip=209.85.210.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="HlQPjY6X" Received: by mail-ot1-f52.google.com with SMTP id 46e09a7af769-7d7507327d7so285778a34.3 for ; Mon, 09 Mar 2026 01:21:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773044495; x=1773649295; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=LYplohEROQ/aWKluUhy32Hw7e1z7Llc/p+3/sITdRrk=; b=HlQPjY6Xfhd0NjJbKzHiTYJcpRMvCEEt+LLsHF4Cwu19IuN+8rVoCUM6yakW9JHOrS xzg2o0JMTX9M63EME6ZdyyA7ghzTMl0HQN4SxQrAtJrl7hMwQOwDu+0dOhooNpaqF4Al BrVHBFCXYtMGU+Yuv6BRm07X7CoZtUeqTRIeAxCXppufM6eKIeWKD3D0lgKwGE1+L3t2 V015sOwFM/hC46p6UculaN+MpmnNOZrxrwhRIPw8a3NaLXVbi44tdT+iqCOZ1HYb9iXi Y4xi3huoHE295uapYo6qgvKXjXxXABXYJPq6DJct3Ljx+M0493/5b8EZu//H12RTJXgz Lr6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773044495; x=1773649295; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=LYplohEROQ/aWKluUhy32Hw7e1z7Llc/p+3/sITdRrk=; b=O3lr/rg/Lcqj+YkFZ3sk68OmXNJMNwVh1+VhopmHlEX6QCNlKDlUk5ho86oWChvwLR N655Z7mlRBDR1u1yXp8Ecee/Lvof9F2qJpMLrugUzFmuKhp46ghYil6104qXm7qthB7E 2sEb3xzxX2TgXcikDGaE6j2AW+k/70wWB7Ikge4Wb6tNKhTzMu4HsomFmm6/puyj/FmN A8sGWMyvJ8NdUiHQN/mRrm03vgpxa8lSaFcuJztVbYGO6QYDeClCnNP/hX3E6NFsTpE3 qYb6pLCa9UGJCWeJMN3LTw5w8oFdG40j25NWGIhP5OgMrzltuXuMlWg1DPjgCuQNfXmZ mYLQ== X-Forwarded-Encrypted: i=1; AJvYcCVF+LBQcUr+w8aLnMHjA8WuUrtF9h8/vgEI++WJLMhGtb2FsKiyhi1DgHUGrRq3dGfsZ5NmNoidNr1Xdk8=@vger.kernel.org X-Gm-Message-State: AOJu0YyIxS1Nsxvc135mSoofPW7FHMxgfYDZhSRyFSVOGdiWqxnZrYIl LTDUTYq1/9649vxUR90UwpqWbk7hwBdARVkzmHCOHiwr8yWgbFQxjFcH X-Gm-Gg: ATEYQzzgB1EtYjD/mxryVlz9FYf4Jafc/kfabPOZtyjH4gpVvrZqWuoJ1SsavLMI22q oOga9tTHXFrvPBqilFJ4b9kKPDz+mebbpPX/MLQp81eJ1FzUcRHvqTs5tAa9ibefQkYFQnDNrPJ BQ2IidupxXhVwSsZWo9zR1q8trlsDor+/LfHQurR2pDTTdQ1ib+LbgwqpE5yZn/vh+NBZfhRBtp njAQoedfs+pVAw6xkjl0UQ2K/iV8tmqhWhtwPHoyLQuxXBmHCBKogtG5E8bMB0H3BW7PlzrYr/0 7TcF1HOwrlhTTipMA99CBMV9BF08QAf+ByndtTricJ1P/LZv7szrvt4OCy+sRWrP+0vHKGe7T8f F+iQsMYg8fOgr9sUO0oeHt9ph8X29qBQ93ACGBP1YMVr+nefBnvmilrL/vsmqfBGKNZ7s6sm+/F J0lS55kEHs2g7IFG4zrK0IaJR+MuKKn0hIoX2mwvw2/V+4O3eibW35yJCLbZl7h41Adz+0yCI5i POAQTYz5GANbF1516FxDFJgCxOJTbenjvb3wEuLaCEPh6oS X-Received: by 2002:a05:6830:d18:b0:7c7:2c3c:690e with SMTP id 46e09a7af769-7d72707f403mr5596943a34.35.1773044495036; Mon, 09 Mar 2026 01:21:35 -0700 (PDT) Received: from localhost.localdomain (108-212-132-20.lightspeed.irvnca.sbcglobal.net. [108.212.132.20]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d74a4cdd70sm1709788a34.12.2026.03.09.01.21.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 09 Mar 2026 01:21:34 -0700 (PDT) From: Wesley Atwell To: herbert@gondor.apana.org.au, davem@davemloft.net, terrelln@fb.com, dsterba@suse.com, giovanni.cabiddu@intel.com, suman.kumar.chakraborty@intel.com Cc: linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, Wesley Atwell Subject: [PATCH] crypto: zstd - fix segmented acomp streaming paths Date: Mon, 9 Mar 2026 02:20:51 -0600 Message-Id: <20260309082051.2087363-1-atwellwea@gmail.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- Local validation: - built bzImage with CONFIG_CRYPTO_SELFTESTS=3Dy and CONFIG_CRYPTO_SELFTEST= S_FULL=3Dy - exercised segmented zstd acomp requests using temporary local testmgr sca= ffolding - booted under virtme and verified zstd-generic selftest passed in /proc/cr= ypto 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, str= uct zstd_ctx *ctx, return 0; } =20 +static int zstd_acomp_next_dst(struct acomp_walk *walk, zstd_out_buffer *o= utbuf) +{ + unsigned int dcur =3D acomp_walk_next_dst(walk); + + if (!dcur) + return -ENOSPC; + + outbuf->pos =3D 0; + outbuf->dst =3D walk->dst.virt.addr; + outbuf->size =3D 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 =3D 0; - bool data_available =3D 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; =20 s =3D crypto_acomp_lock_stream_bh(&zstd_streams); @@ -115,66 +127,87 @@ static int zstd_compress(struct acomp_req *req) if (ret) goto out; =20 + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + ctx->cctx =3D zstd_init_cstream(&ctx->params, 0, ctx->wksp, ctx->wksp_siz= e); if (!ctx->cctx) { ret =3D -EINVAL; goto out; } =20 - do { - dcur =3D acomp_walk_next_dst(&walk); - if (!dcur) { - ret =3D -ENOSPC; + for (;;) { + scur =3D acomp_walk_next_src(&walk); + if (outbuf.size =3D=3D req->dlen && scur =3D=3D req->slen) { + ret =3D 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; } =20 - outbuf.pos =3D 0; - outbuf.dst =3D (u8 *)walk.dst.virt.addr; - outbuf.size =3D dcur; + if (!scur) + break; + + inbuf.pos =3D 0; + inbuf.src =3D walk.src.virt.addr; + inbuf.size =3D scur; =20 do { - scur =3D acomp_walk_next_src(&walk); - if (dcur =3D=3D req->dlen && scur =3D=3D req->slen) { - ret =3D 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 =3D zstd_compress_stream(ctx->cctx, &outbuf, &inbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; goto out; } =20 - if (scur) { - inbuf.pos =3D 0; - inbuf.src =3D walk.src.virt.addr; - inbuf.size =3D scur; - } else { - data_available =3D false; - break; - } + if (outbuf.pos !=3D outbuf.size) + continue; =20 - num_bytes =3D zstd_compress_stream(ctx->cctx, &outbuf, &inbuf); - if (ZSTD_isError(num_bytes)) { - ret =3D -EIO; + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) goto out; + } while (inbuf.pos !=3D inbuf.size); + + acomp_walk_done_src(&walk, inbuf.pos); + } + + for (;;) { + remaining =3D zstd_end_stream(ctx->cctx, &outbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; + goto out; + } + + if (outbuf.pos =3D=3D outbuf.size) { + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + + if (!remaining) { + outbuf.pos =3D 0; + break; } =20 - pending_bytes =3D zstd_flush_stream(ctx->cctx, &outbuf); - if (ZSTD_isError(pending_bytes)) { - ret =3D -EIO; + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) goto out; - } - acomp_walk_done_src(&walk, inbuf.pos); - } while (dcur !=3D outbuf.pos); =20 - total_out +=3D outbuf.pos; - acomp_walk_done_dst(&walk, dcur); - } while (data_available); + continue; + } =20 - pos =3D outbuf.pos; - num_bytes =3D zstd_end_stream(ctx->cctx, &outbuf); - if (ZSTD_isError(num_bytes)) - ret =3D -EIO; - else - total_out +=3D (outbuf.pos - pos); + if (!remaining) + break; + } + + if (outbuf.pos) { + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + } =20 out: if (ret) @@ -209,12 +242,12 @@ static int zstd_decompress(struct acomp_req *req) { struct crypto_acomp_stream *s; unsigned int total_out =3D 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 =3D 1; int ret; =20 s =3D crypto_acomp_lock_stream_bh(&zstd_streams); @@ -224,54 +257,105 @@ static int zstd_decompress(struct acomp_req *req) if (ret) goto out; =20 + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + ctx->dctx =3D zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ctx->wksp_size); if (!ctx->dctx) { ret =3D -EINVAL; goto out; } =20 - do { + for (;;) { scur =3D acomp_walk_next_src(&walk); - if (scur) { - inbuf.pos =3D 0; - inbuf.size =3D scur; - inbuf.src =3D walk.src.virt.addr; - } else { - break; + if (outbuf.size =3D=3D req->dlen && scur =3D=3D req->slen) { + ret =3D 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; } =20 + if (!scur) + break; + + inbuf.pos =3D 0; + inbuf.size =3D scur; + inbuf.src =3D walk.src.virt.addr; + do { - dcur =3D acomp_walk_next_dst(&walk); - if (dcur =3D=3D req->dlen && scur =3D=3D req->slen) { - ret =3D 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 =3D zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; goto out; } =20 - if (!dcur) { - ret =3D -ENOSPC; - goto out; + if (outbuf.pos !=3D outbuf.size) + continue; + + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + + if (!remaining) { + outbuf.pos =3D 0; + break; } =20 - outbuf.pos =3D 0; - outbuf.dst =3D (u8 *)walk.dst.virt.addr; - outbuf.size =3D dcur; + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + } while (inbuf.pos !=3D scur); =20 - pending_bytes =3D zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf); - if (ZSTD_isError(pending_bytes)) { - ret =3D -EIO; + acomp_walk_done_src(&walk, inbuf.pos); + } + + inbuf.pos =3D 0; + inbuf.size =3D 0; + inbuf.src =3D NULL; + + /* Drain any buffered output after the source walk is exhausted. */ + while (remaining) { + size_t pos =3D outbuf.pos; + + remaining =3D zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; + goto out; + } + + if (outbuf.pos =3D=3D pos) { + ret =3D -EINVAL; + goto out; + } + + if (outbuf.pos !=3D outbuf.size) { + if (remaining) { + ret =3D -EINVAL; goto out; } + break; + } =20 - total_out +=3D outbuf.pos; + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); =20 - acomp_walk_done_dst(&walk, outbuf.pos); - } while (inbuf.pos !=3D scur); + if (!remaining) { + outbuf.pos =3D 0; + break; + } =20 - acomp_walk_done_src(&walk, scur); - } while (ret =3D=3D 0); + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + } + + if (outbuf.pos) { + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + } =20 out: if (ret) base-commit: 1f318b96cc84d7c2ab792fcc0bfd42a7ca890681 --=20 2.34.1