> On Jul 18, 2023, at 11:37 AM, Qing Zhao via Gcc-patches 
> <gcc-patches@gcc.gnu.org> wrote:
> 
> 
> 
>> On Jul 17, 2023, at 7:40 PM, Kees Cook <keesc...@chromium.org> wrote:
>> 
>> On Mon, Jul 17, 2023 at 09:17:48PM +0000, Qing Zhao wrote:
>>> 
>>>> On Jul 13, 2023, at 4:31 PM, Kees Cook <keesc...@chromium.org> wrote:
>>>> 
>>>> In the bug, the problem is that "p" isn't known to be allocated, if I'm
>>>> reading that correctly?
>>> 
>>> I think that the major point in PR109557 
>>> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109557):
>>> 
>>> for the following pointer p.3_1, 
>>> 
>>> p.3_1 = p;
>>> _2 = __builtin_object_size (p.3_1, 0);
>>> 
>>> Question: why the size of p.3_1 cannot use the TYPE_SIZE of the pointee of 
>>> p when the TYPE_SIZE can be determined at compile time?
>>> 
>>> Answer:  From just knowing the type of the pointee of p, the compiler 
>>> cannot determine the size of the object.  
>> 
>> Why is that? "p" points to "struct P", which has a fixed size. There
>> must be an assumption somewhere that a pointer is allocated, otherwise
>> __bos would almost never work?
> 
> My understanding from the comments in PR109557 was: 
> 
> In general the pointer could point to the first object of an array that has 
> more elements, or to an object of a different type. 
> Without seeing the real allocation to the pointer, the compiler cannot assume 
> that the pointer point to an object that has
> the exact same type as its declaration. 
> 
> Sid and Martin, is the above understand correctly?
> 
> Honestly, I am still not 100% clear on this yet.
> 
> Jakub, Sid and Martin, could  you please explain a little bit more here, 
> especially for the following small example?
> 
> [opc@qinzhao-ol8u3-x86 109557]$ cat t.c
> #include <stdlib.h>
> #include <assert.h>
> struct P {
>  int k;
>  int x[10]; 
> } *p;
> 
> void store(int a, int b) 
> {
>  p = (struct P *)malloc (sizeof (struct P));
>  p->k = a;
>  p->x[b] = 0;
>  assert (__builtin_dynamic_object_size (p->x, 1) == sizeof (int[10]));
>  assert (__builtin_dynamic_object_size (p, 1) == sizeof (struct P));
>  return;
> }
> 
> int main()
> {
>  store(7, 7);
>  assert (__builtin_dynamic_object_size (p->x, 1) == sizeof (int[10]));
>  assert (__builtin_dynamic_object_size (p, 1) == sizeof (struct P));
>  free (p);
> }
> [opc@qinzhao-ol8u3-x86 109557]$ sh t
> /home/opc/Install/latest/bin/gcc -O -fdump-tree-all t.c
> a.out: t.c:21: main: Assertion `__builtin_dynamic_object_size (p->x, 1) == 
> sizeof (int[10])' failed.
> t: line 19: 859944 Aborted                 (core dumped) ./a.out
> 
> 

A correction to the above compilation option:
/home/opc/Install/latest-d/bin/gcc -O -fstrict-flex-arrays=3 t.c
a.out: t.c:22: main: Assertion `__builtin_dynamic_object_size (p, 1) == sizeof 
(struct P)' failed.
t: line 19: 918833 Aborted                 (core dumped) ./a.out

(All others keep the same).

Sorry for the mistake.

Qing

