> On Aug 3, 2023, at 1:51 PM, Kees Cook <k...@kernel.org> wrote:
> 
> On August 3, 2023 10:34:24 AM PDT, Qing Zhao <qing.z...@oracle.com> wrote:
>> One thing I need to point out first is, currently, even for regular fixed 
>> size array in the structure,
>> We have this same issue, for example:
>> 
>> #define LENGTH 10
>> 
>> struct fix {
>> size_t foo;
>> int array[LENGTH];
>> };
>> 
>> …
>> int main ()
>> {
>> struct fix *p;
>> p = alloc_buf_more ();
>> 
>> expect(__builtin_object_size(p->array, 1), LENGTH * sizeof(int));
>> expect(__builtin_object_size(p->array, 0), -1);
>> }
>> 
>> Currently, for __builtin_object_size(p->array, 0),  GCC return UNKNOWN for 
>> it.
>> This is not a special issue for flexible array member.
> 
> Is this true with -fstrict-flex-arrays=3 ?

Yes. 


Please see the following testing case:
#include <stdint.h>
#include <malloc.h>

#define LENGTH 10
#define SIZE_BUMP 5 
#define noinline __attribute__((__noinline__))

struct fix {
  size_t foo;
  int array[LENGTH]; 
};

#define expect(p, _v) do { \
    size_t v = _v; \
    if (p == v) \
        __builtin_printf ("ok:  %s == %zd\n", #p, p); \
    else \
        {  \
          __builtin_printf ("WAT: %s == %zd (expected %zd)\n", #p, p, v); \
        } \
} while (0);


/* in the following function, malloc allocated more space than size of the 
   struct fix.  Then what's the correct behavior we expect
   the __builtin_object_size should have for the following?
 */

static struct fix * noinline alloc_buf_more ()
{
  struct fix *p;
  p = malloc(sizeof (struct fix) + SIZE_BUMP * sizeof (int)); 

  /*when checking the observed access p->array, we have info on both
    observered allocation and observed access, 
    A. from observed allocation (alloc_size): (LENGTH + SIZE_BUMP) * sizeof 
(int)
    B. from observed access (TYPE): LENGTH * sizeof (int)
   */
   
  /* for MAXIMUM size in the whole object: currently, GCC always used the A.  */
  expect(__builtin_object_size(p->array, 0), (LENGTH + SIZE_BUMP) * 
sizeof(int));

  /* for MAXIMUM size in the sub-object: currently, GCC chose the smaller
     one among these two: B.  */
  expect(__builtin_object_size(p->array, 1), LENGTH * sizeof(int));

  return p;
}

/* in the following function, malloc allocated less space than size of the 
   struct fix.  Then what's the correct behavior we expect
   the __builtin_object_size should have for the following?
 */

static struct fix * noinline alloc_buf_less ()
{
  struct fix *p;
  p = malloc(sizeof (struct fix) - SIZE_BUMP * sizeof (int)); 

  /*when checking the observed access p->array, we have info on both
    observered allocation and observed access, 
    A. from observed allocation (alloc_size): (LENGTH - SIZE_BUMP) * sizeof 
(int)
    B. from observed access (TYPE): LENGTH * sizeof (int)
   */
   
  /* for MAXIMUM size in the whole object: currently, GCC always used the A.  */
  expect(__builtin_object_size(p->array, 0), (LENGTH - SIZE_BUMP) * 
sizeof(int));

  /* for MAXIMUM size in the sub-object: currently, GCC chose the smaller
     one among these two: B.  */
  expect(__builtin_object_size(p->array, 1), (LENGTH - SIZE_BUMP) * 
sizeof(int));

  return p;
}


int main ()
{
  struct fix *p, *q; 
  p = alloc_buf_more ();
  q = alloc_buf_less ();

  expect(__builtin_object_size(p->array, 1), LENGTH * sizeof(int));
  expect(__builtin_object_size(p->array, 0), -1);
  /*when checking the pointer p, we have no observed allocation nor observed 
access.
    therefore, we cannot determine the size info here.  */
  expect(__builtin_object_size(p, 1), -1);
  expect(__builtin_object_size(p, 0), -1);

  /*when checking the observed access p->array, we only have info on the
    observed access, i.e, the TYPE_SIZE info from the access. We don't have
    info on the whole object.  */
  expect(__builtin_object_size(q->array, 1), LENGTH * sizeof(int));
  expect(__builtin_object_size(q->array, 0), -1);
  /*when checking the pointer p, we have no observed allocation nor observed 
access.
    therefore, we cannot determine the size info here.  */
  expect(__builtin_object_size(q, 1), -1);
  expect(__builtin_object_size(q, 0), -1);

  return 0;
}

[opc@qinzhao-ol8u3-x86 108896]$ sh t
/home/opc/Install/latest/bin/gcc -O -fstrict-flex-arrays=3 t28.c
ok:  __builtin_object_size(p->array, 0) == 60
ok:  __builtin_object_size(p->array, 1) == 40
ok:  __builtin_object_size(p->array, 0) == 20
ok:  __builtin_object_size(p->array, 1) == 20
ok:  __builtin_object_size(p->array, 1) == 40
ok:  __builtin_object_size(p->array, 0) == -1
ok:  __builtin_object_size(p, 1) == -1
ok:  __builtin_object_size(p, 0) == -1
ok:  __builtin_object_size(q->array, 1) == 40
ok:  __builtin_object_size(q->array, 0) == -1
ok:  __builtin_object_size(q, 1) == -1
ok:  __builtin_object_size(q, 0) == -1
[opc@qinzhao-ol8u3-x86 108896]$ 

> 
> -Kees
> 
>> 
>> Qing
>> 
>> 
>> On Aug 3, 2023, at 1:19 PM, Siddhesh Poyarekar <siddh...@gotplt.org> wrote:
>>> 
>>> On 2023-08-03 12:43, Qing Zhao wrote:
>>>>> Surely we could emit that for __bdos(q->array, 0) though, couldn't we?
>>>> For __bdos(q->array, 0), we only have the access info for the sub-object 
>>>> q->array, we can surely decide the size of the sub-object q->array, but we 
>>>> still cannot
>>>> decide the whole object that is pointed by q (the same reason as above), 
>>>> right?
>>> 
>>> It's tricky, I mean we could assume p to be a valid object due to the 
>>> dereference and hence assume that q->foo is also valid and that there's at 
>>> least sizeof(*q) + q->foo * sizeof (q->array) bytes available.  The 
>>> question then is whether q could be pointing to an element of an array of 
>>> `struct annotated`.  Could we ever have a valid array of such structs that 
>>> have a flex array at the end?  Wouldn't it always be a single object?
>>> 
>>> In fact for all pointers to such structs with a flex array at the end, 
>>> could we always assume that it is a single object and never part of an 
>>> array, and hence return sizeof()?
>>> 
>>> Thanks,
>>> Sid
>> 
> 
> 
> -- 
> Kees Cook

Reply via email to