Las sobrecargas de búfer son la causa de muchos problemas de seguridad y son una preocupación constante para los programadores. El lenguaje C es particularmente vulnerable a estas sobrecargas. La aparición de sanitizadores ha mitigado algunos problemas de seguridad al insertar automáticamente comprobaciones de límites, pero no pueden hacerlo en todas las situaciones, especialmente con miembros de matrices flexibles, cuyo tamaño solo se conoce en tiempo de ejecución.
El tamaño de un miembro de matriz flexible es típicamente opaco para el compilador. El atributo alloc_size
en malloc()
puede utilizarse para la comprobación de límites de los miembros de matrices flexibles dentro de la misma función que la asignación. Sin embargo, la información del atributo no se lleva con el objeto asignado, lo que hace imposible realizar verificaciones de límites en otros lugares.
Para mitigar este inconveniente, Clang y GCC están introduciendo el atributo counted_by
para los miembros de matrices flexibles.
El número de elementos asignados para un miembro de matriz flexible se almacena frecuentemente en otro campo dentro de la misma estructura. Cuando se aplica al miembro de la matriz flexible, el atributo counted_by
es usado por el sanitizador—habilitado por -fsanitize=array-bounds
—haciendo referencia explícita al campo que almacena el número de elementos. El atributo crea una relación implícita entre el miembro de la matriz flexible y el campo de conteo, lo que permite al sanitizador de límites de matriz verificar las operaciones de la matriz flexible.
Existen algunas reglas a seguir al usar esta característica. Para la siguiente estructura:
struct foo {
/* ... */
size_t count; /* Number of elements in array */
int array[] __attribute__((counted_by(count)));
};
- El campo
count
debe estar dentro de la misma estructura no anónima que el miembro de la matriz flexible. - El campo
count
debe ser establecido antes de cualquier acceso a la matriz. - El campo
array
debe tener al menos el número de elementoscount
disponibles en todo momento. - El campo
count
puede cambiar, pero nunca debe ser mayor que el número de elementos originalmente asignados.
Un ejemplo de asignación de la estructura anterior sería:
struct foo *foo_alloc(size_t count) {
struct foo *ptr = NULL;
size_t size = MAX(sizeof(struct foo),
offsetof(struct foo, array[0]) +
count * sizeof(p->array[0]));
ptr = calloc(1, size);
ptr->count = count;
return ptr;
}
La fortificación (habilitada por el macro _FORTIFY_SOURCE
) es un proyecto en curso para hacer el kernel de Linux más seguro. Su enfoque principal es prevenir sobrecargas de búfer en operaciones de memoria y cadenas.
La fortificación utiliza los built-in __builtin_object_size()
y __builtin_dynamic_object_size()
para intentar determinar si la entrada pasada a una función es válida (es decir, "segura"). Una llamada a __builtin_dynamic_object_size()
generalmente no puede tener en cuenta el tamaño de un miembro de matriz flexible. Pero con el atributo counted_by
, es posible calcular el tamaño y mejorar la seguridad.
El atributo counted_by
ya se utiliza en el kernel de Linux y será esencial para detectar problemas como desbordamientos de enteros, que conducen a sobrecargas de búfer en el heap. Queremos expandir su uso a más miembros de matrices flexibles y hacer cumplir su uso en el futuro.
En conclusión, el atributo counted_by
ayuda a abordar un obstáculo de fortificación de larga data donde los límites de memoria de un miembro de matriz flexible no podían ser determinados por el compilador, haciendo que Linux y otras aplicaciones robustas sean menos explotables.
Por Bill Wendling, Ingeniero de Software del Personal
vía: Google Blog Open Source