From: Bill Wendling <morbo@google.com>
To: Andrey Ryabinin <ryabinin.a.a@gmail.com>,
Andrew Morton <akpm@linux-foundation.org>
Cc: Alexander Potapenko <glider@google.com>,
Andrey Konovalov <andreyknvl@gmail.com>,
Dmitry Vyukov <dvyukov@google.com>,
Vincenzo Frascino <vincenzo.frascino@arm.com>,
Kees Cook <kees@kernel.org>,
"Gustavo A. R. Silva" <gustavoars@kernel.org>,
kasan-dev@googlegroups.com, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org,
thomas.weissschuh@linutronix.de,
Bill Wendling <morbo@google.com>
Subject: [PATCH v3] fortify: add KUnit tests for __counted_by and __counted_by_ptr
Date: Tue, 29 Sep 2026 07:20:16 +0000 [thread overview]
Message-ID: <20260929072016.2360806-1-morbo@google.com> (raw)
In-Reply-To: <20260929010031.2186255-1-morbo@google.com>
The '__counted_by' and '__counted_by_ptr' attributes associate a
flexible array member or pointer member with a struct field that holds
its element count. Supporting compilers use these annotations to
compute dynamic object sizes via '__builtin_dynamic_object_size()' for
runtime bounds checking with CONFIG_FORTIFY_SOURCE.
Add KUnit tests ('fortify_test_counted_by_flex' and
'fortify_test_counted_by_ptr', guarded by CONFIG_CC_HAS_COUNTED_BY and
CONFIG_CC_HAS_COUNTED_BY_PTR respectively) to verify that:
- '__builtin_dynamic_object_size()', if available, returns the expected
logical byte size for annotated flexible array and pointer members.
- Fortified operations ('memset()' and 'memchr()') succeed within the
logical bounds and detect out-of-bounds read and write accesses
beyond the annotated count.
Allocate the test buffers with extra physical capacity (2 * size) in
'noinline' helpers and hide the returned pointers with
OPTIMIZER_HIDE_VAR() so allocation-size attributes, physical slab
bounds, and compiler optimizations do not mask the '__counted_by' and
'__counted_by_ptr' checks.
Signed-off-by: Bill Wendling <morbo@google.com>
---
v3: - Use "IS_ENABLED(CONFIG...)" instead of "#ifdefs". It's a lot cleaner
and documents better when skipped.
- Use "kunit_kzalloc" and "struct_size" for the flexible array member.
- Use KUNIT_EXPECT_BDOS which skips the test if BDOS isn't available.
v2: - Move tests to the 'fortify' KUnit tests. It uses UBSAN, which is
what gets triggered by 'counted_by'.
---
lib/tests/fortify_kunit.c | 114 ++++++++++++++++++++++++++++++++++++++
1 file changed, 114 insertions(+)
diff --git a/lib/tests/fortify_kunit.c b/lib/tests/fortify_kunit.c
index 413cdbf3dc0d..8baf6dc96bab 100644
--- a/lib/tests/fortify_kunit.c
+++ b/lib/tests/fortify_kunit.c
@@ -1011,6 +1011,118 @@ static void fortify_test_kmemdup(struct kunit *test)
kfree(copy);
}
+struct counted_by_flex_struct {
+ size_t size;
+ int array[] __counted_by(size);
+};
+
+/*
+ * Allocate the struct out-of-line with extra physical capacity so that
+ * __alloc_size() and physical slab bounds do not mask the __counted_by()
+ * logical bounds check.
+ */
+static noinline struct counted_by_flex_struct *
+alloc_counted_by_flex_struct(struct kunit *test, size_t size)
+{
+ struct counted_by_flex_struct *s;
+
+ s = kunit_kzalloc(test, struct_size(s, array, 2 * size), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s);
+
+ /* Intentionally fake the size so that we can trigger a trap. */
+ s->size = size;
+ return s;
+}
+
+static void fortify_test_counted_by_flex(struct kunit *test)
+{
+ size_t size = 128;
+ struct counted_by_flex_struct *s;
+ size_t elem_bytes = size * sizeof(s->array[0]);
+
+ if (!IS_ENABLED(CONFIG_CC_HAS_COUNTED_BY))
+ kunit_skip(test, "requires CONFIG_CC_HAS_COUNTED_BY");
+
+ s = alloc_counted_by_flex_struct(test, size);
+
+ OPTIMIZER_HIDE_VAR(s);
+ OPTIMIZER_HIDE_VAR(elem_bytes);
+
+ /* __builtin_dynamic_object_size() should return the logical length. */
+ KUNIT_EXPECT_BDOS(test, s->array, elem_bytes,
+ "struct counted_by_flex_struct");
+
+ /* Within-bounds write and read succeed. */
+ memset(s->array, 0x42, elem_bytes);
+ KUNIT_EXPECT_EQ(test, fortify_write_overflows, 0);
+ KUNIT_EXPECT_NOT_NULL(test, memchr(s->array, 0x42, elem_bytes));
+ KUNIT_EXPECT_EQ(test, fortify_read_overflows, 0);
+
+ /* Out-of-bounds write and read past logical size are caught. */
+ memset(s->array, 0x42, elem_bytes + 1);
+ KUNIT_EXPECT_EQ(test, fortify_write_overflows, 1);
+ KUNIT_EXPECT_NULL(test, memchr(s->array, 0x42, elem_bytes + 1));
+ KUNIT_EXPECT_EQ(test, fortify_read_overflows, 1);
+}
+
+struct counted_by_ptr_struct {
+ char *ptr __counted_by_ptr(size);
+ size_t size;
+};
+
+/*
+ * Allocate the struct out-of-line with extra physical capacity so that
+ * __alloc_size() and physical slab bounds do not mask the __counted_by_ptr()
+ * logical bounds check.
+ */
+static noinline struct counted_by_ptr_struct *
+alloc_counted_by_ptr_struct(struct kunit *test, size_t size)
+{
+ struct counted_by_ptr_struct *s;
+
+ s = kmalloc_obj(struct counted_by_ptr_struct);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s);
+
+ /* Intentionally fake the size so that we can trigger a trap. */
+ s->size = size;
+ s->ptr = kzalloc(2 * size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s->ptr);
+
+ return s;
+}
+
+static void fortify_test_counted_by_ptr(struct kunit *test)
+{
+ size_t size = 128;
+ struct counted_by_ptr_struct *s;
+
+ if (!IS_ENABLED(CONFIG_CC_HAS_COUNTED_BY_PTR))
+ kunit_skip(test, "requires CONFIG_CC_HAS_COUNTED_BY_PTR");
+
+ s = alloc_counted_by_ptr_struct(test, size);
+
+ OPTIMIZER_HIDE_VAR(s);
+ OPTIMIZER_HIDE_VAR(size);
+
+ /* __builtin_dynamic_object_size() should return the logical length. */
+ KUNIT_EXPECT_BDOS(test, s->ptr, size, "struct counted_by_ptr_struct");
+
+ /* Within-bounds write and read succeed. */
+ memset(s->ptr, 0x42, size);
+ KUNIT_EXPECT_EQ(test, fortify_write_overflows, 0);
+ KUNIT_EXPECT_NOT_NULL(test, memchr(s->ptr, 0x42, size));
+ KUNIT_EXPECT_EQ(test, fortify_read_overflows, 0);
+
+ /* Out-of-bounds write and read past logical size are caught. */
+ memset(s->ptr, 0x42, size + 1);
+ KUNIT_EXPECT_EQ(test, fortify_write_overflows, 1);
+ KUNIT_EXPECT_NULL(test, memchr(s->ptr, 0x42, size + 1));
+ KUNIT_EXPECT_EQ(test, fortify_read_overflows, 1);
+
+ kfree(s->ptr);
+ kfree(s);
+}
+
static int fortify_test_init(struct kunit *test)
{
if (!IS_ENABLED(CONFIG_FORTIFY_SOURCE))
@@ -1054,6 +1166,8 @@ static struct kunit_case fortify_test_cases[] = {
KUNIT_CASE(fortify_test_memchr_inv),
KUNIT_CASE(fortify_test_memcmp),
KUNIT_CASE(fortify_test_kmemdup),
+ KUNIT_CASE(fortify_test_counted_by_flex),
+ KUNIT_CASE(fortify_test_counted_by_ptr),
{}
};
--
2.56.0.rc1.315.gc6ed9934b7-goog
prev parent reply other threads:[~2026-09-29 7:20 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 23:17 [PATCH] kasan: add KUnit tests for __counted_by and __counted_by_ptr Bill Wendling
2026-09-28 23:42 ` Andrew Morton
2026-09-29 0:59 ` Bill Wendling
2026-09-29 1:00 ` [PATCH] fortify: " Bill Wendling
2026-09-29 6:23 ` Thomas Weißschuh
2026-09-29 6:53 ` Bill Wendling
2026-09-29 7:20 ` Bill Wendling [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260929072016.2360806-1-morbo@google.com \
--to=morbo@google.com \
--cc=akpm@linux-foundation.org \
--cc=andreyknvl@gmail.com \
--cc=dvyukov@google.com \
--cc=glider@google.com \
--cc=gustavoars@kernel.org \
--cc=kasan-dev@googlegroups.com \
--cc=kees@kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ryabinin.a.a@gmail.com \
--cc=thomas.weissschuh@linutronix.de \
--cc=vincenzo.frascino@arm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.