From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qv1-f53.google.com (mail-qv1-f53.google.com [209.85.219.53]) (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 4709E3A5431 for ; Sun, 14 Jun 2026 13:08:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.53 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781442503; cv=none; b=cODGeklpAomuV4SwucVxYG9hHnUOEzmy8HWDvanWpSuDbUq471I6JMTJIlUhCS6CUfgMU+i7Gc8nJXNVWeABsCa1PAa+3JL81e7Oj/ToDC0TWfDahiQEXMeYlfrAwkvYYn3fMVWskedxP86FMcqklvDyWj88Pqmd9OJOCKf3Hp4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781442503; c=relaxed/simple; bh=MpLnIeYn7owYSdnYQlJ38aky7+NM7yEMTY5VxwzIAcM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fO2MPS8Ss0z+ih1L8ZOXKxKZYwx6tWXJveeQqUaOENGEW1s6EzLeqEGwB4SsoKcQ7cw6x0XNyuPZgy/3rFAIN4TdCuiCI7nK/nrzOKcG+wLG/asUMPpQWJqSQf6u2l6aNiRo650bHY95vWkFJ6TKZD4OWkGVGnitgvujGWEn8hE= 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=YoBq16Fe; arc=none smtp.client-ip=209.85.219.53 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="YoBq16Fe" Received: by mail-qv1-f53.google.com with SMTP id 6a1803df08f44-8cce87d7995so29114336d6.0 for ; Sun, 14 Jun 2026 06:08:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781442501; x=1782047301; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=hnL26yNc7x7CUKnmGQn06aGfaRhQ08nHiRAS64LIsU8=; b=YoBq16Fer6ypaxqbr7/m5rkyM7NmFt/BDdRw1ZE9Jc1kFtduZTsY3hn6w6L0LqWeLF ZYjFBZ54rDdrNon5bn5QLvswWl3Be1gzJKyVkQlt+pUIcrJIdN+faIEicuNaPigw6C/L Y4ld9WbMiK49Dpgq4MbSMkHpXG4sz38Xud291M4E+a23zkByM82fDZ2PNTVZ3co/GpXB K5Cw124ooUnLN9gJVwQxVARG4zNMOcVtVLhXAiHc5AdkrVEZc7cEceGD+IOg97MwoXfh LjC2G8xQpupz/qm2YxJ2/ul0azy2MguO64ugPO0xAo+HYIlRKvnCMG1T9ZCDJB9DaLPW 0U8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781442501; x=1782047301; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=hnL26yNc7x7CUKnmGQn06aGfaRhQ08nHiRAS64LIsU8=; b=n1ocbulzthqNSOIMQwiNL0X6DqZlr6BJAAKgAQ9vxRaoEaCmJF4doSkPMszU7dPDZh 47Lr9XRCenUhsYhGqMEDrzyLF9Q/M3h76pxpcyrLaFz57JzrUQKCDv4/yTvSC8WexUzf S00iK3XrK69EORgRR+aB8IPmJHPFtoK6TU/SJ7BVzITGUiFZ+aiyMi5EK6oI68qNs5/h AJQaw2Ys+EuJ7tLXMjyjc0TePesjAJPV6pFAKiGisw7WZrlcGAbS9T6toRQkIz007gRt oIro5zEJBskQriW93IBPXPM+/Gm4NGkdqPAsMgOtQPp4+lLSljWAZdLD9K0TkXrfKv5S jPGg== X-Forwarded-Encrypted: i=1; AFNElJ+f7cMLhDNRqa4comEO97hvmio/eHK/LtvZuhjKoKIkgzfAojX5ivKvmEN4o4981k8xD7sgrNZejvMMg5Y=@vger.kernel.org X-Gm-Message-State: AOJu0YyqwRD21e8yfe5IlxO0uhukizPtRDJXnWxAJxrEDDYYl0OY53fr B5maTQ/zgYUi3crsmX/nzE15KEno0rGmSq7LLIzI9emASyf/nPQKHMa9 X-Gm-Gg: Acq92OEyVqPuTnz2DvGIamtVtBx3tY4e1e/YYo7UIcl+09i7l5bFyKoD2s2Zm8BaoH3 EplhjyZ3Rs0a1LVt9YKPXIEbTyeZ4Ewn67o5ZIMQLHznB6Yac7URecrJWWMnMUK3bG5290Ml1lz ABi+FVf38gMJDbDby/opdvcqjzjwG0XK0CwZgJds3uhbWgq7ck6x9XmL+Ma6hpOfSTJZwr+CnjH tQuvvDg7OZiC95ntdNysX2eXdXaKExO3peWymKYbCSroFI7S/RQT9xW9hW03HmXXPbA7SdeS2Cu KtIXBegxjkylEdchD6QPV0DVmsqx+IQuvBoA8a6ZBWfPWXxsdaRJ2XahB2D3D3jafr402oMIKbl Njo8lHkO0gHDLB/Vex1cCtnJoPauBiCsy8gLeQLC2CEsZWk6QlmxVSSP2dMwLgGVfC6syWvNsGm nm7Hmuj+abI8jEj37tyzTV+dyfXYGYo6coCQeSGGdbZgIt2nU1SaTmBA80ruJFOrnFZSnEq5igL jLwnK9RzjpNzjAkprK9RKAtPu8LvwvVTER6ptPLItk= X-Received: by 2002:a05:6214:53c1:b0:8cc:e8f4:1636 with SMTP id 6a1803df08f44-8d32e30e81fmr199155156d6.25.1781442501242; Sun, 14 Jun 2026 06:08:21 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8d301b2cf04sm77838966d6.16.2026.06.14.06.08.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 14 Jun 2026 06:08:20 -0700 (PDT) From: Michael Bommarito To: Trond Myklebust , Anna Schumaker Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/2] NFSv4.1/pnfs: add KUnit coverage for GETDEVICEINFO notification decode Date: Sun, 14 Jun 2026 09:08:14 -0400 Message-ID: <20260614130814.2521819-3-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260614130814.2521819-1-michael.bommarito@gmail.com> References: <20260614130814.2521819-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Add a KUnit suite driving the real file-local decode_getdeviceinfo() over a crafted GETDEVICEINFO reply to cover the notification-bitmap length pass. It is included into nfs4xdr.c (gated by CONFIG_NFS_GETDEVICEINFO_KUNIT_TEST) to reach the static decoder without exporting it. A trigger supplies the wrapping length and two benign controls drive the same decoder in bounds. Integer overflow has no sanitizer, so the oracle is the downstream KASAN slab-out-of-bounds read: on QEMU x86_64 with KASAN the trigger faults on stock and passes after patch 1, while both controls pass on stock and fixed trees. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito --- On QEMU x86_64 with KASAN: the trigger faults on stock (slab-out-of- bounds READ) and passes after patch 1; both benign controls pass on stock and patched. The suite builds the reply in a kmalloc(PAGE_SIZE) buffer with the notification fields in the XDR tail. fs/nfs/Kconfig | 14 ++++ fs/nfs/getdeviceinfo_notify_kunit.c | 110 ++++++++++++++++++++++++++++ fs/nfs/nfs4xdr.c | 4 + 3 files changed, 128 insertions(+) create mode 100644 fs/nfs/getdeviceinfo_notify_kunit.c diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index 6bb30543eff00..73cdf201ebb23 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -215,3 +215,17 @@ config NFS_V4_2_READ_PLUS default y help Choose Y here to enable use of the NFS v4.2 READ_PLUS operation. + +config NFS_GETDEVICEINFO_KUNIT_TEST + tristate "KUnit test for pNFS GETDEVICEINFO notification decode" if !KUNIT_ALL_TESTS + depends on NFS_V4 && KUNIT + default KUNIT_ALL_TESTS + help + Builds KUnit coverage for the notification-bitmap length pass in + the NFS client pNFS GETDEVICEINFO reply decoder. The test drives + the real decode_getdeviceinfo() over a crafted reply and, on an + unfixed kernel built with CONFIG_KASAN, reports the slab-out-of- + bounds read caused by a 32-bit overflow in the notification-bitmap + length handling. + + If unsure, say N. diff --git a/fs/nfs/getdeviceinfo_notify_kunit.c b/fs/nfs/getdeviceinfo_notify_kunit.c new file mode 100644 index 0000000000000..1bfc40aabd15d --- /dev/null +++ b/fs/nfs/getdeviceinfo_notify_kunit.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit coverage for decode_getdeviceinfo()'s notification-bitmap length. + * Drives the real static decoder over a crafted reply: with len = 0x40000000 + * the u32 "4 * len" wraps to 0, defeating the bounds check, and the verify + * loop reads past the buffer. The length word sits at the page edge so the + * first over-read word hits the KASAN redzone. Level 2; from nfs4xdr.c. + */ +#include + +#define GDI_OPNUM 47U /* OP_GETDEVICEINFO */ +#define GDI_LAYOUT_TYPE 1U /* arbitrary; pdev->layout_type matches */ + +/* Fixed fields in the XDR head, notification bitmap in the tail ending at @tail_end. */ +static int run_decode(struct kunit *test, __be32 *tail_end, u32 notify_len, + u32 notify_word0) +{ + struct pnfs_device pdev = { .layout_type = GDI_LAYOUT_TYPE }; + struct nfs4_getdeviceinfo_res res = { .pdev = &pdev }; + struct xdr_stream xdr; + struct xdr_buf buf; + __be32 head[4]; + unsigned int tail_words = notify_len <= 2 ? notify_len + 1 : 1; + __be32 *tail = tail_end - tail_words; + + head[0] = cpu_to_be32(GDI_OPNUM); /* op_hdr: opnum */ + head[1] = cpu_to_be32(0); /* op_hdr: NFS_OK */ + head[2] = cpu_to_be32(GDI_LAYOUT_TYPE); /* device type */ + head[3] = cpu_to_be32(0); /* mincount = 0 */ + tail[0] = cpu_to_be32(notify_len); /* notification len */ + if (notify_len == 1) { + tail[1] = cpu_to_be32(notify_word0); + } else if (notify_len == 2) { + tail[1] = cpu_to_be32(notify_word0); + tail[2] = cpu_to_be32(1); + } + + memset(&buf, 0, sizeof(buf)); + buf.head[0].iov_base = head; + buf.head[0].iov_len = sizeof(head); + buf.tail[0].iov_base = tail; + buf.tail[0].iov_len = tail_words * sizeof(*tail); + buf.len = buf.head[0].iov_len + buf.tail[0].iov_len; + buf.buflen = buf.len; + xdr_init_decode(&xdr, &buf, head, NULL); + return decode_getdeviceinfo(&xdr, &res); +} + +/* Control: one-word bitmap (len 1, word 0) decodes cleanly; PASS stock+patched. */ +static void getdeviceinfo_notify_control_len1(struct kunit *test) +{ + __be32 *obj = kmalloc(PAGE_SIZE, GFP_KERNEL); + int ret; + + KUNIT_ASSERT_NOT_NULL(test, obj); + /* Place reply mid-buffer; nothing reads past it. */ + ret = run_decode(test, obj + 32, 1, 0); + KUNIT_EXPECT_EQ(test, ret, 0); + kfree(obj); +} + +/* Control: len 2, nonzero unsupported word -> -EIO in bounds; PASS stock+patched. */ +static void getdeviceinfo_notify_control_unsupported_len2(struct kunit *test) +{ + __be32 *obj = kmalloc(PAGE_SIZE, GFP_KERNEL); + int ret; + + KUNIT_ASSERT_NOT_NULL(test, obj); + ret = run_decode(test, obj + 32, 2, 0); + KUNIT_EXPECT_EQ(test, ret, -EIO); + kfree(obj); +} + +/* Trigger: wrapping len 0x40000000 at the page edge -> KASAN OOB on stock, -EIO patched. */ +static void getdeviceinfo_notify_trigger_oob(struct kunit *test) +{ + __be32 *obj = kmalloc(PAGE_SIZE, GFP_KERNEL); + __be32 *obj_end = (__be32 *)((char *)obj + PAGE_SIZE); + int ret; + + KUNIT_ASSERT_NOT_NULL(test, obj); + ret = run_decode(test, obj_end, 0x40000000U, 0); + /* Reached only on the patched tree. */ + KUNIT_EXPECT_EQ(test, ret, -EIO); + kfree(obj); +} + +static struct kunit_case getdeviceinfo_notify_cases[] = { + KUNIT_CASE(getdeviceinfo_notify_control_len1), + KUNIT_CASE(getdeviceinfo_notify_control_unsupported_len2), + KUNIT_CASE(getdeviceinfo_notify_trigger_oob), + {} +}; + +static struct kunit_suite getdeviceinfo_notify_suite = { + .name = "nfs4_getdeviceinfo_notify", + .test_cases = getdeviceinfo_notify_cases, +}; + +kunit_test_suite(getdeviceinfo_notify_suite); diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c index ca84d0c872a6c..42eb82ab0346f 100644 --- a/fs/nfs/nfs4xdr.c +++ b/fs/nfs/nfs4xdr.c @@ -7788,3 +7788,7 @@ const struct rpc_version nfs_version4 = { .procs = nfs4_procedures, .counts = nfs_version4_counts, }; + +#if IS_ENABLED(CONFIG_NFS_GETDEVICEINFO_KUNIT_TEST) +#include "getdeviceinfo_notify_kunit.c" +#endif -- 2.53.0