On 2025-06-18 15:14, Qing Zhao wrote:
struct fam_container
{
int a;
int count;
char fam[] __counted_by__ (count);
};
size_t
baz (struct fam_container *ptr)
{
...
ptr->count = 0;
__builtin_free (ptr);
...
return 0;
}
void
bar (size_t insz)
{
struct fam_container *c =
__builtin_malloc (insz + sizeof (struct fam_container));
c->count = insz;
sz = baz (c);
foo (c, sz);
}
void
__attribute__ ((noinline))
foo (struct fam_container *ptr, size_t sz)
{
...
objsz = __builtin_dynamic_object_size (ptr, 0);
__memcpy_chk (ptr, src, sz, objsz);
...
}
Because SZ is 0, in practice there would have been no dereference of PTR in
__memcpy_chk because the underlying memcpy would have not dereferenced PTR.
Here, _FORTIFY_SOURCE ends up actually introducing an invalid dereference
through the OBJSZ argument.
I hope that clarifies what I'm trying to say.
Yes, Now I see what did you mean from the above example.
However, I think that the use-after-free error in the source code should be
caught by a separate tool -fsanitize=address. (And I just checked the above
small testing case with -fsanitize=address, Yes, the use-after-free error is
detected during run-time). It’s not the job of __builtin_dynamic_object_size
to catch such errors.
I agree, validating invalid pointers is not the job of __bdos. My
concern is about having __bdos *generate* code that results in invalid
pointer access.
It should be reasonable to assume that in
__builtin_dynamic_object_size (ptr, 0);
ptr is NULL or point to a valid memory.
(I think that we might need clarify in the documentation of __bos and __bdos,
if an invalid pointer is passed to these builtins, the behavior is undefined)
Forcing compiler optimization or other sanitizers always check whether every
pointer reference might be use-after-free is not a reasonable requirement.
For example:
[opc@qinzhao-ol8u3-x86 ~]$ cat tt.c
struct fam_container
{
char fam[10];
int count;
};
void __attribute__ ((noinline))
baz (struct fam_container *ptr)
{
ptr->count = 0;
__builtin_free (ptr);
return;
}
void
__attribute__ ((noinline))
foo (struct fam_container *ptr)
{
__builtin_printf ("size of fam is %u \n”,
__builtin_dynamic_object_size (ptr->fam, 1));
}
void
bar ()
{
struct fam_container *c =
__builtin_malloc (sizeof (struct fam_container));
baz (c);
foo (c);
}
int main()
{
bar ();
return 0;
}
[opc@qinzhao-ol8u3-x86 ~]$ gcc -O2 tt.c;./a.out
size of fam is 10
In the above, we can see clearly that “ptr” is invalid when passed to
__builtin_dynamic_object_size,
However, the current __builtin_dynamic_object_size didn’t check such invalid
ptr and on the other hand, it
Incorrectly reports the size of the invalid pointer is 10. Is this wrong?
It's not wrong, because __bdos does not validate a pointer. They key
difference from the example I cited though, is that in this case the
size expression does not *create* a dereference of a pointer. With this
patch, __bdos will start doing that, which is the risky proposition.
I think that for such behavior, we should clarify in the documentation of
__bdos and __bos that:
if an invalid pointer is passed to these builtins, __bdos and __bos does
check for this. the behavior is undefined.
This should resolve the issue.
If we say that, it could indeed give us the ability to dereference
without validating even for NULL, although we'll still need it to avoid
a case where passes later assume validity of the pointer based on the
dereference __bdos generated. However I'm not sure if it's a net
positive because of the second example I shared (the SZ = 0 one);
there's a real chance that we introduce an active vulnerability in buggy
code that was at least not vulnerable.
Sid