> On Jun 18, 2025, at 15:50, Siddhesh Poyarekar <siddh...@gotplt.org> wrote: > > 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.
Then even the current implementation for using counted_by information for FAM should check the invalid pointer? Right now, for a slightly modified testing case for the above tt.c (change the fixed size array “fam[10]” to a flexible array member with counted_by): [opc@qinzhao-ol8u3-x86 ~]$ cat ttt.c struct fam_container { int count; char fam[] __attribute__ ((counted_by (count))); }; void __attribute__ ((noinline)) baz (struct fam_container *ptr) { ptr->count = 0; __builtin_free (ptr); } 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 (unsigned insz) { struct fam_container *c = __builtin_malloc (insz + sizeof (struct fam_container)); c->count = insz; baz (c); foo (c); } int main() { bar (10); return 0; } [opc@qinzhao-ol8u3-x86 ~]$ gcc -O2 ttt.c;./a.out size of fam is 6847 In the above example, when ptr->fam is passed to __builtin_dynamic_object_size, we assume that “ptr” should be a valid pointer and should not be NULL, therefore, ptr->count is generated to represent the size of the array. However, now, even when ptr->fam is available in the source code, we cannot assume that the “ptr” is valid, is such assumption too conservative? I still think that "checking the pointer is valid” should separate from __bdos/bos. > >> 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. The above had a typo, the clarification in DOC should be: "if an invalid pointer is passed to these builtins, __bdos and __bos does NOT check for this. the behavior of the __bdos/__bos for an invalid pointer passed to it is undefined.” > > If we say that, it could indeed give us the ability to dereference without > validating even for NULL, We should check for NULL for the current patch since NULL is a valid value for a pointer. When ptr->fam is passed to __bdos in the source code as following: (the situation we already handled in __bdos) __builtin_dynamic_object_size (ptr->fam, 1)); “ptr" is guaranteed not a NULL. So, we don’t need to check for NULL when generating prt->count to represent the size; However, when ptr is passed to __bdos as the following: (as the current patch try to handle) __builtin_dynamic_object_size (ptr, 1)); “ptr" could be a NULL since NULL is a valid pointer value. If ptr is NULL, generating “ptr->count” to represent the size will introduce NEW undefined behavior into the program, which is wrong behavior we should avoid. Therefore, when __bdos only see the pointer “ptr”, not “ptr->fam”, we should check “ptr” is NOT NULL first before generating “ptr->count”. > 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. As long as we don’t introduce NEW undefined behavior code, I think we are doing correctly. Qing > > Sid