Incorrect use of pointer type casting (GC_CELL_OP)

Author: safoclCreated Jun 22, 2026Updated Jul 9, 2026
LabelsBugTriage

This macro casts the cell pointer to a pointer to char, performs arithmetic operations on it, and then casts it to a pointer to a struct gc_cell

c
#define GC_CELL_OP(arena, cell, op, arg) \
(
(struct gc_cell*)
(
  ((char*) (cell)) 
     \op(
              (arg) * (arena)->cell_size)
     )
)

However, this operation can only be performed if the object referenced by the pointer is a pointer-interconvertible with the struct gc_cell type. Moreover, if the pointer is not a pointer to an array of struct gc_cell elements, then arithmetic cannot be performed on a pointer to a char further than the object occupies in its object representation. It is permissible to obtain a pointer following the last byte of the object's object representation, but this pointer cannot be the beginning of the next object when used.

In other words:

c
alignas(int) unsigned char a[sizeof(int) * 10] = {};
int* p1 = (int*)(&a[sizeof(int) * 0]);
int* p2 = (int*)(&a[sizeof(int) * 1]);

*p1 = 5;
*p2 = 42;

char* pc1 = ((char*)p1) + sizeof(int);
int* past_the_end_1 = (int*)pc1;
*past_the_end_1 = 33; // invalid usage

ISO/IEC 9899:TC2 Committee Draft — May 6, 2005 WG14/N1124

6.2.6 Representations of types 6.2.6.1 General

  1. Values stored in non-bit-field objects of any other object type consist of n × CHAR_BIT bits, where n is the size of an object of that type, in bytes. The value may be copied into an object of type unsigned char [n] (e.g., by memcpy); the resulting set of bytes is called the object representation of the value.
  2. Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined.

6.3.2.3 Pointers

  1. [...] When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

6.5.6 Additive operators

  1. When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the _i-_th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i+n-th and _i−n-_th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object [...]. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

I believe the specified macro is irrelevant for use, since either it already contains a pointer to an array of type struct gc_cell elements, or it contains an array of unsigned char elements. But then, in the first case, casting to a pointer to a char and then performing move arithmetic outside the object representation is invalid (this is simply not required — you can simply move using regular pointer arithmetic on a type struct gc_cell).

c
struct gc_cell a[10];
struct gc_cell* p = &a[0];
*p = {};
struct gc_cell v = *p;

p = &a[1];
*p = {};
v = *(p - 1); // is valid
v = *(--p); // is valid
v = *p; // is valid; p points to a[0]
p++;
v = *p; // is valid; p points to a[1]

p = &a[0];
char* obj_start_byte = (char*)p;
v = *(struct gc_cell*)(obj_start_byte + sizeof(struct gc_cell)); // is invalid; points one past the last element of the array object (object representation)

Source: flipperdevices/flipperzero-firmware