mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®