From nobody Sat Apr 4 05:56:03 2026 Received: from mail-ot1-f51.google.com (mail-ot1-f51.google.com [209.85.210.51]) (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 D033137188A for ; Fri, 20 Mar 2026 21:51:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774043492; cv=none; b=WZ2fPQrQbVPFhnn6DUANp7wfrkpGL+MfMstSo+BoiMWO41LVRafqbgnZumQ9y1xNGq5uMiEy1Nn9Yj+Ur9qgBcMmfEpvJcxCCEdwA/cmYeIQti/L/+X+TuTkMT5TR7er+VWrDSNDcktNXxVEZxqpTaU9SfwVH4loN+iXz10UbTg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774043492; c=relaxed/simple; bh=M5uuJj6H5j5o7MY8GZQVZDKOwVD70YVutRRi674j9vc=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=k1m3JzeYePJmXfw/SC/a6vRBlRrwjUIpjAZS0vLpKmuYFMbLLaoMQKd5Iw1V1jzJPc5ZY5etujbAT5C99uueIxVt3VL6wCXCxRIotEleuNHOFbxcoHq0UYt7SvFZC7PuprLCn78J/NiEi+kpCi2rJjSOY/7ehopFNMTI2GxGiXg= 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=eWB/5e/+; arc=none smtp.client-ip=209.85.210.51 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="eWB/5e/+" Received: by mail-ot1-f51.google.com with SMTP id 46e09a7af769-7d77b179b52so1826358a34.2 for ; Fri, 20 Mar 2026 14:51:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1774043490; x=1774648290; 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=IGXNy+HMPOOSjFyvvOU7ENU9RYAs70/3AGVGPc75sz8=; b=eWB/5e/+QfmQn2wqnPfrJKLNaJnO0BVsNc6WL/xqo4+Bhou/8ETrtxr5hEZ2vYy/E8 Jk1kyQrwwhHQlJVJaZHpa23zBJAEHhFcaH3A5/JtIWSiLUw5VbKYO49eqCutqRiIcheJ lvYiLwZrsqB2LXQwHvtrqATr90WpBUaUzgjiYv0i6+yB0biFWcW9Yn2tmzqhKGKkg2b8 7EqKWwk93H1p3HeWtiC2MAQd2IyXW7BN85h/uAQzBVpZ9AcOL6qHI+T2+6eimDE0a0Jm BL1Km/vDkFDWBNxfX3iWa1q+pZPDkZVq6UhcMweS1GWdt4iPakMhOb3s/93SBWbFpfsM 9/+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774043490; x=1774648290; 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=IGXNy+HMPOOSjFyvvOU7ENU9RYAs70/3AGVGPc75sz8=; b=tH+2xu6NEPSaYSBB7zvphlb68l+vmyo++N8dKcqwjf2dYKRZLb90Md+35QFWkc5f/3 RKwJiuQ2Tf+23iRTs3dnrB/EkzuI9oVoWVzRlXL1o3GHErVwuwDDV99JtluOShEf5CYB qWXSLFylgDwFBRDzq0EU1MDuiF1zSiA+n0A8JE17s5PTb63eO8RBtpJ+KUFg6V1jnbcf roKsDRndlBJMfPdr5Og74ckKZagqZiplUAKMLoYdMGt+LTtxA6bGCnYGII50KNy/Q7nm VvRALnmXTCkPYAYy7d0m3XM5PZmM1vmrAxAOykoofmv+kGdCQQmsLO7wWRBC3kOhFJ/q 2vtQ== X-Forwarded-Encrypted: i=1; AJvYcCVMoPG35luhTBiMlINqcvm1mXBBVcZ+eFZsmrzcmJxfSR48ZznuwF5m33TrbuIrrwPjJCgrKoa9lbFUrC8=@vger.kernel.org X-Gm-Message-State: AOJu0YxTis3gFcGB9XFH98mXBDoskZMecVvM14nTXUNOTssjG8zP6ZIT PLOW70raDzRnWuXlf8POABlNEAaf3bIOZhe0nbZCh6apDeTCVa1V+Zrd X-Gm-Gg: ATEYQzwApedoeUrv7vHZ12VLHW9KDiRJ4gzmU2bUSjbWIbBxtRHxwYj49Hm/RskxZEe yAggvR4ZWlYvoNoXjpGMt5mt10rpjv53Hj9mfI1ANvVoxl7rgPf7QnXz93jGTsYhJ5LU+3ZB5e3 s3cFsWhXPD0X/7QVRzjbyXET/w/S7uy//RUmzE0XvRlxD09xQdO99gcmJ0lInYeuyWsACOIEYuq fWIWR8KphAMK+PjRpEZtAXE9XefxuifoFva1f+M6qkh6A0lfdhqdRPTJcq5wc+WUw5EHXikW8pk tFG1c3GUGBM7tE5CAWUIxfEQrOocMiANoxufZ3e+67W3TpWK7fMLa75LxXd9mvj28va/92DuAKt b/ZJbun5Hasd5m1+bMq/wcNrO0oUD7T0nPM7n/z8688J6SlsasDp4zHotkeJFfyOFU6djWKKZ1C K+kmpwp4U9hBA+Vq+kf93K4PyIsieTNqkEne4Op1L+puLhn7eRsa+nSOBU+y4klR/e9Z7riwY27 nUC5vM1Widv9gYB1+SbTVwFnEv2Rs1xCxKhYaV4Kw== X-Received: by 2002:a05:6830:83a9:b0:7d7:c911:1829 with SMTP id 46e09a7af769-7d7eaf6c25amr3520951a34.16.1774043489663; Fri, 20 Mar 2026 14:51:29 -0700 (PDT) Received: from Atwell-Laptop.. (108-212-132-20.lightspeed.irvnca.sbcglobal.net. [108.212.132.20]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7d7eadce96dsm3172347a34.18.2026.03.20.14.51.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 14:51:29 -0700 (PDT) From: Wesley Atwell To: Herbert Xu , "David S. Miller" Cc: Nick Terrell , David Sterba , Suman Kumar Chakraborty , Giovanni Cabiddu , linux-crypto@vger.kernel.org, linux-kernel@vger.kernel.org, Wesley Atwell Subject: [PATCH v2] crypto: zstd - fix segmented acomp streaming paths Date: Fri, 20 Mar 2026 15:51:24 -0600 Message-ID: <20260320215124.389938-1-atwellwea@gmail.com> X-Mailer: git-send-email 2.43.0 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 --- Changes in v2: - always finalize acomp walk mappings in the direct one-shot paths - add mapped src/dst cleanup on streaming error exits - reacquire a destination segment in decompression before resuming after a full output-chunk completion Local validation: - built bzImage with CONFIG_CRYPTO_SELFTESTS=3Dy and CONFIG_CRYPTO_SELFTESTS_FULL=3Dy - exercised segmented zstd acomp requests using temporary local testmgr scaffolding - booted under virtme and verified zstd-generic selftest passed in /proc/crypto diff --git a/crypto/zstd.c b/crypto/zstd.c index 556f5d2bdd5f..3fb04acf6b7f 100644 --- a/crypto/zstd.c +++ b/crypto/zstd.c @@ -94,18 +94,32 @@ 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; + bool src_mapped =3D false; + bool dst_mapped =3D false; 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); @@ -121,60 +135,102 @@ static int zstd_compress(struct acomp_req *req) goto out; } =20 - do { - dcur =3D acomp_walk_next_dst(&walk); - if (!dcur) { - ret =3D -ENOSPC; + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + dst_mapped =3D true; + + for (;;) { + scur =3D acomp_walk_next_src(&walk); + src_mapped =3D scur; + 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); + acomp_walk_done_src(&walk, scur); + src_mapped =3D false; + acomp_walk_done_dst(&walk, ret ? outbuf.size : total_out); + dst_mapped =3D false; 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); - goto out; + remaining =3D zstd_compress_stream(ctx->cctx, &outbuf, &inbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; + goto out_free_walk; } =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) { + if (inbuf.pos =3D=3D inbuf.size) + break; + continue; } =20 - num_bytes =3D zstd_compress_stream(ctx->cctx, &outbuf, &inbuf); - if (ZSTD_isError(num_bytes)) { - ret =3D -EIO; - goto out; + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + dst_mapped =3D false; + + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out_free_walk; + dst_mapped =3D true; + } while (inbuf.pos !=3D inbuf.size); + + acomp_walk_done_src(&walk, inbuf.pos); + src_mapped =3D false; + } + + for (;;) { + remaining =3D zstd_end_stream(ctx->cctx, &outbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; + goto out_free_walk; + } + + if (outbuf.pos =3D=3D outbuf.size) { + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + dst_mapped =3D false; + + 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); + dst_mapped =3D true; + + continue; + } + + if (!remaining) + break; + } =20 + if (outbuf.pos) { total_out +=3D outbuf.pos; - acomp_walk_done_dst(&walk, dcur); - } while (data_available); + acomp_walk_done_dst(&walk, outbuf.pos); + dst_mapped =3D false; + } =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); +out_free_walk: + if (src_mapped) + acomp_walk_done_src(&walk, inbuf.pos); + if (dst_mapped) + /* + * The streaming helpers can fail after dirtying part of the + * current destination chunk while leaving outbuf.pos stale. + */ + acomp_walk_done_dst(&walk, ret ? outbuf.size : outbuf.pos); =20 out: if (ret) @@ -209,12 +265,14 @@ 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; + bool src_mapped =3D false; + bool dst_mapped =3D false; 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); @@ -230,48 +288,128 @@ static int zstd_decompress(struct acomp_req *req) goto out; } =20 - do { + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + dst_mapped =3D true; + + 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 { + src_mapped =3D scur; + if (!scur) break; + + inbuf.pos =3D 0; + inbuf.size =3D scur; + inbuf.src =3D walk.src.virt.addr; + + if (!dst_mapped) { + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out_free_walk; + dst_mapped =3D true; + } + + 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); + acomp_walk_done_src(&walk, scur); + src_mapped =3D false; + acomp_walk_done_dst(&walk, ret ? outbuf.size : total_out); + dst_mapped =3D false; + goto out; } =20 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); - goto out; + remaining =3D zstd_decompress_stream(ctx->dctx, &outbuf, + &inbuf); + if (zstd_is_error(remaining)) { + ret =3D -EIO; + goto out_free_walk; } =20 - if (!dcur) { - ret =3D -ENOSPC; - goto out; + if (outbuf.pos !=3D outbuf.size) { + if (inbuf.pos =3D=3D inbuf.size) + break; + continue; } =20 - outbuf.pos =3D 0; - outbuf.dst =3D (u8 *)walk.dst.virt.addr; - outbuf.size =3D dcur; + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + dst_mapped =3D false; =20 - pending_bytes =3D zstd_decompress_stream(ctx->dctx, &outbuf, &inbuf); - if (ZSTD_isError(pending_bytes)) { - ret =3D -EIO; - goto out; + if (!remaining) { + outbuf.pos =3D 0; + break; } =20 - total_out +=3D outbuf.pos; + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out_free_walk; + dst_mapped =3D true; + } while (inbuf.pos !=3D inbuf.size); =20 - acomp_walk_done_dst(&walk, outbuf.pos); - } while (inbuf.pos !=3D scur); + acomp_walk_done_src(&walk, inbuf.pos); + src_mapped =3D false; + } + + 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_free_walk; + } + + if (outbuf.pos =3D=3D pos) { + ret =3D -EINVAL; + goto out_free_walk; + } + + if (outbuf.pos !=3D outbuf.size) { + if (remaining) { + ret =3D -EINVAL; + goto out_free_walk; + } + break; + } + + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + dst_mapped =3D false; + + if (!remaining) { + outbuf.pos =3D 0; + break; + } + + ret =3D zstd_acomp_next_dst(&walk, &outbuf); + if (ret) + goto out; + dst_mapped =3D true; + } + + if (outbuf.pos) { + total_out +=3D outbuf.pos; + acomp_walk_done_dst(&walk, outbuf.pos); + dst_mapped =3D false; + } =20 - acomp_walk_done_src(&walk, scur); - } while (ret =3D=3D 0); +out_free_walk: + if (src_mapped) + acomp_walk_done_src(&walk, inbuf.pos); + if (dst_mapped) + /* + * The streaming helpers can fail after dirtying part of the + * current destination chunk while leaving outbuf.pos stale. + */ + acomp_walk_done_dst(&walk, ret ? outbuf.size : outbuf.pos); =20 out: if (ret) base-commit: 1f318b96cc84d7c2ab792fcc0bfd42a7ca890681 --=20 2.34.1