> In the above, among the 4 assertions, only the last one failed.
> 
> Why GCC can use the TYPE_SIZE of the pointee of the pointer “p->x” as the 
> size of the object, 
> but cannot use the TYPE_SIZE of the pointee of the pointer “p” as the size of 
> the object? 
> 
> 
>> 
>>> Therefore the bug has been closed. 
>>> 
>>> In your following testing 5:
>>> 
>>>> I'm not sure this is a reasonable behavior, but
>>>> let me get into the specific test, which looks like this:
>>>> 
>>>> TEST(counted_by_seen_by_bdos)
>>>> {
>>>>      struct annotated *p;
>>>>      int index = MAX_INDEX + unconst;
>>>> 
>>>>      p = alloc_annotated(index);
>>>> 
>>>>      REPORT_SIZE(p->array);
>>>> /* 1 */ EXPECT_EQ(sizeof(*p), offsetof(typeof(*p), array));
>>>>      /* Check array size alone. */
>>>> /* 2 */ EXPECT_EQ(__builtin_object_size(p->array, 1), SIZE_MAX);
>>>> /* 3 */ EXPECT_EQ(__builtin_dynamic_object_size(p->array, 1), p->foo * 
>>>> sizeof(*p->array));
>>>>      /* Check check entire object size. */
>>>> /* 4 */ EXPECT_EQ(__builtin_object_size(p, 1), SIZE_MAX);
>>>> /* 5 */ EXPECT_EQ(__builtin_dynamic_object_size(p, 1), sizeof(*p) + p->foo 
>>>> * sizeof(*p->array));
>>>> }
>>>> 
>>>> Test 5 should pass as well, since, again, p can be examined. Passing p
>>>> to __bdos implies it is allocated and the __counted_by annotation can be
>>>> examined.
>>> 
>>> Since the call to the routine “alloc_annotated" cannot be inlined, GCC does 
>>> not see any allocation calls for the pointer p.
>> 
>> Correct.
>> 
>>> At the same time, due to the same reason as PR109986, GCC cannot determine 
>>> the size of the object by just knowing the TYPE_SIZE
>>> of the pointee of p.  
>> 
>> So the difference between test 3 and test 5 is that "p" is explicitly
>> dereferenced to find "array", and therefore an assumption is established
>> that "p" is allocated?
> 
> Yes, this might be the assumption that GCC made  -:)
>> 
>>> So, this is exactly the same issue as PR109557.  It’s an existing behavior 
>>> per the current __buitlin_object_size algorithm.
>>> I am still not very sure whether the situation in PR109557 can be improved 
>>> or not, but either way, it’s a separate issue.
>> 
>> Okay, so the issue is one of object allocation visibility (or
>> assumptions there in)?
> 
> Might be, let’s see what Sid or Martin will say on this.
>> 
>>> Please see the new testing case I added for PR109557, you will see that the 
>>> above case 5 is a similar case as the new testing case in PR109557.
>> 
>> I will ponder this a bit more to see if I can come up with a useful
>> test case to replace the part from "test 5" above.
>> 
>>>> 
>>>> If "return p->array[index];" would be caught by the sanitizer, then
>>>> it follows that __builtin_dynamic_object_size(p, 1) must also know the
>>>> size. i.e. both must examine "p" and its trailing flexible array and
>>>> __counted_by annotation.
>>>> 
>>>>> 
>>>>> 2. The common issue for  the failed testing 3, 4, 9, 10 is:
>>>>> 
>>>>> for the following annotated structure: 
>>>>> 
>>>>> ====
>>>>> struct annotated {
>>>>>      unsigned long flags;
>>>>>      size_t foo;
>>>>>      int array[] __attribute__((counted_by (foo)));
>>>>> };
>>>>> 
>>>>> 
>>>>> struct annotated *p;
>>>>> int index = 16;
>>>>> 
>>>>> p = malloc(sizeof(*p) + index * sizeof(*p->array));  // allocated real 
>>>>> size 
>>>>> 
>>>>> p->foo = index + 2;  // p->foo was set by a different value than the real 
>>>>> size of p->array as in test 9 and 10
>>>> 
>>>> Right, tests 9 and 10 are checking that the _smallest_ possible value of
>>>> the array is used. (There are two sources of information: the allocation
>>>> size and the size calculated by counted_by. The smaller of the two
>>>> should be used when both are available.)
>>> 
>>> The counted_by attribute is used to annotate a Flexible array member on how 
>>> many elements it will have.
>>> However, if this information can not accurately reflect the real number of 
>>> elements for the array allocated, 
>>> What’s the purpose of such information? 
>> 
>> For example, imagine code that allocates space for 100 elements since
>> the common case is that the number of elements will grow over time.
>> Elements are added as it goes. For example:
>> 
>> struct grows {
>>      int alloc_count;
>>      int valid_count;
>>      struct element item[] __counted_by(valid_count);
>> } *p;
>> 
>> void something(void)
>> {
>>      p = malloc(sizeof(*p) + sizeof(*p->item) * 100);
>>      p->alloc_count = 100;
>>      p->valid_count = 0;
>> 
>>      /* this loop doesn't check that we don't go over 100. */
>>      while (items_to_copy) {
>>              struct element *item_ptr = get_next_item();
>>              /* __counted_by stays in sync: */
>>              p->valid_count++;
>>              p->item[p->valid_count - 1] = *item_ptr;
>>      }
>> }
>> 
>> We would want to catch cases there p->item[] is accessed with an index
>> that is >= p->valid_count, even though the allocation is (currently)
>> larger.
>> 
>> However, if we ever reached valid_count >= alloc_count, we need to trap
>> too, since we can still "see" the true allocation size.
>> 
>> Now, the __alloc_size hint is visible in very few places, so if there is
>> a strong reason to do so, I can live with saying that __counted_by takes
>> full precedence over __alloc_size. It seems it should be possible to
>> compare when both are present, but I can live with __counted_by being
>> the universal truth. :)
>> 
> 
> Thanks for the example and the explanation. Understood now.
> 
> LLVM’s RFC requires the following relationship: 
> (https://discourse.llvm.org/t/rfc-enforcing-bounds-safety-in-c-fbounds-safety/70854#maintaining-correctness-of-bounds-annotations-18)
> 
> Buffer’s real allocated size >= counted_by value
> 
> Should be true all the time. 
> 
> I think that this is a reasonable requirement. 
> 
> (Otherwise, if counted_by > buffer’s real allocated size, overflow might 
> happen)
> 
> Is the above enough to cover your use cases?
> 
> If so, I will study a little bit more to see how to implement this.
>>> 
>>>>> or
>>>>> p->foo was not set to any value as in test 3 and 4
>>>> 
>>>> For tests 3 and 4, yes, this was my mistake. I have fixed up the tests
>>>> now. Bill noticed the same issue. Sorry for the think-o!
>>>> 
>>>>> ====
>>>>> 
>>>>> i.e, the value of p->foo is NOT synced with the number of elements 
>>>>> allocated for the array p->array.  
>>>>> 
>>>>> I think that this should be considered as an user error, and the 
>>>>> documentation of the attribute should include
>>>>> this requirement.  (In the LLVM’s RFC, such requirement was included in 
>>>>> the programing model: 
>>>>> https://discourse.llvm.org/t/rfc-enforcing-bounds-safety-in-c-fbounds-safety/70854#maintaining-correctness-of-bounds-annotations-18)
>>>>> 
>>>>> We can add a new warning option -Wcounted-by to report such user error if 
>>>>> needed.
>>>>> 
>>>>> What’s your opinion on this?
>>>> 
>>>> I think it is correct to allow mismatch between allocation and
>>>> counted_by as long as only the least of the two is used.
>>> 
>>> What’s your mean by “only the least of the two is used”?
>> 
>> I was just restating the above: if size information is available via
>> both __alloc_size and __counted_by, the smaller of the two should be
>> used.
> 
> If we are consistent with LLVM’s RFC, __alloc_size >= __counted_by all the 
> time.
> Then we can always use the counted_by info for the array size. 
>> 
>>> 
>>>> This may be
>>>> desirable in a few situations. One example would be a large allocation
>>>> that is slowly filled up by the program.
>>> 
>>> So, for such situation, whenever the allocation is filled up, the field 
>>> that hold the “counted_by” attribute should be increased at the same time,
>>> Then, the “counted_by” value always sync with the real allocation. 
>>>> I.e. the counted_by member is
>>>> slowly increased during runtime (but not beyond the true allocation size).
>>> 
>>> Then there should be source code to increase the “counted_by” field 
>>> whenever the allocated space increased too. 
>>>> 
>>>> Of course allocation size is only available in limited situations, so
>>>> the loss of that info is fine: we have counted_by for everything else.
>>> 
>>> The point is: allocation size should synced with the value of “counted_by”. 
>>> LLVM’s RFC also have the similar requirement:
>>> https://discourse.llvm.org/t/rfc-enforcing-bounds-safety-in-c-fbounds-safety/70854#maintaining-correctness-of-bounds-annotations-18
>> 
>> Right, I'm saying it would be nice if __alloc_size was checked as well,
>> in the sense that if it is available, it knows without question what the
>> size of the allocation is. If __alloc_size and __counted_by conflict,
>> the smaller of the two should be the truth.
> 
>> 
>> But, as I said, if there is some need to explicitly ignore __alloc_size
>> when __counted_by is present, I can live with it; we just need to
>> document it.
>> 
>> If the RFC and you agree that the __counted_by variable can only ever be
>> (re)assigned after the flex array has been (re)allocated, then I guess
>> we'll see how it goes. :) I think most places in the kernel using
>> __counted_by will be fine, but I suspect we may have cases where we need
>> to update it like in the loop I described above. If that's true, we can
>> revisit the requirement then. :)
> 
> I guess if we can always keep  the relationship:   __alloc_size >= 
> __counted_by all the time. Should be fine.
> 
> Please check whether this is enough for kernel, I will check whether this is 
> doable For GCC.
> 
> thanks.
> 
> 
> Qing
>> 
>> -Kees
>> 
>> -- 
>> Kees Cook

Reply via email to