From nobody Mon Jun 8 07:22:47 2026 Received: from mail-wr1-f46.google.com (mail-wr1-f46.google.com [209.85.221.46]) (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 950C7233936 for ; Sun, 31 May 2026 23:42:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780270945; cv=none; b=AyPsVcbAw7rrjgxzcfqQB7Zl1qBO++T3DJFnaeGT9C9MpZbSmdgtyI6TPXj7mnpTnMNFQcBVq7JtwaE5vReCWb89zEUKSUDnenOrFfdesy8LbajWbcRLy3o8fm8/pFLkzMNh18FZZBbUP5guqKi9rOMc7u26rowNEITEv4YcUhc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780270945; c=relaxed/simple; bh=ZdhCfU2NCRfkFTPirJO2EahHOFjwRR2cXrXkEpHHKNQ=; h=Date:From:To:Subject:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=Lc+QP7H0cKnHW4JCQ/BYuk8fDp0tUioz1fpK5+kd5sL9V7kyiIWA1LCdNZvy1MhsOELPCBtYXPUC+vcS+3Y+QeZWtvim16gT81Shf5c63TkpE+BKw/DAWKgp71CPiQfDzDgB77vbd5S/tijDsbPWJFykb7Uaacp44K2/x+NfYog= 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=r7/Amuv7; arc=none smtp.client-ip=209.85.221.46 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="r7/Amuv7" Received: by mail-wr1-f46.google.com with SMTP id ffacd0b85a97d-45ee1a56328so4001163f8f.3 for ; Sun, 31 May 2026 16:42:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780270942; x=1780875742; darn=vger.kernel.org; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:subject:to:from:date:from:to:cc:subject:date:message-id :reply-to; bh=zsfWlGncHQgiiYO7yjj/ulRFmjF/DIPVayuRQPsVfhY=; b=r7/Amuv7iskUF0IM9AJaewyfaNF/rhEUxYSUwyPPD2vTHwDf4TYvzlwoDhzKqYChcl WvIJ17SnuDL4e6ITGYL7Fb4v7w5+kUJr4/lNomNVLIgJL+TQQUx/YYzp9ap5PoFucTRs rw/NB6TspQmk+QQWiNOipzf0ngfY4Sai4MGTtBIU/gHhOJY6CYKa+JAqe4+2crRA6ZFJ ysbr1Tk13bYH2rsxP9cl7g225xgTpAOXd74MjK+OVo/04T+AwMI20Z552x/AWdqCxN3p omMtNtqoMlRggg0Ck8LZ7vRc04CMgHHrKTzHOEdp9fYXXjElOk16mCAqTEtO+vWkqVOn vb3A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780270942; x=1780875742; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:subject:to:from:date:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=zsfWlGncHQgiiYO7yjj/ulRFmjF/DIPVayuRQPsVfhY=; b=VQqhZVrkp9zvfwc9B+tSWW/7s2neIc+8lwXJsyB2PoXSvThc1O2VyyMb0vpSld7e0R oQW8EHXBdC/BCXJEC3Y4wOyNWmvQZOC10EUPi3EUodIqwSLS+4+A95BFZcp/H3ZtMYU2 9y6P7UB5dg1fLovHSiWXk5yujDMr/V4V6wnokLH8W6iL31IqVK8mvUqfrWvPzOEkBubT gd8pWVlJMnzN1yuKttARXS3Otlq+3Ys/iOp8N8zl/a4b4ju546Jl1Ty2jEGCK1hJ54wb /QBSzONBMLs1mMhzjnDZdxo2wXJXgNfZGAa3U2sE5GJ7FB73yqWjQtwLA7EE3dFQdzjG +/lA== X-Forwarded-Encrypted: i=1; AFNElJ8hR5u0HYYsZGQrM5m/JoFX70xvnsn5YuTnEI0FL0FqfagBtTTG9svoDKl5LCdJZ0xOvmIbmxoVsMuiJ7Y=@vger.kernel.org X-Gm-Message-State: AOJu0YzhDveWdorQ8EyiykIjGTNo8L9WK9YxUGtNENVcWNlRy5/0yc1A LrYe1BZ5kW72HhUu2zRaYUgCW0j+++fLHQ/ijhTKcGpMWVUvzAsj5eECg6uLTM5N X-Gm-Gg: Acq92OG4bVuSnL9YNGnHnLV76LGbQLcdhOyh0nMTE2cq2NkGbN2oxmwuwEWDnRbRjI5 jWTAai0IXIlFNxuUttmhwNRlCXPTRKDXqJ+H1Ye54Vvf/mBVtkqH5raZe6UYs9hAqgumB3A0Bav l+sjTWLsZCMN6MRZOjQWR40waEGxIdaJFgEZ71sRuy2NXxoKP/vCaaJ65stuq+58l8GTeHVtcNB HvZbe0P9UqS6yGvwmGPuaERKF2kDbFAOZfVwT6zsguiHsePeq11ZvuVZ+kMXX+Nx1n2WS0267Kp c/65VfEztb2nVpn/l33bxh9VnyiZ4b0yIHsRkTpP3Qytuq0shhLBoQiBgbCGNQHUWygPht+WcUH vLPnqBVX4fWvudsfUzBOZ5Blloe/Z49uEg59kf3XFTHFXOfhikj2hdyCQbCS4a/3HOHEX0rWb2P 7b/RTtFBFr4JlZj3fAgiOLXSHUPGTQZzvo5TVGroB9+RNXIQ== X-Received: by 2002:a5d:6049:0:b0:45e:ea68:5237 with SMTP id ffacd0b85a97d-45ef6b138e9mr12774487f8f.11.1780270941936; Sun, 31 May 2026 16:42:21 -0700 (PDT) Received: from [127.0.0.1] ([141.255.129.1]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45ef354cf0dsm21196831f8f.17.2026.05.31.16.42.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 31 May 2026 16:42:21 -0700 (PDT) Date: Sun, 31 May 2026 16:42:21 -0700 (PDT) From: Charles Pellegrini To: akpm@linux-foundation.org, egorenar-dev@posteo.net, robert.jarzmik@free.fr, t-pratham@ti.com, linux-kernel@vger.kernel.org Subject: [PATCH 1/2] lib: scatterlist: fix sg_calculate_split() nb_splits overshoot on partial coverage Message-ID: <178027099087.72481.15914006421095162619@gmail.com> In-Reply-To: <178027099087.72481.1976843064458686851@gmail.com> References: <178027099087.72481.1976843064458686851@gmail.com> Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 sg_calculate_split() decrements nb_splits in two places per outer-loop iteration: once in the inner while() that advances to the next output split when a split boundary falls mid-entry, and once in the trailing if (!size && --nb_splits > 0) The loop-termination check only tests for nb_splits =3D=3D 0: if (!nb_splits) break; When the final requested split ends mid-entry of a non-last input entry, both decrements run with their bodies skipped: the inner while() takes nb_splits 1 -> 0, then the trailing if() takes it 0 -> -1. The termination check then sees -1, not 0, so the loop does not break and the next input entry is folded into the last split, inflating its nents and length_last_sg. The resulting split covers more bytes than requested. For in=3D[2, 1], skip=3D0, nb_splits=3D1, sizes=3D[1] the single output spl= it reports a total length of 2 instead of 1. Widen the termination check to also catch the overshoot to -1: if (nb_splits <=3D 0) break; The new condition only fires when nb_splits is already negative, which is reachable solely through the double decrement above, so it cannot change behaviour for any input that splits correctly today. This was first reported by Alexander Egorenkov in 2021, with an identical root-cause analysis; that patch was never reviewed or merged. Its proposed fix moved the existing termination check above the second decrement rather than widening it. That handles the mid-entry case but regresses edge-aligned partial coverage -- a split ending exactly on an input-entry boundary with further entries trailing -- which the current code handles correctly. The KUnit test added in the following patch (sg_split_t10_edge_first_two_trailing) covers that regression. No in-tree caller triggers the bug today: four request full coverage or re-split over a DMA-mapped subset, and the one caller that requests partial coverage (the DTHEv2 AEAD driver) bounds its consumer by cryptlen, masking the wrong geometry. The fix matters for callers that legitimately request partial coverage, which the API has documented as supported since the original commit ("the union of spans of all resulting scatter lists is a subrange of the span of the original scatter list"). Fixes: f8bcbe62acd0 ("lib: scatterlist: add sg splitting function") Reported-by: Alexander Egorenkov Closes: https://lore.kernel.org/all/20210418143425.22944-1-egorenar-dev@pos= teo.net/ Assisted-by: Claude:claude-opus-4-8 hegel-c Signed-off-by: Charles Pellegrini --- lib/sg_split.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sg_split.c b/lib/sg_split.c index 24e8f5e48e63..28fe7fa6c9ac 100644 --- a/lib/sg_split.c +++ b/lib/sg_split.c @@ -67,7 +67,7 @@ static int sg_calculate_split(struct scatterlist *in, int= nents, int nb_splits, size =3D *(++sizes); } =20 - if (!nb_splits) + if (nb_splits <=3D 0) break; } =20 --=20 2.47.3 From nobody Mon Jun 8 07:22:47 2026 Received: from mail-wr1-f43.google.com (mail-wr1-f43.google.com [209.85.221.43]) (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 BF66A3806B5 for ; Sun, 31 May 2026 23:42:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780270946; cv=none; b=mEYUYJsNzle7hXFOVJ6waEvWeMRlPT2kC2xAcFizeQRFtsTSd3pKJtvRxyS9htXZvgsVYJDQtimSkhZdwDQyrLC0BUp/bJx/mPFYATEBqOHYdkTrQbnUGzJCmX1Zvjzz2Gl43k+kWE5wFF6ZT/3nhtEGe2jMOabvDEWqNiqFUYQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780270946; c=relaxed/simple; bh=kV2cT/nwZvc2GWG2DI7VhoWTh8y2J8mCuXrVMjiFeys=; h=Date:From:To:Subject:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=f6nal+4q62AUWGx/loFlaRY2SzCLao7e1tGgfDdJg0HnNO4egKc8P4eHLNfDlLKi6JcHKNAanQgSOLPru8aP8o/FTECIwzkIwD6ojhDh8ZW32689B59Rg/GzQT/a6IEtTvnwaLItbaPX30/TQ7/xuK8h2+coHN+Unb4m2bBoBYs= 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=hYqSYc9n; arc=none smtp.client-ip=209.85.221.43 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="hYqSYc9n" Received: by mail-wr1-f43.google.com with SMTP id ffacd0b85a97d-45ef1198766so1302244f8f.0 for ; Sun, 31 May 2026 16:42:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780270943; x=1780875743; darn=vger.kernel.org; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:subject:to:from:date:from:to:cc:subject:date:message-id :reply-to; bh=PjxS8qDZcHqNUOXgq3yehgoZ/9CnDr51sxvbptbZgS0=; b=hYqSYc9ntYNFf1Awi0bKsqB1LgoUaqm8A0ckKypI7/cS81r+dkTRPWvREmJHKBhNTB 5zedUa0qepwPnDt0QIbpuDZEAH7x2lm63886B0rVgnsiceSDLKNo/TfD7GRRcNSIdhEd zjvIuVASbz4FIFdUSrvgTJyB0TSn5uWLYM6bfUHtuPC41y9vYG1tx8PRvL6mjrEvx4gT qsIPgFAauTWx4+upFDCTBUNrl4SjqO0WQTStksYpDPzO8ZtZ6Q/tGOYrG1Zjmkh6AJ+0 HgwVI2hclEzj26ziVnpt2eFTmfyz/tdtYv3UVp8DJuUs4apPVP44lHWPjy3uHyEvNhK+ xrBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780270943; x=1780875743; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:subject:to:from:date:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=PjxS8qDZcHqNUOXgq3yehgoZ/9CnDr51sxvbptbZgS0=; b=iuOVTKD9obn3UtVFcE0Bny8MDhPL3bS5OZAmCy/aaYyUCafSkJe5lTuUym8BYgFqYj gVRLTMHjZG+I7a7cMLK7Dy60bBrc0uQLc8FCA2ohWdKlnqQQ+8oq9ickP2CzqWMiWCW2 w1K/NHpAx2cImZ4O1fLR468Unx0QIFATUVDQA8x0jVuHDL4RQ68OjRvarUQLumVD/6qN ySGGYB2DNRvKTJf2WnIVb5JaBWANKT1aD3JbLNC0JMzwTHMod5n23y/LobNkpZXZl2ul fsGiar1x3v/6d/ayRUKsu4SzdyeLc3eF7wOig1wHmkrQB5Nkvzk+o3hHpdtkskYzempT xNkA== X-Forwarded-Encrypted: i=1; AFNElJ/KcO7LOaJpEYv6COnMV4rOGS0JVN3kxLks6ToFCmZYCuoQ1NaYgfodJqzdRrZWpHPBAau3R+0/eD++gGg=@vger.kernel.org X-Gm-Message-State: AOJu0YwX7XdsX18u2Wwom7YIz8hZykNqvsX545XqgVncvq4JrBseJ//s CnAr/vyNpe01mMfZqRMKI5bTptU00htDnJyfZf+UeLnXmv5w93t8/FQH X-Gm-Gg: Acq92OF38b+z+ijcWy2YVIoQJR13xD8khZFH3j8++/r1wA0d1HXVWNYpOgSqYmkZYpb LwLj1qyGcN+gdAsOp6qZ8i2kR3fxQJ4stpAJ0MpTQXVotMUafADSU50pbAvUWxGi8c5ISd04d/I ZxU9gaTa8IAOgoea0uMHwGuoHpHfwzZ6NusjhODkoWQuf9nZbME0CagRXV7CFRpFWcoigBuzFZk hAVsD3dN66aMOIaovWA76rNT/t0AXbmHaAfjurowzjkK0LZ+guAau2+pNEu9X0Ob28eneXjUnei DYgqv8a+/N0gKkx2VgmErszp+q9YmPh9fsHDZtv5zy07IjegnkRRo2lyBl/fzRzAtyJYmGd2nHy C8F9cObgkHJSNQr/9+AjmDlKAFYnUzhNB3ZUZqqxZ+qD7LWp8Bebs1g/OfVwZBjVh9+9AQYfOfs MFzhn7LRBN6GyT3IMgazE9nUze5J8WqT2qKf/hE00v/pDBLA== X-Received: by 2002:a5d:5e11:0:b0:460:10f6:a554 with SMTP id ffacd0b85a97d-46010f6a9d9mr2176002f8f.14.1780270943070; Sun, 31 May 2026 16:42:23 -0700 (PDT) Received: from [127.0.0.1] ([141.255.129.1]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45ef354cf0dsm21196831f8f.17.2026.05.31.16.42.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 31 May 2026 16:42:22 -0700 (PDT) Date: Sun, 31 May 2026 16:42:22 -0700 (PDT) From: Charles Pellegrini To: akpm@linux-foundation.org, egorenar-dev@posteo.net, robert.jarzmik@free.fr, t-pratham@ti.com, linux-kernel@vger.kernel.org Subject: [PATCH 2/2] lib: scatterlist: add KUnit tests for sg_split() Message-ID: <178027099087.72481.6024679185366189129@gmail.com> In-Reply-To: <178027099087.72481.1976843064458686851@gmail.com> References: <178027099087.72481.1976843064458686851@gmail.com> Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add a KUnit suite for lib/sg_split.c covering the (input shape, skip, split sizes) matrix: full coverage, edge-aligned partial coverage, mid-entry partial coverage at three boundary positions across one to three input entries, skip-position variants (skip=3D0, at an exact entry edge, and a mid-entry fast-forward), and multi-split runs with mid-entry transitions. A degenerate sizes-contains-zero case is also exercised. Each case checks the two documented post-conditions of sg_split(): that the sum of each output split's entry lengths equals the requested split size, and that the bytes exposed by each split match the corresponding region of the input. Backing memory uses a position-unique byte pattern so a content mismatch pinpoints which input region was mis-exposed. Against the nb_splits overshoot fixed in the previous patch, the mid-entry partial cases (e.g. sg_split_t5_minimal_repro) fail without the fix and pass with it. sg_split_t10_edge_first_two_trailing additionally guards against the regression introduced by the 2021 fix proposal, which mishandled edge-aligned partial coverage. Run with: tools/testing/kunit/kunit.py run sg_split Assisted-by: Claude:claude-opus-4-8 hegel-c Signed-off-by: Charles Pellegrini --- lib/Kconfig | 13 ++ lib/Makefile | 1 + lib/test_sg_split_kunit.c | 371 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 lib/test_sg_split_kunit.c diff --git a/lib/Kconfig b/lib/Kconfig index 00a9509636c1..93276051336b 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -52,6 +52,19 @@ config PACKING_KUNIT_TEST =20 When in doubt, say N. =20 +config SG_SPLIT_KUNIT_TEST + tristate "KUnit tests for sg_split" if !KUNIT_ALL_TESTS + depends on KUNIT + select SG_SPLIT + default KUNIT_ALL_TESTS + help + This builds KUnit tests for lib/sg_split.c. + + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in Documentation/dev-tools/kuni= t/. + + When in doubt, say N. + config BITREVERSE tristate =20 diff --git a/lib/Makefile b/lib/Makefile index f33a24bf1c19..1bedfa027932 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -149,6 +149,7 @@ obj-$(CONFIG_BITREVERSE) +=3D bitrev.o obj-$(CONFIG_LINEAR_RANGES) +=3D linear_ranges.o obj-$(CONFIG_PACKING) +=3D packing.o obj-$(CONFIG_PACKING_KUNIT_TEST) +=3D packing_test.o +obj-$(CONFIG_SG_SPLIT_KUNIT_TEST) +=3D test_sg_split_kunit.o obj-$(CONFIG_XXHASH) +=3D xxhash.o obj-$(CONFIG_GENERIC_ALLOCATOR) +=3D genalloc.o =20 diff --git a/lib/test_sg_split_kunit.c b/lib/test_sg_split_kunit.c new file mode 100644 index 000000000000..81af01c37233 --- /dev/null +++ b/lib/test_sg_split_kunit.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for lib/sg_split.c. + * + * Each case sets up a deterministic input scatterlist (page + offset + + * length per entry), calls sg_split, and verifies the two documented + * post-conditions: + * + * 1. sum of out[k] entry lengths equals split_sizes[k] (per-split siz= e) + * 2. the bytes walked from out[k] match input bytes + * [skip + sum(prev sizes) .. +sizes[k]) (per-split con= tent) + * + * Backing memory is a position-unique byte pattern, so a content + * mismatch identifies exactly which input region got mis-exposed. + */ + +#include +#include +#include +#include + +#define SG_SPLIT_MAX_IN_ENTRIES 8 +#define SG_SPLIT_MAX_SPLITS 4 +#define SG_SPLIT_PAGE_BYTES 4096 + +struct sg_split_in_spec { + int page; + unsigned int off; + unsigned int len; +}; + +static u8 sg_split_pattern(int page, unsigned int off) +{ + return (u8)(((page * 31u) + off * 17u + 0x5a) & 0xffu); +} + +/* + * Build the input SG, run sg_split, and verify both properties. + * KUnit handles cleanup of kunit_kzalloc'd memory automatically; + * we kfree the per-split output arrays ourselves. + */ +static void sg_split_run(struct kunit *test, + const struct sg_split_in_spec *in_spec, int n_in, + off_t skip, + const size_t *sizes, int nb_splits) +{ + u8 *backing, *oracle_buf; + struct scatterlist *in_sgl; + struct scatterlist *out[SG_SPLIT_MAX_SPLITS] =3D { NULL }; + int out_nents[SG_SPLIT_MAX_SPLITS] =3D { 0 }; + unsigned long oracle_cursor =3D (unsigned long)skip; + size_t oracle_total =3D 0; + int rc, k, i; + + KUNIT_ASSERT_LE(test, n_in, SG_SPLIT_MAX_IN_ENTRIES); + KUNIT_ASSERT_LE(test, nb_splits, SG_SPLIT_MAX_SPLITS); + + backing =3D kunit_kzalloc(test, + SG_SPLIT_MAX_IN_ENTRIES * SG_SPLIT_PAGE_BYTES, + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, backing); + + for (int p =3D 0; p < SG_SPLIT_MAX_IN_ENTRIES; p++) + for (unsigned int o =3D 0; o < SG_SPLIT_PAGE_BYTES; o++) + backing[p * SG_SPLIT_PAGE_BYTES + o] =3D + sg_split_pattern(p, o); + + in_sgl =3D kunit_kzalloc(test, sizeof(*in_sgl) * n_in, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, in_sgl); + sg_init_table(in_sgl, n_in); + for (i =3D 0; i < n_in; i++) { + u8 *buf =3D backing + in_spec[i].page * SG_SPLIT_PAGE_BYTES + + in_spec[i].off; + + sg_set_buf(&in_sgl[i], buf, in_spec[i].len); + oracle_total +=3D in_spec[i].len; + } + + /* Linear concatenation oracle for property 2. */ + oracle_buf =3D kunit_kzalloc(test, oracle_total, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, oracle_buf); + { + size_t op =3D 0; + + for (i =3D 0; i < n_in; i++) { + memcpy(oracle_buf + op, + backing + + in_spec[i].page * SG_SPLIT_PAGE_BYTES + + in_spec[i].off, + in_spec[i].len); + op +=3D in_spec[i].len; + } + } + + rc =3D sg_split(in_sgl, n_in, skip, nb_splits, sizes, + out, out_nents, GFP_KERNEL); + KUNIT_ASSERT_EQ_MSG(test, rc, 0, "sg_split returned %d", rc); + + for (k =3D 0; k < nb_splits; k++) { + unsigned long sum =3D 0; + struct scatterlist *sg; + int j; + u8 *got; + size_t copied =3D 0; + + KUNIT_ASSERT_NOT_NULL_MSG(test, out[k], + "split[%d] is NULL", k); + + /* Property 1: per-split size. */ + for_each_sg(out[k], sg, out_nents[k], j) + sum +=3D sg->length; + KUNIT_EXPECT_EQ_MSG(test, sum, sizes[k], + "P1 split[%d]: sum=3D%lu requested=3D%zu", + k, sum, sizes[k]); + + /* Property 2: per-split content. */ + got =3D kunit_kzalloc(test, sizes[k] + 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, got); + for_each_sg(out[k], sg, out_nents[k], j) { + size_t take =3D min_t(size_t, sg->length, + sizes[k] - copied); + + memcpy(got + copied, sg_virt(sg), take); + copied +=3D take; + if (copied >=3D sizes[k]) + break; + } + KUNIT_EXPECT_EQ_MSG(test, + memcmp(got, oracle_buf + oracle_cursor, + min_t(size_t, copied, sizes[k])), + 0, + "P2 split[%d]: bytes mismatch", k); + + oracle_cursor +=3D sizes[k]; + } + + for (k =3D 0; k < nb_splits; k++) + kfree(out[k]); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Group A =E2=80=94 full or edge-aligned coverage. These must pass even = on the + * pre-patch kernel; they exercise the path the existing five upstream + * callers all hit. + */ + +static void sg_split_t1_single_entry_full(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { { 0, 0, 100 } }; + const size_t sizes[] =3D { 100 }; + + sg_split_run(test, in, 1, 0, sizes, 1); +} + +static void sg_split_t2_two_entries_full_coverage(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { { 0, 0, 60 }, { 1, 0, 40 } }; + const size_t sizes[] =3D { 100 }; + + sg_split_run(test, in, 2, 0, sizes, 1); +} + +static void sg_split_t3_two_entries_two_splits_full(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { { 0, 0, 60 }, { 1, 0, 40 } }; + const size_t sizes[] =3D { 50, 50 }; + + sg_split_run(test, in, 2, 0, sizes, 2); +} + +static void sg_split_t4_edge_aligned_partial(struct kunit *test) +{ + /* Boundary at end of in[0]; partial coverage but edge-aligned. */ + const struct sg_split_in_spec in[] =3D { { 0, 0, 60 }, { 1, 0, 40 } }; + const size_t sizes[] =3D { 60 }; + + sg_split_run(test, in, 2, 0, sizes, 1); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Group B =E2=80=94 partial coverage with mid-entry boundary. These exer= cise + * the contract clause "union of spans is a subrange of the original" + * from f8bcbe62acd0. They fail on the pre-patch kernel. + */ + +static void sg_split_t5_minimal_repro(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { { 0, 0, 2 }, { 1, 0, 1 } }; + const size_t sizes[] =3D { 1 }; + + sg_split_run(test, in, 2, 0, sizes, 1); +} + +static void sg_split_t6_dthev2_aead_shape(struct kunit *test) +{ + /* AEAD shape: [AAD || ciphertext || TAG]; skip=3DAAD, take cryptlen, + * leave TAG as trailing tail. Edge-aligned in this layout, so + * even pre-patch the bug doesn't fire =E2=80=94 included to pin that. + */ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 64 }, { 1, 0, 100 }, { 2, 0, 16 } + }; + const size_t sizes[] =3D { 100 }; + + sg_split_run(test, in, 3, 64, sizes, 1); +} + +static void sg_split_t7_skip_plus_partial_midentry(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { { 0, 0, 100 }, { 1, 0, 50 } }; + const size_t sizes[] =3D { 30 }; + + sg_split_run(test, in, 2, 20, sizes, 1); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Group C =E2=80=94 sizes-containing-zero edge. Pre-patch behaviour is to + * produce a zero-length split and return success. The break-condition + * fix preserves this; a (rejected) earlier attempt at fixing the bug + * via `if (!size) continue;` regressed this case to a crash. + */ + +static void sg_split_t8_sizes_trailing_zero(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { { 0, 0, 10 }, { 1, 0, 5 } }; + const size_t sizes[] =3D { 10, 0 }; + + sg_split_run(test, in, 2, 0, sizes, 2); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Group D =E2=80=94 boundary-position matrix. Using 3 input entries of 1= 0 bytes + * each, walk the (skip + sum(sizes)) boundary through each position to + * confirm the trigger condition is "mid-entry AND has trailing input". + */ + +static void sg_split_t9_mid_first_two_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 5 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +static void sg_split_t10_edge_first_two_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 10 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +static void sg_split_t11_mid_middle_one_trailing(struct kunit *test) +{ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 15 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +static void sg_split_t12_mid_last_no_trailing(struct kunit *test) +{ + /* Boundary mid-in[2] with NO trailing entries. Even with the + * pre-patch dual-decrement overshoot, the for_each_sg loop ends + * before iter N+1 can corrupt state. Confirms the trigger + * narrowness. + */ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 25 }; + + sg_split_run(test, in, 3, 0, sizes, 1); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Group E =E2=80=94 skip-position variations. + */ + +static void sg_split_t13_skip_at_entry_boundary(struct kunit *test) +{ + /* skip =3D exactly len(in[0]). `skip > sglen` is strict, so the + * main body runs with len=3D0 in iter 1. Iter 2 takes 5 from in[1]; + * boundary mid-in[1], in[2] trailing. + */ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 5 }; + + sg_split_run(test, in, 3, 10, sizes, 1); +} + +static void sg_split_t14_skip_fastforward_partial(struct kunit *test) +{ + /* skip=3D12 =E2=86=92 iter 1 fast-forwards (skip -=3D 10, continue); ite= r 2 + * has effective skip=3D2, takes 5 from in[1] starting at byte 2; + * boundary at byte 17 =3D mid-in[1] with in[2] trailing. + */ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 5 }; + + sg_split_run(test, in, 3, 12, sizes, 1); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * Group F =E2=80=94 multi-split variations. + */ + +static void sg_split_t15_two_splits_last_mid_trailing(struct kunit *test) +{ + /* sizes=3D[5,10] over [10,10,10]: first split mid-in[0], second + * split ends mid-in[1] with in[2] trailing. Trigger on the LAST + * split. + */ + const struct sg_split_in_spec in[] =3D { + { 0, 0, 10 }, { 1, 0, 10 }, { 2, 0, 10 } + }; + const size_t sizes[] =3D { 5, 10 }; + + sg_split_run(test, in, 3, 0, sizes, 2); +} + +static void sg_split_t16_two_splits_full_coverage_mid_first(struct kunit *= test) +{ + /* sizes=3D[3,17] over [10,10]: first-split boundary mid-in[0], + * second-split boundary at end of in[1]. Full coverage. + */ + const struct sg_split_in_spec in[] =3D { { 0, 0, 10 }, { 1, 0, 10 } }; + const size_t sizes[] =3D { 3, 17 }; + + sg_split_run(test, in, 2, 0, sizes, 2); +} + +static struct kunit_case sg_split_test_cases[] =3D { + KUNIT_CASE(sg_split_t1_single_entry_full), + KUNIT_CASE(sg_split_t2_two_entries_full_coverage), + KUNIT_CASE(sg_split_t3_two_entries_two_splits_full), + KUNIT_CASE(sg_split_t4_edge_aligned_partial), + KUNIT_CASE(sg_split_t5_minimal_repro), + KUNIT_CASE(sg_split_t6_dthev2_aead_shape), + KUNIT_CASE(sg_split_t7_skip_plus_partial_midentry), + KUNIT_CASE(sg_split_t8_sizes_trailing_zero), + KUNIT_CASE(sg_split_t9_mid_first_two_trailing), + KUNIT_CASE(sg_split_t10_edge_first_two_trailing), + KUNIT_CASE(sg_split_t11_mid_middle_one_trailing), + KUNIT_CASE(sg_split_t12_mid_last_no_trailing), + KUNIT_CASE(sg_split_t13_skip_at_entry_boundary), + KUNIT_CASE(sg_split_t14_skip_fastforward_partial), + KUNIT_CASE(sg_split_t15_two_splits_last_mid_trailing), + KUNIT_CASE(sg_split_t16_two_splits_full_coverage_mid_first), + {} +}; + +static struct kunit_suite sg_split_test_suite =3D { + .name =3D "sg_split", + .test_cases =3D sg_split_test_cases, +}; + +kunit_test_suite(sg_split_test_suite); + +MODULE_DESCRIPTION("KUnit tests for lib/sg_split.c"); +MODULE_LICENSE("GPL"); --=20 2.47.3