* [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®