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 <[email protected]>
> ---
> 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
>