* [PATCH] kasan: add KUnit tests for __counted_by and __counted_by_ptr
@ 2026-09-28 23:17 Bill Wendling
2026-09-28 23:42 ` Andrew Morton
2026-09-29 1:00 ` [PATCH] fortify: " Bill Wendling
0 siblings, 2 replies; 7+ messages in thread
From: Bill Wendling @ 2026-09-28 23:17 UTC (permalink / raw)
To: Andrey Ryabinin, Andrew Morton
Cc: Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov,
Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev,
linux-mm, linux-kernel, linux-hardening, thomas.weissschuh,
Bill Wendling
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()' and
perform runtime bounds checking with KASAN.
Add KUnit tests ('counted_by_flex_oob_access' and
'counted_by_ptr_oob_access', guarded by CONFIG_CC_HAS_COUNTED_BY and
CONFIG_CC_HAS_COUNTED_BY_PTR respectively) to verify that:
- '__builtin_dynamic_object_size()' returns the expected byte size for
annotated flexible array and pointer members.
- KASAN detects out-of-bounds read and write accesses beyond the
annotated count.
Allocate the test structures in 'noinline' helpers and hide the
returned pointers with OPTIMIZER_HIDE_VAR() so allocation-size
attributes and compiler optimizations do not mask the '__counted_by'
and '__counted_by_ptr' checks.
Signed-off-by: Bill Wendling <morbo@google.com>
---
mm/kasan/kasan_test_c.c | 100 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
index b9e167ed5be3..f481183c84f1 100644
--- a/mm/kasan/kasan_test_c.c
+++ b/mm/kasan/kasan_test_c.c
@@ -2201,6 +2201,100 @@ static void copy_user_test_oob(struct kunit *test)
unused = strncpy_from_user(kmem, usermem, size + 1));
}
+#ifdef CONFIG_CC_HAS_COUNTED_BY
+struct counted_by_flex_struct {
+ size_t size;
+ int array[] __counted_by(size);
+};
+
+/*
+ * Allocate the struct out-of-line to prevent inherent attributes from
+ * affecting the '__builtin_dynamic_object_size' 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 = kzalloc(sizeof(struct counted_by_flex_struct) +
+ size * sizeof(s->array[0]), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s);
+
+ s->size = size;
+ return s;
+}
+
+static void counted_by_flex_oob_access(struct kunit *test)
+{
+ size_t size = 128;
+ struct counted_by_flex_struct *s;
+
+ s = alloc_counted_by_flex_struct(test, size);
+
+ OPTIMIZER_HIDE_VAR(s);
+
+ /* __builtin_dynamic_object_size() should return the correct length. */
+ KUNIT_EXPECT_EQ(test, size * sizeof(s->array[0]),
+ __builtin_dynamic_object_size(s->array, 0));
+
+ /* Out-of-bounds assignment. */
+ KUNIT_EXPECT_KASAN_FAIL(test, s->array[size + 1] = 42);
+
+ /* Out-of-bounds read. */
+ KUNIT_EXPECT_KASAN_FAIL_READ(test, s->array[0] = s->array[size + 13]);
+
+ kfree(s);
+}
+
+#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR
+struct counted_by_ptr_struct {
+ char *ptr __counted_by_ptr(size);
+ size_t size;
+};
+
+/*
+ * Allocate the struct out-of-line to prevent inherent attributes from
+ * affecting the '__builtin_dynamic_object_size' 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);
+
+ s->size = size;
+ s->ptr = kzalloc(size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s->ptr);
+
+ return s;
+}
+
+static void counted_by_ptr_oob_access(struct kunit *test)
+{
+ size_t size = 128;
+ struct counted_by_ptr_struct *s;
+
+ s = alloc_counted_by_ptr_struct(test, size);
+
+ OPTIMIZER_HIDE_VAR(s);
+
+ /* __builtin_dynamic_object_size() should return the correct length. */
+ KUNIT_EXPECT_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 0));
+
+ /* Out-of-bounds assignment. */
+ KUNIT_EXPECT_KASAN_FAIL(test, s->ptr[size + 1] = 42);
+
+ /* Out-of-bounds read. */
+ KUNIT_EXPECT_KASAN_FAIL_READ(test, s->ptr[0] = s->ptr[size + 13]);
+
+ kfree(s->ptr);
+ kfree(s);
+}
+#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */
+#endif /* CONFIG_CC_HAS_COUNTED_BY */
+
static struct kunit_case kasan_kunit_test_cases[] = {
KUNIT_CASE(kmalloc_oob_right),
KUNIT_CASE(kmalloc_oob_left),
@@ -2280,6 +2374,12 @@ static struct kunit_case kasan_kunit_test_cases[] = {
#endif
KUNIT_CASE(rust_uaf),
KUNIT_CASE(copy_user_test_oob),
+#ifdef CONFIG_CC_HAS_COUNTED_BY
+ KUNIT_CASE(counted_by_flex_oob_access),
+#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR
+ KUNIT_CASE(counted_by_ptr_oob_access),
+#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */
+#endif /* CONFIG_CC_HAS_COUNTED_BY */
{}
};
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] kasan: add KUnit tests for __counted_by and __counted_by_ptr 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 1 sibling, 1 reply; 7+ messages in thread From: Andrew Morton @ 2026-09-28 23:42 UTC (permalink / raw) To: Bill Wendling Cc: Andrey Ryabinin, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev, linux-mm, linux-kernel, linux-hardening, thomas.weissschuh On Mon, 28 Sep 2026 23:17:37 +0000 Bill Wendling <morbo@google.com> wrote: > 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()' and > perform runtime bounds checking with KASAN. > > Add KUnit tests ('counted_by_flex_oob_access' and > 'counted_by_ptr_oob_access', guarded by CONFIG_CC_HAS_COUNTED_BY and > CONFIG_CC_HAS_COUNTED_BY_PTR respectively) to verify that: > > - '__builtin_dynamic_object_size()' returns the expected byte size for > annotated flexible array and pointer members. > - KASAN detects out-of-bounds read and write accesses beyond the > annotated count. > > Allocate the test structures in 'noinline' helpers and hide the > returned pointers with OPTIMIZER_HIDE_VAR() so allocation-size > attributes and compiler optimizations do not mask the '__counted_by' > and '__counted_by_ptr' checks. Thanks. Are any of Sashiko's comments pertinent? https://sashiko.dev/#/patchset/20260928231737.2092716-1-morbo@google.com ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] kasan: add KUnit tests for __counted_by and __counted_by_ptr 2026-09-28 23:42 ` Andrew Morton @ 2026-09-29 0:59 ` Bill Wendling 0 siblings, 0 replies; 7+ messages in thread From: Bill Wendling @ 2026-09-29 0:59 UTC (permalink / raw) To: Andrew Morton Cc: Andrey Ryabinin, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev, linux-mm, linux-kernel, linux-hardening, thomas.weissschuh On Mon, Sep 28, 2026 at 4:42 PM Andrew Morton <akpm@linux-foundation.org> wrote: > > On Mon, 28 Sep 2026 23:17:37 +0000 Bill Wendling <morbo@google.com> wrote: > > > 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()' and > > perform runtime bounds checking with KASAN. > > > > Add KUnit tests ('counted_by_flex_oob_access' and > > 'counted_by_ptr_oob_access', guarded by CONFIG_CC_HAS_COUNTED_BY and > > CONFIG_CC_HAS_COUNTED_BY_PTR respectively) to verify that: > > > > - '__builtin_dynamic_object_size()' returns the expected byte size for > > annotated flexible array and pointer members. > > - KASAN detects out-of-bounds read and write accesses beyond the > > annotated count. > > > > Allocate the test structures in 'noinline' helpers and hide the > > returned pointers with OPTIMIZER_HIDE_VAR() so allocation-size > > attributes and compiler optimizations do not mask the '__counted_by' > > and '__counted_by_ptr' checks. > > Thanks. Are any of Sashiko's comments pertinent? > https://sashiko.dev/#/patchset/20260928231737.2092716-1-morbo@google.com Yes. I found a better place to put these tests. I'll send a v2. -bw ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH] fortify: add KUnit tests for __counted_by and __counted_by_ptr 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 1:00 ` Bill Wendling 2026-09-29 6:23 ` Thomas Weißschuh 2026-09-29 7:20 ` [PATCH v3] " Bill Wendling 1 sibling, 2 replies; 7+ messages in thread From: Bill Wendling @ 2026-09-29 1:00 UTC (permalink / raw) To: Andrey Ryabinin, Andrew Morton Cc: Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev, linux-mm, linux-kernel, linux-hardening, thomas.weissschuh, Bill Wendling 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()' (both types 0 and 1) 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> --- v2: Move tests to the 'fortify' KUnit tests. It uses UBSAN, which is what gets triggered by 'counted_by'. --- lib/tests/fortify_kunit.c | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/lib/tests/fortify_kunit.c b/lib/tests/fortify_kunit.c index 413cdbf3dc0d..2335171f84d8 100644 --- a/lib/tests/fortify_kunit.c +++ b/lib/tests/fortify_kunit.c @@ -1011,6 +1011,119 @@ static void fortify_test_kmemdup(struct kunit *test) kfree(copy); } +#ifdef CONFIG_CC_HAS_COUNTED_BY +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 = kzalloc(sizeof(*s) + 2 * size * sizeof(s->array[0]), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s); + + 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]); + + 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_EQ(test, elem_bytes, + __builtin_dynamic_object_size(s->array, 0)); + KUNIT_EXPECT_EQ(test, elem_bytes, + __builtin_dynamic_object_size(s->array, 1)); + + /* 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); + + kfree(s); +} + +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR +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); + + 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; + + 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_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 0)); + KUNIT_EXPECT_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 1)); + + /* 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); +} +#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */ +#endif /* CONFIG_CC_HAS_COUNTED_BY */ + static int fortify_test_init(struct kunit *test) { if (!IS_ENABLED(CONFIG_FORTIFY_SOURCE)) @@ -1054,6 +1167,12 @@ static struct kunit_case fortify_test_cases[] = { KUNIT_CASE(fortify_test_memchr_inv), KUNIT_CASE(fortify_test_memcmp), KUNIT_CASE(fortify_test_kmemdup), +#ifdef CONFIG_CC_HAS_COUNTED_BY + KUNIT_CASE(fortify_test_counted_by_flex), +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR + KUNIT_CASE(fortify_test_counted_by_ptr), +#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */ +#endif /* CONFIG_CC_HAS_COUNTED_BY */ {} }; -- 2.56.0.rc1.315.gc6ed9934b7-goog ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] fortify: add KUnit tests for __counted_by and __counted_by_ptr 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 ` [PATCH v3] " Bill Wendling 1 sibling, 1 reply; 7+ messages in thread From: Thomas Weißschuh @ 2026-09-29 6:23 UTC (permalink / raw) To: Bill Wendling Cc: Andrey Ryabinin, Andrew Morton, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev, linux-mm, linux-kernel, linux-hardening Hi Bill, this looks much better. Thanks for the rework. On Tue, Sep 29, 2026 at 01:00:31AM +0000, Bill Wendling wrote: > 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()' (both types 0 and 1) 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> > --- > v2: Move tests to the 'fortify' KUnit tests. It uses UBSAN, which is > what gets triggered by 'counted_by'. v2 is missing in subject. > --- > lib/tests/fortify_kunit.c | 119 ++++++++++++++++++++++++++++++++++++++ > 1 file changed, 119 insertions(+) > > diff --git a/lib/tests/fortify_kunit.c b/lib/tests/fortify_kunit.c > index 413cdbf3dc0d..2335171f84d8 100644 > --- a/lib/tests/fortify_kunit.c > +++ b/lib/tests/fortify_kunit.c > @@ -1011,6 +1011,119 @@ static void fortify_test_kmemdup(struct kunit *test) > kfree(copy); > } > > +#ifdef CONFIG_CC_HAS_COUNTED_BY The ugly ifdeffery can be replaced by IS_ENABLED(): if (!IS_ENABLED(CONFIG_FOO)) kunit_skip(test, "Not built with CONFIG_FOO=y"); It makes the code cleaner and gives some useful feedback at runtime. > +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 = kzalloc(sizeof(*s) + 2 * size * sizeof(s->array[0]), GFP_KERNEL); kunit_kzalloc() to automatically free the allocation again. struct_size() for the size calculation. > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s); > + > + 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]); > + > + 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_EQ(test, elem_bytes, > + __builtin_dynamic_object_size(s->array, 0)); > + KUNIT_EXPECT_EQ(test, elem_bytes, > + __builtin_dynamic_object_size(s->array, 1)); > + > + /* 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); > + > + kfree(s); > +} > + > +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR > +struct counted_by_ptr_struct { > + char *ptr __counted_by_ptr(size); > + size_t size; > +}; In the other structure the arguments where swapped, intentional? > + > +/* > + * 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); We should probably also get kunit_kmalloc_obj() at some point. > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s); > + > + 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; > + > + 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_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 0)); > + KUNIT_EXPECT_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 1)); Is this builtin guaranteed to be available? We have a wrapper KUNIT_EXPECT_BDOS() above. > + > + /* 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); > +} > +#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */ > +#endif /* CONFIG_CC_HAS_COUNTED_BY */ > + > static int fortify_test_init(struct kunit *test) > { > if (!IS_ENABLED(CONFIG_FORTIFY_SOURCE)) > @@ -1054,6 +1167,12 @@ static struct kunit_case fortify_test_cases[] = { > KUNIT_CASE(fortify_test_memchr_inv), > KUNIT_CASE(fortify_test_memcmp), > KUNIT_CASE(fortify_test_kmemdup), > +#ifdef CONFIG_CC_HAS_COUNTED_BY > + KUNIT_CASE(fortify_test_counted_by_flex), > +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR The nesting of these conditionals looks unnecessary. > + KUNIT_CASE(fortify_test_counted_by_ptr), > +#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */ > +#endif /* CONFIG_CC_HAS_COUNTED_BY */ > {} > }; > > -- > 2.56.0.rc1.315.gc6ed9934b7-goog > ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] fortify: add KUnit tests for __counted_by and __counted_by_ptr 2026-09-29 6:23 ` Thomas Weißschuh @ 2026-09-29 6:53 ` Bill Wendling 0 siblings, 0 replies; 7+ messages in thread From: Bill Wendling @ 2026-09-29 6:53 UTC (permalink / raw) To: Thomas Weißschuh Cc: Andrey Ryabinin, Andrew Morton, Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev, linux-mm, linux-kernel, linux-hardening Hi Thomas, On Mon, Sep 28, 2026 at 11:23 PM Thomas Weißschuh <thomas.weissschuh@linutronix.de> wrote: > > Hi Bill, > > this looks much better. Thanks for the rework. > > On Tue, Sep 29, 2026 at 01:00:31AM +0000, Bill Wendling wrote: > > 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()' (both types 0 and 1) 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> > > --- > > v2: Move tests to the 'fortify' KUnit tests. It uses UBSAN, which is > > what gets triggered by 'counted_by'. > > v2 is missing in subject. > Doh! > > --- > > lib/tests/fortify_kunit.c | 119 ++++++++++++++++++++++++++++++++++++++ > > 1 file changed, 119 insertions(+) > > > > diff --git a/lib/tests/fortify_kunit.c b/lib/tests/fortify_kunit.c > > index 413cdbf3dc0d..2335171f84d8 100644 > > --- a/lib/tests/fortify_kunit.c > > +++ b/lib/tests/fortify_kunit.c > > @@ -1011,6 +1011,119 @@ static void fortify_test_kmemdup(struct kunit *test) > > kfree(copy); > > } > > > > +#ifdef CONFIG_CC_HAS_COUNTED_BY > > The ugly ifdeffery can be replaced by IS_ENABLED(): > > if (!IS_ENABLED(CONFIG_FOO)) > kunit_skip(test, "Not built with CONFIG_FOO=y"); > > It makes the code cleaner and gives some useful feedback at runtime. > Ah yes! This is much nicer. It'll also fix up the #ifdef stuff at the end as well. > > +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 = kzalloc(sizeof(*s) + 2 * size * sizeof(s->array[0]), GFP_KERNEL); > > kunit_kzalloc() to automatically free the allocation again. > struct_size() for the size calculation. > Oh cool! done. > > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s); > > + > > + 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]); > > + > > + 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_EQ(test, elem_bytes, > > + __builtin_dynamic_object_size(s->array, 0)); > > + KUNIT_EXPECT_EQ(test, elem_bytes, > > + __builtin_dynamic_object_size(s->array, 1)); > > + > > + /* 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); > > + > > + kfree(s); > > +} > > + > > +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR > > +struct counted_by_ptr_struct { > > + char *ptr __counted_by_ptr(size); > > + size_t size; > > +}; > > In the other structure the arguments where swapped, intentional? > Yes. It's a minor test to make sure that the attribute can refer to a field that's defined after the pointer. > > + > > +/* > > + * 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); > > We should probably also get kunit_kmalloc_obj() at some point. > > > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, s); > > + > > + 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; > > + > > + 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_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 0)); > > + KUNIT_EXPECT_EQ(test, size, __builtin_dynamic_object_size(s->ptr, 1)); > > Is this builtin guaranteed to be available? > We have a wrapper KUNIT_EXPECT_BDOS() above. > I suppose not. I'll use the macros instead. > > + > > + /* 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); > > +} > > +#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */ > > +#endif /* CONFIG_CC_HAS_COUNTED_BY */ > > + > > static int fortify_test_init(struct kunit *test) > > { > > if (!IS_ENABLED(CONFIG_FORTIFY_SOURCE)) > > @@ -1054,6 +1167,12 @@ static struct kunit_case fortify_test_cases[] = { > > KUNIT_CASE(fortify_test_memchr_inv), > > KUNIT_CASE(fortify_test_memcmp), > > KUNIT_CASE(fortify_test_kmemdup), > > +#ifdef CONFIG_CC_HAS_COUNTED_BY > > + KUNIT_CASE(fortify_test_counted_by_flex), > > +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR > > The nesting of these conditionals looks unnecessary. > -bw > > + KUNIT_CASE(fortify_test_counted_by_ptr), > > +#endif /* CONFIG_CC_HAS_COUNTED_BY_PTR */ > > +#endif /* CONFIG_CC_HAS_COUNTED_BY */ > > {} > > }; > > > > -- > > 2.56.0.rc1.315.gc6ed9934b7-goog > > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3] fortify: add KUnit tests for __counted_by and __counted_by_ptr 2026-09-29 1:00 ` [PATCH] fortify: " Bill Wendling 2026-09-29 6:23 ` Thomas Weißschuh @ 2026-09-29 7:20 ` Bill Wendling 1 sibling, 0 replies; 7+ messages in thread From: Bill Wendling @ 2026-09-29 7:20 UTC (permalink / raw) To: Andrey Ryabinin, Andrew Morton Cc: Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Kees Cook, Gustavo A. R. Silva, kasan-dev, linux-mm, linux-kernel, linux-hardening, thomas.weissschuh, Bill Wendling 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 ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-29 7:20 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 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 ` [PATCH v3] " Bill Wendling
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®