* [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
@ 2026-09-04 2:02 Gustavo A. R. Silva
2026-09-17 6:58 ` Gustavo A. R. Silva
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Gustavo A. R. Silva @ 2026-09-04 2:02 UTC (permalink / raw)
To: Jakub Jelinek, Richard Biener, Siddhesh Poyarekar, Kees Cook
Cc: gcc-patches, Martin Uecker, josmyers, Bill Wendling,
linux-hardening, Gustavo A. R. Silva
For a pointer to a subobject whose record/union type ends in a flexible-array
member (directly, or through a trailing nested struct), addr_object_size()
walked up to the enclosing object (v = TREE_OPERAND (v, 0)) instead of
measuring the referenced subobject. __builtin_object_size() and
__builtin_dynamic_object_size() type 1 therefore returned the whole-object
size, collapsing type 1 onto type 0 and losing the distinction between
&p->inner and p. FORTIFY_SOURCE relies on the type-1 distinction, so this
weakens its bounds checks.
Fix this by computing the size directly from the referenced record/union
instead of walking up, under -fstrict-flex-arrays=3 specifically to avoid
conflicting with -fstrict-flex-arrays semantics at lower levels, thereby
restoring the type-0/type-1 distinction that Clang already implements.
Bootstrapped and regtested on x86_64-linux-gnu.
Changes in v2:
- Change behavior under -fstrict-flex-arrays=3 only.
- Update subject line and changelog text.
- Document the new type-1 behavior in extend.texi.
v1:
- Link: https://lore.kernel.org/linux-hardening/aojDH2Db6XIkVqcf@kspp/
PR tree-optimization/126975
gcc/ChangeLog:
* tree-object-size.cc (addr_object_size): For a reference to a
record or union type that recursively includes a flexible-array
member, compute the object size from the referenced subobject
instead of walking up to the enclosing object when
-fstrict-flex-arrays=3 is in effect.
* doc/extend.texi (__builtin_object_size): Document the type-1
behavior for pointers to subobjects that end in a flexible array
member, and its interaction with -fstrict-flex-arrays=3.
gcc/testsuite/ChangeLog:
* gcc.dg/builtin-object-size-pr126975.c: New test.
Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org>
---
gcc/doc/extend.texi | 32 +++++++++++++
.../gcc.dg/builtin-object-size-pr126975.c | 45 +++++++++++++++++++
gcc/tree-object-size.cc | 7 ++-
3 files changed, 82 insertions(+), 2 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 397b05ab87d..30fdc702f13 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -17894,6 +17894,38 @@ assert (__builtin_object_size (q, 0)
/* The subobject q points to is var.b. */
assert (__builtin_object_size (q, 1) == sizeof (var.b));
@end smallexample
+
+When, for @var{type} 1, the closest surrounding subobject is of a
+@code{struct} or @code{union} type that ends in a flexible array
+(@pxref{Zero Length,,Arrays of Length Zero}), either directly or through a
+trailing nested struct, that subobject can be extended past its declared
+size. Its size is therefore constrained only by the enclosing complete
+object, as it is for @var{type} 0.
+
+Under @option{-fstrict-flex-arrays=3}, where only a true C99
+flexible array member is treated as a flexible array, the declared size of
+the referenced subobject is used instead, which restores the distinction
+between @var{type} 0 and @var{type} 1 for such pointers.
+
+@smallexample
+/* Compiled with -fstrict-flex-arrays=3. */
+struct A @{ int n; char data[]; @};
+struct B @{ int m; struct A a; @};
+struct B *b = malloc (sizeof (struct B) + 48);
+struct B *volatile vp = malloc (sizeof (struct B) + 48);
+struct B *op = vp;
+
+/* &b->a ends in a flexible array member, but -fstrict-flex-arrays=3 makes
+ type 1 its declared size, not the whole-object (type 0) size. */
+assert (__builtin_object_size (&b->a, 1) == sizeof (b->a));
+/* Likewise when op is opaque, where type 0 would be (size_t) -1. */
+assert (__builtin_object_size (&op->a, 1) == sizeof (op->a));
+
+/* For a pointer to the flexible array member itself, type 0 and type 1
+ both return (size_t) -1 here. */
+assert (__builtin_object_size (op->a.data, 0) == (size_t) -1);
+assert (__builtin_object_size (op->a.data, 1) == (size_t) -1);
+@end smallexample
@enddefbuiltin
@defbuiltin{{size_t} __builtin_dynamic_object_size (const void * @var{ptr}, int @var{type})}
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c b/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
new file mode 100644
index 00000000000..c8c60c4f76e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
@@ -0,0 +1,45 @@
+/* PR 126975:
+ Under -fstrict-flex-arrays=3 only a true C99 flexible-array member is
+ treated as flexible. For &subobject whose type ends in such a member,
+ __builtin_object_size/__builtin_dynamic_object_size type 1 must measure
+ the subobject, not the tail of the allocation (type 0). */
+/* { dg-do run } */
+/* { dg-options "-O2 -fstrict-flex-arrays=3" } */
+
+#include "builtin-object-size-common.h"
+
+struct flex {
+ size_t count;
+ char fam[];
+};
+
+struct outer {
+ int hdr;
+ struct flex inner;
+};
+
+int main (void)
+{
+ struct outer *p = __builtin_malloc (sizeof(*p) + 48);
+ struct outer *volatile vp = __builtin_malloc (sizeof(*vp) + 48);
+ struct outer *op = vp;
+
+ /* True C99 flexible-array member: type 1 measures the subobject. */
+ EXPECT(__builtin_object_size(&p->inner, 1), sizeof(p->inner));
+ EXPECT(__builtin_dynamic_object_size(&p->inner, 1), sizeof(p->inner));
+
+ /* Type 0 still reports the whole tail of the allocation. */
+ EXPECT(__builtin_object_size(&p->inner, 0), sizeof(p->inner) + 48);
+ EXPECT(__builtin_dynamic_object_size(&p->inner, 0), sizeof(p->inner) + 48);
+
+ /* When op is opaque, type 0 is unknown (-1), but type 1 still measures
+ the subobject. */
+ EXPECT(__builtin_object_size(&op->inner, 0), -1);
+ EXPECT(__builtin_object_size(&op->inner, 1), sizeof(op->inner));
+ EXPECT(__builtin_dynamic_object_size(&op->inner, 1), sizeof(op->inner));
+
+ __builtin_free (p);
+ __builtin_free (op);
+
+ DONE ();
+}
diff --git a/gcc/tree-object-size.cc b/gcc/tree-object-size.cc
index 54c320d36d0..90d2ac32614 100644
--- a/gcc/tree-object-size.cc
+++ b/gcc/tree-object-size.cc
@@ -734,10 +734,13 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
}
/* if the ref is to a record or union type, but the type
does not include a flexible array recursively, compute
- the object size directly. */
+ the object size directly. With -fstrict-flex-arrays=3 do
+ the same even when it does, so type 1 measures the subobject
+ instead of collapsing onto the whole-object type-0 size. */
if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (v)))
{
- if (!TYPE_INCLUDES_FLEXARRAY (TREE_TYPE (v)))
+ if (!TYPE_INCLUDES_FLEXARRAY (TREE_TYPE (v))
+ || flag_strict_flex_arrays == 3)
{
v = NULL_TREE;
break;
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
2026-09-04 2:02 [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975] Gustavo A. R. Silva
@ 2026-09-17 6:58 ` Gustavo A. R. Silva
2026-09-18 16:19 ` Siddhesh Poyarekar
2026-09-19 4:22 ` Kees Cook
2 siblings, 0 replies; 7+ messages in thread
From: Gustavo A. R. Silva @ 2026-09-17 6:58 UTC (permalink / raw)
To: Jakub Jelinek, Richard Biener, Siddhesh Poyarekar, Kees Cook
Cc: gcc-patches, Martin Uecker, josmyers, Bill Wendling,
linux-hardening, Gustavo A. R. Silva
Hi all,
Friendly ping: who can review/apply this, please? :)
BTW, I have a patch ready to address this other bug:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127234
but I'd like to land this bugfix first.
Thanks!
-Gustavo
On 9/4/26 11:02, Gustavo A. R. Silva wrote:
> For a pointer to a subobject whose record/union type ends in a flexible-array
> member (directly, or through a trailing nested struct), addr_object_size()
> walked up to the enclosing object (v = TREE_OPERAND (v, 0)) instead of
> measuring the referenced subobject. __builtin_object_size() and
> __builtin_dynamic_object_size() type 1 therefore returned the whole-object
> size, collapsing type 1 onto type 0 and losing the distinction between
> &p->inner and p. FORTIFY_SOURCE relies on the type-1 distinction, so this
> weakens its bounds checks.
>
> Fix this by computing the size directly from the referenced record/union
> instead of walking up, under -fstrict-flex-arrays=3 specifically to avoid
> conflicting with -fstrict-flex-arrays semantics at lower levels, thereby
> restoring the type-0/type-1 distinction that Clang already implements.
>
> Bootstrapped and regtested on x86_64-linux-gnu.
>
> Changes in v2:
> - Change behavior under -fstrict-flex-arrays=3 only.
> - Update subject line and changelog text.
> - Document the new type-1 behavior in extend.texi.
>
> v1:
> - Link: https://lore.kernel.org/linux-hardening/aojDH2Db6XIkVqcf@kspp/
>
> PR tree-optimization/126975
>
> gcc/ChangeLog:
>
> * tree-object-size.cc (addr_object_size): For a reference to a
> record or union type that recursively includes a flexible-array
> member, compute the object size from the referenced subobject
> instead of walking up to the enclosing object when
> -fstrict-flex-arrays=3 is in effect.
> * doc/extend.texi (__builtin_object_size): Document the type-1
> behavior for pointers to subobjects that end in a flexible array
> member, and its interaction with -fstrict-flex-arrays=3.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/builtin-object-size-pr126975.c: New test.
>
> Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org>
> ---
> gcc/doc/extend.texi | 32 +++++++++++++
> .../gcc.dg/builtin-object-size-pr126975.c | 45 +++++++++++++++++++
> gcc/tree-object-size.cc | 7 ++-
> 3 files changed, 82 insertions(+), 2 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
>
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 397b05ab87d..30fdc702f13 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -17894,6 +17894,38 @@ assert (__builtin_object_size (q, 0)
> /* The subobject q points to is var.b. */
> assert (__builtin_object_size (q, 1) == sizeof (var.b));
> @end smallexample
> +
> +When, for @var{type} 1, the closest surrounding subobject is of a
> +@code{struct} or @code{union} type that ends in a flexible array
> +(@pxref{Zero Length,,Arrays of Length Zero}), either directly or through a
> +trailing nested struct, that subobject can be extended past its declared
> +size. Its size is therefore constrained only by the enclosing complete
> +object, as it is for @var{type} 0.
> +
> +Under @option{-fstrict-flex-arrays=3}, where only a true C99
> +flexible array member is treated as a flexible array, the declared size of
> +the referenced subobject is used instead, which restores the distinction
> +between @var{type} 0 and @var{type} 1 for such pointers.
> +
> +@smallexample
> +/* Compiled with -fstrict-flex-arrays=3. */
> +struct A @{ int n; char data[]; @};
> +struct B @{ int m; struct A a; @};
> +struct B *b = malloc (sizeof (struct B) + 48);
> +struct B *volatile vp = malloc (sizeof (struct B) + 48);
> +struct B *op = vp;
> +
> +/* &b->a ends in a flexible array member, but -fstrict-flex-arrays=3 makes
> + type 1 its declared size, not the whole-object (type 0) size. */
> +assert (__builtin_object_size (&b->a, 1) == sizeof (b->a));
> +/* Likewise when op is opaque, where type 0 would be (size_t) -1. */
> +assert (__builtin_object_size (&op->a, 1) == sizeof (op->a));
> +
> +/* For a pointer to the flexible array member itself, type 0 and type 1
> + both return (size_t) -1 here. */
> +assert (__builtin_object_size (op->a.data, 0) == (size_t) -1);
> +assert (__builtin_object_size (op->a.data, 1) == (size_t) -1);
> +@end smallexample
> @enddefbuiltin
>
> @defbuiltin{{size_t} __builtin_dynamic_object_size (const void * @var{ptr}, int @var{type})}
> diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c b/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
> new file mode 100644
> index 00000000000..c8c60c4f76e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
> @@ -0,0 +1,45 @@
> +/* PR 126975:
> + Under -fstrict-flex-arrays=3 only a true C99 flexible-array member is
> + treated as flexible. For &subobject whose type ends in such a member,
> + __builtin_object_size/__builtin_dynamic_object_size type 1 must measure
> + the subobject, not the tail of the allocation (type 0). */
> +/* { dg-do run } */
> +/* { dg-options "-O2 -fstrict-flex-arrays=3" } */
> +
> +#include "builtin-object-size-common.h"
> +
> +struct flex {
> + size_t count;
> + char fam[];
> +};
> +
> +struct outer {
> + int hdr;
> + struct flex inner;
> +};
> +
> +int main (void)
> +{
> + struct outer *p = __builtin_malloc (sizeof(*p) + 48);
> + struct outer *volatile vp = __builtin_malloc (sizeof(*vp) + 48);
> + struct outer *op = vp;
> +
> + /* True C99 flexible-array member: type 1 measures the subobject. */
> + EXPECT(__builtin_object_size(&p->inner, 1), sizeof(p->inner));
> + EXPECT(__builtin_dynamic_object_size(&p->inner, 1), sizeof(p->inner));
> +
> + /* Type 0 still reports the whole tail of the allocation. */
> + EXPECT(__builtin_object_size(&p->inner, 0), sizeof(p->inner) + 48);
> + EXPECT(__builtin_dynamic_object_size(&p->inner, 0), sizeof(p->inner) + 48);
> +
> + /* When op is opaque, type 0 is unknown (-1), but type 1 still measures
> + the subobject. */
> + EXPECT(__builtin_object_size(&op->inner, 0), -1);
> + EXPECT(__builtin_object_size(&op->inner, 1), sizeof(op->inner));
> + EXPECT(__builtin_dynamic_object_size(&op->inner, 1), sizeof(op->inner));
> +
> + __builtin_free (p);
> + __builtin_free (op);
> +
> + DONE ();
> +}
> diff --git a/gcc/tree-object-size.cc b/gcc/tree-object-size.cc
> index 54c320d36d0..90d2ac32614 100644
> --- a/gcc/tree-object-size.cc
> +++ b/gcc/tree-object-size.cc
> @@ -734,10 +734,13 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
> }
> /* if the ref is to a record or union type, but the type
> does not include a flexible array recursively, compute
> - the object size directly. */
> + the object size directly. With -fstrict-flex-arrays=3 do
> + the same even when it does, so type 1 measures the subobject
> + instead of collapsing onto the whole-object type-0 size. */
> if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (v)))
> {
> - if (!TYPE_INCLUDES_FLEXARRAY (TREE_TYPE (v)))
> + if (!TYPE_INCLUDES_FLEXARRAY (TREE_TYPE (v))
> + || flag_strict_flex_arrays == 3)
> {
> v = NULL_TREE;
> break;
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
2026-09-04 2:02 [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975] Gustavo A. R. Silva
2026-09-17 6:58 ` Gustavo A. R. Silva
@ 2026-09-18 16:19 ` Siddhesh Poyarekar
2026-09-19 4:22 ` Kees Cook
2 siblings, 0 replies; 7+ messages in thread
From: Siddhesh Poyarekar @ 2026-09-18 16:19 UTC (permalink / raw)
To: Gustavo A. R. Silva, Jakub Jelinek, Richard Biener, Kees Cook
Cc: gcc-patches, Martin Uecker, josmyers, Bill Wendling,
linux-hardening, Gustavo A. R. Silva
On 2026-09-03 22:02, Gustavo A. R. Silva wrote:
> For a pointer to a subobject whose record/union type ends in a flexible-array
> member (directly, or through a trailing nested struct), addr_object_size()
> walked up to the enclosing object (v = TREE_OPERAND (v, 0)) instead of
> measuring the referenced subobject. __builtin_object_size() and
> __builtin_dynamic_object_size() type 1 therefore returned the whole-object
> size, collapsing type 1 onto type 0 and losing the distinction between
> &p->inner and p. FORTIFY_SOURCE relies on the type-1 distinction, so this
> weakens its bounds checks.
>
> Fix this by computing the size directly from the referenced record/union
> instead of walking up, under -fstrict-flex-arrays=3 specifically to avoid
> conflicting with -fstrict-flex-arrays semantics at lower levels, thereby
> restoring the type-0/type-1 distinction that Clang already implements.
I don't understand the rationale, why is only the declared size the
correct answer for maximum size even for -fstrict-flex-arrays=3? The
correct maximum size answer should also include the computed size of the
flex array since it's part of the inner object.
> Bootstrapped and regtested on x86_64-linux-gnu.
>
> Changes in v2:
> - Change behavior under -fstrict-flex-arrays=3 only.
> - Update subject line and changelog text.
> - Document the new type-1 behavior in extend.texi.
>
> v1:
> - Link: https://lore.kernel.org/linux-hardening/aojDH2Db6XIkVqcf@kspp/
>
> PR tree-optimization/126975
>
> gcc/ChangeLog:
>
> * tree-object-size.cc (addr_object_size): For a reference to a
> record or union type that recursively includes a flexible-array
> member, compute the object size from the referenced subobject
> instead of walking up to the enclosing object when
> -fstrict-flex-arrays=3 is in effect.
> * doc/extend.texi (__builtin_object_size): Document the type-1
> behavior for pointers to subobjects that end in a flexible array
> member, and its interaction with -fstrict-flex-arrays=3.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/builtin-object-size-pr126975.c: New test.
>
> Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org>
> ---
> gcc/doc/extend.texi | 32 +++++++++++++
> .../gcc.dg/builtin-object-size-pr126975.c | 45 +++++++++++++++++++
> gcc/tree-object-size.cc | 7 ++-
> 3 files changed, 82 insertions(+), 2 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
>
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 397b05ab87d..30fdc702f13 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -17894,6 +17894,38 @@ assert (__builtin_object_size (q, 0)
> /* The subobject q points to is var.b. */
> assert (__builtin_object_size (q, 1) == sizeof (var.b));
> @end smallexample
> +
> +When, for @var{type} 1, the closest surrounding subobject is of a
> +@code{struct} or @code{union} type that ends in a flexible array
> +(@pxref{Zero Length,,Arrays of Length Zero}), either directly or through a
> +trailing nested struct, that subobject can be extended past its declared
> +size. Its size is therefore constrained only by the enclosing complete
> +object, as it is for @var{type} 0.
> +
> +Under @option{-fstrict-flex-arrays=3}, where only a true C99
> +flexible array member is treated as a flexible array, the declared size of
> +the referenced subobject is used instead, which restores the distinction
> +between @var{type} 0 and @var{type} 1 for such pointers.
> +
> +@smallexample
> +/* Compiled with -fstrict-flex-arrays=3. */
> +struct A @{ int n; char data[]; @};
> +struct B @{ int m; struct A a; @};
> +struct B *b = malloc (sizeof (struct B) + 48);
> +struct B *volatile vp = malloc (sizeof (struct B) + 48);
> +struct B *op = vp;
> +
> +/* &b->a ends in a flexible array member, but -fstrict-flex-arrays=3 makes
> + type 1 its declared size, not the whole-object (type 0) size. */
> +assert (__builtin_object_size (&b->a, 1) == sizeof (b->a));
> +/* Likewise when op is opaque, where type 0 would be (size_t) -1. */
> +assert (__builtin_object_size (&op->a, 1) == sizeof (op->a));
> +
> +/* For a pointer to the flexible array member itself, type 0 and type 1
> + both return (size_t) -1 here. */
> +assert (__builtin_object_size (op->a.data, 0) == (size_t) -1);
> +assert (__builtin_object_size (op->a.data, 1) == (size_t) -1);
> +@end smallexample
> @enddefbuiltin
>
> @defbuiltin{{size_t} __builtin_dynamic_object_size (const void * @var{ptr}, int @var{type})}
> diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c b/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
> new file mode 100644
> index 00000000000..c8c60c4f76e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-object-size-pr126975.c
> @@ -0,0 +1,45 @@
> +/* PR 126975:
> + Under -fstrict-flex-arrays=3 only a true C99 flexible-array member is
> + treated as flexible. For &subobject whose type ends in such a member,
> + __builtin_object_size/__builtin_dynamic_object_size type 1 must measure
> + the subobject, not the tail of the allocation (type 0). */
> +/* { dg-do run } */
> +/* { dg-options "-O2 -fstrict-flex-arrays=3" } */
> +
> +#include "builtin-object-size-common.h"
> +
> +struct flex {
> + size_t count;
> + char fam[];
> +};
> +
> +struct outer {
> + int hdr;
> + struct flex inner;
> +};
> +
> +int main (void)
> +{
> + struct outer *p = __builtin_malloc (sizeof(*p) + 48);
> + struct outer *volatile vp = __builtin_malloc (sizeof(*vp) + 48);
> + struct outer *op = vp;
> +
> + /* True C99 flexible-array member: type 1 measures the subobject. */
> + EXPECT(__builtin_object_size(&p->inner, 1), sizeof(p->inner));
> + EXPECT(__builtin_dynamic_object_size(&p->inner, 1), sizeof(p->inner));
> +
> + /* Type 0 still reports the whole tail of the allocation. */
> + EXPECT(__builtin_object_size(&p->inner, 0), sizeof(p->inner) + 48);
> + EXPECT(__builtin_dynamic_object_size(&p->inner, 0), sizeof(p->inner) + 48);
> +
> + /* When op is opaque, type 0 is unknown (-1), but type 1 still measures
> + the subobject. */
> + EXPECT(__builtin_object_size(&op->inner, 0), -1);
> + EXPECT(__builtin_object_size(&op->inner, 1), sizeof(op->inner));
> + EXPECT(__builtin_dynamic_object_size(&op->inner, 1), sizeof(op->inner));
I think the ideal correct answer for __bdos(&op->inner, 1) is in fact
`sizeof (*op) + 48 - __offsetof (outer, inner)` since fam is part of
inner. The bare sizeof excludes memory allocated for the fam.
In case of an opaque pointer one may estimate the *minimum* subobject
size as being just sizeof (inner).
Thanks,
Sid
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
2026-09-04 2:02 [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975] Gustavo A. R. Silva
2026-09-17 6:58 ` Gustavo A. R. Silva
2026-09-18 16:19 ` Siddhesh Poyarekar
@ 2026-09-19 4:22 ` Kees Cook
2026-09-19 13:27 ` Siddhesh Poyarekar
2 siblings, 1 reply; 7+ messages in thread
From: Kees Cook @ 2026-09-19 4:22 UTC (permalink / raw)
To: Gustavo A. R. Silva
Cc: Jakub Jelinek, Richard Biener, Siddhesh Poyarekar, gcc-patches,
Martin Uecker, josmyers, Bill Wendling, linux-hardening,
Gustavo A. R. Silva
On Thu, Sep 03, 2026 at 08:02:20PM -0600, Gustavo A. R. Silva wrote:
> For a pointer to a subobject whose record/union type ends in a flexible-array
> member (directly, or through a trailing nested struct), addr_object_size()
> walked up to the enclosing object (v = TREE_OPERAND (v, 0)) instead of
> measuring the referenced subobject. __builtin_object_size() and
> __builtin_dynamic_object_size() type 1 therefore returned the whole-object
> size, collapsing type 1 onto type 0 and losing the distinction between
> &p->inner and p. FORTIFY_SOURCE relies on the type-1 distinction, so this
> weakens its bounds checks.
Looking at the PR:
struct flex {
size_t count;
char fam[] __attribute__((counted_by(count)));
}; /* sizeof(struct flex) == 8 */
struct outer {
int hdr;
struct flex inner;
}; /* sizeof(struct outer) == 16 */
int main (void)
{
struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte object */
/* This is a bug. */
expect(__builtin_object_size (&p->inner, 1), sizeof(p->inner));
expect(__builtin_dynamic_object_size (&p->inner, 1), sizeof(p->inner));
...
}
Before:
WAT: __builtin_object_size (&p->inner, 1) == 56 (expected 8)
WAT: __builtin_dynamic_object_size (&p->inner, 1) == 56 (expected 8)
After:
ok: __builtin_object_size (&p->inner, 1) == 8
ok: __builtin_dynamic_object_size (&p->inner, 1) == 8
This makes sense to me, the dynamic size of "fam" is ambiguous between
being 0 or 48 (from the "alloc_size" attribute), so type 1 chooses the
smaller.
I would, however, expect the use of counted_by to change it. For
example, if it were this:
struct flex {
size_t count;
char fam[] __attribute__((counted_by(count)));
};
...
struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte object */
p->inner.count = 48;
I would expect the results to be:
ok: __builtin_object_size (&p->inner, 1) == 8 /* compile-time size */
ok: __builtin_dynamic_object_size (&p->inner, 1) == 56 /* run-time size */
As the known size of p->inner is everything contained by "inner" and
that now includes the 48 bytes of "fam".
-Kees
--
Kees Cook
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
2026-09-19 4:22 ` Kees Cook
@ 2026-09-19 13:27 ` Siddhesh Poyarekar
2026-09-21 11:48 ` Gustavo A. R. Silva
0 siblings, 1 reply; 7+ messages in thread
From: Siddhesh Poyarekar @ 2026-09-19 13:27 UTC (permalink / raw)
To: Kees Cook, Gustavo A. R. Silva
Cc: Jakub Jelinek, Richard Biener, gcc-patches, Martin Uecker,
josmyers, Bill Wendling, linux-hardening, Gustavo A. R. Silva
On 2026-09-19 00:22, Kees Cook wrote:
> On Thu, Sep 03, 2026 at 08:02:20PM -0600, Gustavo A. R. Silva wrote:
>> For a pointer to a subobject whose record/union type ends in a flexible-array
>> member (directly, or through a trailing nested struct), addr_object_size()
>> walked up to the enclosing object (v = TREE_OPERAND (v, 0)) instead of
>> measuring the referenced subobject. __builtin_object_size() and
>> __builtin_dynamic_object_size() type 1 therefore returned the whole-object
>> size, collapsing type 1 onto type 0 and losing the distinction between
>> &p->inner and p. FORTIFY_SOURCE relies on the type-1 distinction, so this
>> weakens its bounds checks.
>
> Looking at the PR:
>
> struct flex {
> size_t count;
> char fam[] __attribute__((counted_by(count)));
> }; /* sizeof(struct flex) == 8 */
>
> struct outer {
> int hdr;
> struct flex inner;
> }; /* sizeof(struct outer) == 16 */
>
> int main (void)
> {
> struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte object */
>
> /* This is a bug. */
> expect(__builtin_object_size (&p->inner, 1), sizeof(p->inner));
> expect(__builtin_dynamic_object_size (&p->inner, 1), sizeof(p->inner));
> ...
> }
>
> Before:
>
> WAT: __builtin_object_size (&p->inner, 1) == 56 (expected 8)
> WAT: __builtin_dynamic_object_size (&p->inner, 1) == 56 (expected 8)
>
> After:
>
> ok: __builtin_object_size (&p->inner, 1) == 8
> ok: __builtin_dynamic_object_size (&p->inner, 1) == 8
>
>
> This makes sense to me, the dynamic size of "fam" is ambiguous between
> being 0 or 48 (from the "alloc_size" attribute), so type 1 chooses the
> smaller.
I think you're confusing type 1 with type 2; type 1 is the *maximum*
subobject size, not the minimum. The whole object and subobject size
distinction is only material if there are members after inner, which is
technically "supported" by the complier as an extension, but is a
terrible idea for members with FAM.
Here are the different possibilities for &p->inner:
1. If the allocation via __builtin_malloc is visible: same result for
types 0, 1, 2 and 3, i.e. allocated size - offsetof (struct outer, inner)
2. If the __builtin_malloc is not visible and there's no counted_by
annotation:
- type 0 and type 1: cannot determine size, since FAM could be any
arbitrary size. So, -1
- type 2 and type 3: sizeof (&p->inner), since that's the minimum
estimate.
3. if the __builtin_malloc is not visible and FAM has __counted_by__
annotation: same result for all types: sizeof (&p->inner) + p->count
> I would, however, expect the use of counted_by to change it. For
> example, if it were this:
>
> struct flex {
> size_t count;
> char fam[] __attribute__((counted_by(count)));
> };
> ...
> struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte object */
> p->inner.count = 48;
>
> I would expect the results to be:
>
> ok: __builtin_object_size (&p->inner, 1) == 8 /* compile-time size */
> ok: __builtin_dynamic_object_size (&p->inner, 1) == 56 /* run-time size */
>
> As the known size of p->inner is everything contained by "inner" and
> that now includes the 48 bytes of "fam".
__builtin_object_size is not the compile time size, it is a runtime
constant size estimate, with the `type` argument deciding if the size
estimate is on the maximum or minimum end, and if it considers the whole
object or only the immediate containing subobject that the input pointer
points to. In the presence of FAMs I think sizeof (i.e. the compile
time size) should be seen as a minimum size estimate for the subobject,
if you want to make an equivalence with __builtin_object_size.
Similarly, __builtin_dynamic_object_size is exactly the same as
__builtin_object_size, except that it can return non-constant
expressions too, not just constants.
Thanks,
Sid
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
2026-09-19 13:27 ` Siddhesh Poyarekar
@ 2026-09-21 11:48 ` Gustavo A. R. Silva
2026-09-21 12:46 ` Siddhesh Poyarekar
0 siblings, 1 reply; 7+ messages in thread
From: Gustavo A. R. Silva @ 2026-09-21 11:48 UTC (permalink / raw)
To: Siddhesh Poyarekar, Kees Cook, Gustavo A. R. Silva
Cc: Jakub Jelinek, Richard Biener, gcc-patches, Martin Uecker,
josmyers, Bill Wendling, linux-hardening, Gustavo A. R. Silva
On 9/19/26 22:27, Siddhesh Poyarekar wrote:
> On 2026-09-19 00:22, Kees Cook wrote:
>> On Thu, Sep 03, 2026 at 08:02:20PM -0600, Gustavo A. R. Silva wrote:
>>> For a pointer to a subobject whose record/union type ends in a flexible-array
>>> member (directly, or through a trailing nested struct), addr_object_size()
>>> walked up to the enclosing object (v = TREE_OPERAND (v, 0)) instead of
>>> measuring the referenced subobject. __builtin_object_size() and
>>> __builtin_dynamic_object_size() type 1 therefore returned the whole-object
>>> size, collapsing type 1 onto type 0 and losing the distinction between
>>> &p->inner and p. FORTIFY_SOURCE relies on the type-1 distinction, so this
>>> weakens its bounds checks.
>>
>> Looking at the PR:
>>
>> struct flex {
>> size_t count;
>> char fam[] __attribute__((counted_by(count)));
>> }; /* sizeof(struct flex) == 8 */
>>
>> struct outer {
>> int hdr;
>> struct flex inner;
>> }; /* sizeof(struct outer) == 16 */
>>
>> int main (void)
>> {
>> struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte object */
>>
>> /* This is a bug. */
>> expect(__builtin_object_size (&p->inner, 1), sizeof(p->inner));
>> expect(__builtin_dynamic_object_size (&p->inner, 1), sizeof(p->inner));
>> ...
>> }
>>
>> Before:
>>
>> WAT: __builtin_object_size (&p->inner, 1) == 56 (expected 8)
>> WAT: __builtin_dynamic_object_size (&p->inner, 1) == 56 (expected 8)
>>
>> After:
>>
>> ok: __builtin_object_size (&p->inner, 1) == 8
>> ok: __builtin_dynamic_object_size (&p->inner, 1) == 8
>>
>>
>> This makes sense to me, the dynamic size of "fam" is ambiguous between
>> being 0 or 48 (from the "alloc_size" attribute), so type 1 chooses the
>> smaller.
>
> I think you're confusing type 1 with type 2; type 1 is the *maximum* subobject size, not the minimum. The whole object and subobject size distinction is only
> material if there are members after inner, which is technically "supported" by the complier as an extension, but is a terrible idea for members with FAM.
>
> Here are the different possibilities for &p->inner:
>
> 1. If the allocation via __builtin_malloc is visible: same result for types 0, 1, 2 and 3, i.e. allocated size - offsetof (struct outer, inner)
When the allocation is visible and the FAM is annotated with counted_by, we use
the information provided by counted_by. See commit 6f17933548fc ("Use the
.ACCESS_WITH_SIZE in builtin object size.")
BTW, I recently filed this related issue:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127234
>
> 2. If the __builtin_malloc is not visible and there's no counted_by annotation:
> - type 0 and type 1: cannot determine size, since FAM could be any arbitrary size. So, -1
> - type 2 and type 3: sizeof (&p->inner), since that's the minimum estimate.
>
> 3. if the __builtin_malloc is not visible and FAM has __counted_by__ annotation: same result for all types: sizeof (&p->inner) + p->count
>
>> I would, however, expect the use of counted_by to change it. For
>> example, if it were this:
>>
>> struct flex {
>> size_t count;
>> char fam[] __attribute__((counted_by(count)));
>> };
>> ...
>> struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte object */
>> p->inner.count = 48;
>>
>> I would expect the results to be:
>>
>> ok: __builtin_object_size (&p->inner, 1) == 8 /* compile-time size */
>> ok: __builtin_dynamic_object_size (&p->inner, 1) == 56 /* run-time size */
I agree with counted_by changing things here. This is actually something
that Jakub mentioned in a comment to v1 of this patch:
"What we certainly can change is the behavior when
-fstrict-flex-arrays unless it already behaves the expected way,
and perhaps also when the flex array has counted_by attribute."
See: https://lore.kernel.org/linux-hardening/aogVH8UtICMP1Ihv@tucnak/#t
BTW, I have a patch ready for PR127234 (the missed counted_by bug) where
I implemented some helper functions to get the size of the object based
on the information provided by counted_by. I could use those in this PR. :)
Thanks
-Gustavo
>>
>> As the known size of p->inner is everything contained by "inner" and
>> that now includes the 48 bytes of "fam".
> __builtin_object_size is not the compile time size, it is a runtime constant size estimate, with the `type` argument deciding if the size estimate is on the
> maximum or minimum end, and if it considers the whole object or only the immediate containing subobject that the input pointer points to. In the presence of
> FAMs I think sizeof (i.e. the compile time size) should be seen as a minimum size estimate for the subobject, if you want to make an equivalence with
> __builtin_object_size.
>
> Similarly, __builtin_dynamic_object_size is exactly the same as __builtin_object_size, except that it can return non-constant expressions too, not just constants.
>
> Thanks,
> Sid
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975]
2026-09-21 11:48 ` Gustavo A. R. Silva
@ 2026-09-21 12:46 ` Siddhesh Poyarekar
0 siblings, 0 replies; 7+ messages in thread
From: Siddhesh Poyarekar @ 2026-09-21 12:46 UTC (permalink / raw)
To: Gustavo A. R. Silva, Kees Cook, Gustavo A. R. Silva
Cc: Jakub Jelinek, Richard Biener, gcc-patches, Martin Uecker,
josmyers, Bill Wendling, linux-hardening, Gustavo A. R. Silva
On 2026-09-21 07:48, Gustavo A. R. Silva wrote:
>> I think you're confusing type 1 with type 2; type 1 is the *maximum*
>> subobject size, not the minimum. The whole object and subobject size
>> distinction is only material if there are members after inner, which
>> is technically "supported" by the complier as an extension, but is a
>> terrible idea for members with FAM.
>>
>> Here are the different possibilities for &p->inner:
>>
>> 1. If the allocation via __builtin_malloc is visible: same result for
>> types 0, 1, 2 and 3, i.e. allocated size - offsetof (struct outer, inner)
>
> When the allocation is visible and the FAM is annotated with counted_by,
> we use
> the information provided by counted_by. See commit 6f17933548fc ("Use the
> .ACCESS_WITH_SIZE in builtin object size.")
Yes, the counted_by would basically override allocated size information,
since the .ACCESS_WITH_SIZE gets emitted after the allocation.
> BTW, I recently filed this related issue:
>
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127234
OK, so is this the core problem you're trying to solve? It's different
from pr126975, which AFAICT is NOTABUG since it confuses type-1 with type-3.
>> 2. If the __builtin_malloc is not visible and there's no counted_by
>> annotation:
>> - type 0 and type 1: cannot determine size, since FAM could be any
>> arbitrary size. So, -1
>> - type 2 and type 3: sizeof (&p->inner), since that's the minimum
>> estimate.
>>
>> 3. if the __builtin_malloc is not visible and FAM has __counted_by__
>> annotation: same result for all types: sizeof (&p->inner) + p->count
>>
>>> I would, however, expect the use of counted_by to change it. For
>>> example, if it were this:
>>>
>>> struct flex {
>>> size_t count;
>>> char fam[] __attribute__((counted_by(count)));
>>> };
>>> ...
>>> struct outer *p = __builtin_malloc (sizeof (*p) + 48); /* 64-byte
>>> object */
>>> p->inner.count = 48;
>>>
>>> I would expect the results to be:
>>>
>>> ok: __builtin_object_size (&p->inner, 1) == 8 /* compile-time
>>> size */
>>> ok: __builtin_dynamic_object_size (&p->inner, 1) == 56 /* run-time
>>> size */
>
> I agree with counted_by changing things here. This is actually something
> that Jakub mentioned in a comment to v1 of this patch:
>
> "What we certainly can change is the behavior when
> -fstrict-flex-arrays unless it already behaves the expected way,
> and perhaps also when the flex array has counted_by attribute."
>
> See: https://lore.kernel.org/linux-hardening/aogVH8UtICMP1Ihv@tucnak/#t
>
> BTW, I have a patch ready for PR127234 (the missed counted_by bug) where
> I implemented some helper functions to get the size of the object based
> on the information provided by counted_by. I could use those in this PR. :)
Yes, I agree this would be interesting to fix. I'm not sure if it needs
to be under -fstrict-flex-arrays=3 though, since the size of the FAM
itself will always be dictated by the counted_by; this will just be an
extension of that behaviour.
Thanks,
Sid
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-21 14:04 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 2:02 [PATCH v2] tree-object-size: Fix type-1 size for FAM subobjects under -fstrict-flex-arrays=3 [PR126975] Gustavo A. R. Silva
2026-09-17 6:58 ` Gustavo A. R. Silva
2026-09-18 16:19 ` Siddhesh Poyarekar
2026-09-19 4:22 ` Kees Cook
2026-09-19 13:27 ` Siddhesh Poyarekar
2026-09-21 11:48 ` Gustavo A. R. Silva
2026-09-21 12:46 ` Siddhesh Poyarekar
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.