Warning
Si tiene alguna duda sobre la exactitud del contenido de esta traducción, la única referencia válida es la documentación oficial en inglés.
- Original:
- Translator:
Sergio Gonzalez <sergio.collado@gmail.com>
Interfaces obsoletos, Características del lenguaje, Atributos y Convenciones¶
En un mundo perfecto, sería posible convertir todas las instancias de alguna API obsoleta en una nueva API y quitar la API anterior en un único ciclo de desarrollo. Desafortunadamente, debido al tamaño del kernel, la jerarquía de mantenimiento, y el tiempo, no siempre es posible hacer estos cambios de una única vez. Esto significa que las nuevas instancias han de ir creándose en el kernel, mientras que las antiguas se quitan, haciendo que la cantidad de trabajo para limpiar las APIs crezca. Para informar a los desarrolladores sobre qué ha sido declarado obsoleto y por qué, ha sido creada esta lista como un lugar donde indicar cuando los usos obsoletos son propuestos para incluir en el kernel.
__deprecated¶
Mientras que este atributo señala visualmente que un interface ha sido declarado obsoleto, este no produce más avisos durante las compilaciones porque uno de los objetivos del kernel es que compile sin avisos, y nadie ha hecho nada para quitar estos interfaces obsoletos. Mientras que usar __deprecated es sencillo para anotar una API obsoleta en un archivo de cabecera, no es la solución completa. Dichos interfaces deben o bien ser quitados por completo, o añadidos a este archivo para desanimar a otros a usarla en el futuro.
BUG() y BUG_ON()¶
Use WARN() y WARN_ON() en su lugar, y gestione las condiciones de error "imposibles" tan elegantemente como se pueda. Mientras que la familia de funciones BUG() fueron originalmente diseñadas para actuar como una "situación imposible", confirmar y disponer de un hilo del kernel de forma "segura", estas funciones han resultado ser demasiado arriesgadas. (e.g. "¿en qué orden se necesitan liberar los locks? ¿Se han restaurado sus estados?). La popular función BUG() desestabilizará el sistema o lo romperá totalmente, lo cual hace imposible depurarlo o incluso generar reportes de crash. Linus tiene una opinión muy fuerte y sentimientos sobre esto.
Nótese que la familia de funciones WARN() únicamente debería ser usada
en situaciones que se "esperan no sean alcanzables". Si se quiere
avisar sobre situaciones "alcanzables pero no deseadas", úsese la familia
de funciones pr_warn()
. Los responsables del sistema pueden haber definido
panic_on_warn sysctl para asegurarse que sus sistemas no continúan
ejecutándose en presencia del condiciones "no alcanzables". (Por ejemplo,
véase commits como este.)
Operaciones aritméticas en los argumentos de reserva de memoria¶
Los cálculos dinámicos de tamaño (especialmente multiplicaciones) no deberían realizarse en los argumentos de reserva de memoria (o similares) debido al riesgo de desbordamiento. Esto puede llevar a valores rotando y que se realicen reservas de memoria menores que las que se esperaban. El uso de esas reservas puede llevar a desbordamientos en el 'heap' de memoria y otros funcionamientos incorrectos. (Una excepción a esto son los valores literales donde el compilador si puede avisar si estos puede desbordarse. De todos modos, el método recomendado en estos caso es reescribir el código como se sugiere a continuación para evitar las operaciones aritméticas en la reserva de memoria.)
Por ejemplo, no utilice count * size` como argumento, como en:
foo = kmalloc(count * size, GFP_KERNEL);
En vez de eso, utilice la reserva con dos argumentos:
foo = kmalloc_array(count, size, GFP_KERNEL);
Específicamente, kmalloc()
puede ser sustituido con kmalloc_array()
,
kzalloc()
puede ser sustituido con kcalloc()
.
Si no existen funciones con dos argumentos, utilice las funciones que se saturan, en caso de desbordamiento:
bar = vmalloc(array_size(count, size));
Otro caso común a evitar es calcular el tamaño de una estructura com la suma de otras estructuras, como en:
header = kzalloc(sizeof(*header) + count * sizeof(*header->item),
GFP_KERNEL);
En vez de eso emplee:
header = kzalloc(struct_size(header, item, count), GFP_KERNEL);
Note
Si se usa struct_size()
en una estructura que contiene un elemento
de longitud cero o un array de un único elemento como un array miembro,
por favor reescribir ese uso y cambiar a un miembro array flexible
Para otros cálculos, por favor use las funciones de ayuda: size_mul()
,
size_add()
, and size_sub()
. Por ejemplo, en el caso de:
foo = krealloc(current_size + chunk_size * (count - 3), GFP_KERNEL);
Re-escríbase, como:
foo = krealloc(size_add(current_size,
size_mul(chunk_size,
size_sub(count, 3))), GFP_KERNEL);
Para más detalles, mire también array3_size()
y flex_array_size()
,
como también la familia de funciones relacionadas check_mul_overflow()
,
check_add_overflow()
, check_sub_overflow()
, y check_shl_overflow()
.
simple_strtol(), simple_strtoll(), simple_strtoul(), simple_strtoull()¶
Las funciones: simple_strtol()
, simple_strtoll()
, simple_strtoul()
, y
simple_strtoull()
explícitamente ignoran los desbordamientos, lo que puede
llevar a resultados inesperados por las funciones que las llaman. Las
funciones respectivas kstrtol()
, kstrtoll()
, kstrtoul()
, y kstrtoull()
tienden a ser reemplazos correctos, aunque nótese que necesitarán que la
cadena de caracteres termine en NUL o en el carácter de línea nueva.
strcpy()¶
strcpy()
no realiza verificaciones de los límites del buffer de destino.
Esto puede resultar en desbordamientos lineals más allá del fin del buffer,
causando todo tipo de errores. Mientras CONFIG_FORTIFY_SOURCE=y otras
varias opciones de compilación reducen el riesgo de usar esta función, no
hay ninguna buena razón para añadir nuevos usos de esta. El remplazo seguro
es la función strscpy()
, aunque se ha de tener cuidado con cualquier caso
en el el valor retornado por strcpy()
sea usado, ya que strscpy()
no
devuelve un puntero a el destino, sino el número de caracteres no nulos
compilados (o el valor negativo de errno cuando se trunca la cadena de
caracteres).
strncpy() en cadenas de caracteres terminadas en NUL¶
El uso de strncpy()
no garantiza que el buffer de destino esté terminado en
NUL. Esto puede causar varios errores de desbordamiento en lectura y otros
tipos de funcionamiento erróneo debido a que falta la terminación en NUL.
Esta función también termina la cadena de caracteres en NUL en el buffer de
destino si la cadena de origen es más corta que el buffer de destino, lo
cual puede ser una penalización innecesaria para funciones usen esta
función con cadenas de caracteres que sí están terminadas en NUL.
Cuando se necesita que la cadena de destino sea terminada en NUL,
el mejor reemplazo es usar la función strscpy()
, aunque se ha de tener
cuidado en los casos en los que el valor de strncpy()
fuera usado, ya que
strscpy()
no devuelve un puntero al destino, sino el número de
caracteres no nulos copiados (o el valor negativo de errno cuando se trunca
la cadena de caracteres). Cualquier caso restante que necesitase todavía
ser terminado en el caracter nulo, debería usar strscpy_pad()
.
Si una función usa cadenas de caracteres que no necesitan terminar en NUL,
debería usarse strtomem()
, y el destino debería señalarse con el atributo
__nonstring
para evitar avisos futuros en el compilador. Para casos que todavía
necesitan cadenas de caracteres que se rellenen al final con el
caracter NUL, usar strtomem_pad()
.
strlcpy()¶
strlcpy()
primero lee por completo el buffer de origen (ya que el valor
devuelto intenta ser el mismo que el de strlen()
). Esta lectura puede
sobrepasar el límite de tamaño del destino. Esto ineficiente y puede causar
desbordamientos de lectura si la cadena de origen no está terminada en el
carácter NUL. El reemplazo seguro de esta función es strscpy()
, pero se ha
de tener cuidado que en los casos en lso que se usase el valor devuelto de
strlcpy()
, ya que strscpy()
devolverá valores negativos de erno cuando se
produzcan truncados.
Especificación de formato %p¶
Tradicionalmente,el uso de "%p" en el formato de cadenas de caracteres resultaría en exponer esas direcciones en dmesg, proc, sysfs, etc. En vez de dejar que sean una vulnerabilidad, todos los "%p" que se usan en el kernel se imprimen como un hash, haciéndolos efectivamente inutilizables para usarlos como direcciones de memoria. Nuevos usos de "%p" no deberían ser añadidos al kernel. Para textos de direcciones, usar "%pS" es mejor, ya que resulta en el nombre del símbolo. Para prácticamente el resto de casos, mejor no usar "%p" en absoluto.
Parafraseando las actuales direcciones de Linus:
Si el valor "hasheado" "%p" no tienen ninguna finalidad, preguntarse si el puntero es realmente importante. ¿Quizás se podría quitar totalmente?
Si realmente se piensa que el valor del puntero es importante, ¿porqué algún estado del sistema o nivel de privilegio de usuario es considerado "especial"? Si piensa que puede justificarse (en comentarios y mensajes del commit), de forma suficiente como para pasar el escrutinio de Linux, quizás pueda usar el "%p", a la vez que se asegura que tiene los permisos correspondientes.
Si está depurando algo donde el "%p" hasheado está causando problemas, se puede arrancar temporalmente con la opción de depuración "no_hash_pointers".
Arrays de longitud variable (VLAs)¶
Usando VLA en la pila (stack) produce un código mucho peor que los arrays de tamaño estático. Mientras que estos errores no triviales de rendimiento son razón suficiente para no usar VLAs, esto además son un riesgo de seguridad. El crecimiento dinámico del array en la pila, puede exceder la memoria restante en el segmento de la pila. Esto podría llevara a un fallo, posible sobre-escritura de contenido al final de la pila (cuando se construye sin CONFIG_THREAD_INFO_IN_TASK=y), o sobre-escritura de la memoria adyacente a la pila (cuando se construye sin CONFIG_VMAP_STACK=y).
Switch case fall-through implícito¶
El lenguaje C permite a las sentencias 'switch' saltar de un caso al siguiente caso cuando la sentencia de ruptura "break" no aparece al final del caso. Esto, introduce ambigüedad en el código, ya que no siempre está claro si el 'break' que falta es intencionado o un olvido. Por ejemplo, no es obvio solamente mirando al código si STATE_ONE está escrito para intencionadamente saltar en STATE_TWO:
switch (value) {
case STATE_ONE:
do_something();
case STATE_TWO:
do_other();
break;
default:
WARN("unknown state");
}
Ya que ha habido una larga lista de defectos debidos a declaraciones de "break" que faltan, no se permiten 'fall-through' implícitos. Para identificar 'fall-through' intencionados, se ha adoptado la pseudo-palabra-clave macro "falltrhrough", que expande las extensiones de gcc __attribute__((__fallthrough__)). (Cuando la sintaxis de C17/c18 [[fallthrough]] sea más comúnmente soportadas por los compiladores de C, analizadores estáticos, e IDEs, se puede cambiar a usar esa sintaxis para esa pseudo-palabra-clave.
Todos los bloques switch/case deben acabar en uno de:
break;
fallthrough;
continue;
goto <label>;
return [expression];
Arrays de longitud cero y un elemento¶
Hay una necesidad habitual en el kernel de proveer una forma para declarar un grupo de elementos consecutivos de tamaño dinámico en una estructura. El código del kernel debería usar siempre "miembros array flexible" en estos casos. El estilo anterior de arrays de un elemento o de longitud cero, no deben usarse más.
En el código C más antiguo, los elementos finales de tamaño dinámico se obtenían especificando un array de un elemento al final de una estructura:
struct something {
size_t count;
struct foo items[1];
};
En código C más antiguo, elementos seguidos de tamaño dinámico eran creados especificando una array de un único elemento al final de una estructura:
struct something {
size_t count;
struct foo items[1];
};
Esto llevó a resultados incorrectos en los cálculos de tamaño mediante sizeof() (el cual hubiera necesitado eliminar el tamaño del último elemento para tener un tamaño correcto de la "cabecera"). Una extensión de GNU C se empezó a usar para permitir los arrays de longitud cero, para evitar estos tipos de problemas de tamaño:
struct something {
size_t count;
struct foo items[0];
};
Pero esto llevó a otros problemas, y no solucionó algunos otros problemas compartidos por ambos estilos, como no ser capaz de detectar cuando ese array accidentalmente _no_ es usado al final de la estructura (lo que podía pasar directamente, o cuando dicha estructura era usada en uniones, estructuras de estructuras, etc).
C99 introdujo "los arrays miembros flexibles", los cuales carecen de un tamaño numérico en su declaración del array:
struct something {
size_t count;
struct foo items[];
};
Esta es la forma en la que el kernel espera que se declaren los elementos de tamaño dinámico concatenados. Esto permite al compilador generar errores, cuando el array flexible no es declarado en el último lugar de la estructura, lo que ayuda a prevenir errores en él código del tipo comportamiento indefinido. Esto también permite al compilador analizar correctamente los tamaños de los arrays (via sizeof(), CONFIG_FORTIFY_SOURCE, y CONFIG_UBSAN_BOUNDS). Por ejemplo, si no hay un mecanismo que avise que el siguiente uso de sizeof() en un array de longitud cero, siempre resulta en cero:
struct something {
size_t count;
struct foo items[0];
};
struct something *instance;
instance = kmalloc(struct_size(instance, items, count), GFP_KERNEL);
instance->count = count;
size = sizeof(instance->items) * instance->count;
memcpy(instance->items, source, size);
En la última línea del código anterior, zero
vale cero
, cuando uno
podría esperar que representa el tamaño total en bytes de la memoria dinámica
reservada para el array consecutivo items
. Aquí hay un par de ejemplos
más sobre este tema: link 1,
link 2.
Sin embargo, los array de miembros flexibles tienen un type incompleto, y
no se ha de aplicar el operador sizeof()<https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html>`_,
así cualquier mal uso de dichos operadores será detectado inmediatamente en
el momento de compilación.
Con respecto a los arrays de un único elemento, se ha de ser consciente de que dichos arrays ocupan al menos tanto espacio como un único objeto del tipo https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html>`_, de ahí que estos contribuyan al tamaño de la estructura que los contiene. Esto es proclive a errores cada vez que se quiere calcular el tamaño total de la memoria dinámica para reservar una estructura que contenga un array de este tipo como su miembro:
struct something {
size_t count;
struct foo items[1];
};
struct something *instance;
instance = kmalloc(struct_size(instance, items, count - 1), GFP_KERNEL);
instance->count = count;
size = sizeof(instance->items) * instance->count;
memcpy(instance->items, source, size);
En el ejemplo anterior, hemos de recordar calcular count - 1
, cuando se
usa la función de ayuda struct_size()
, de otro modo estaríamos
--desintencionadamente--reservando memoria para un items
de más. La
forma más clara y menos proclive a errores es implementar esto mediante el
uso de array miembro flexible, junto con las funciones de ayuda:
struct_size()
y flex_array_size()
:
struct something {
size_t count;
struct foo items[];
};
struct something *instance;
instance = kmalloc(struct_size(instance, items, count), GFP_KERNEL);
instance->count = count;
memcpy(instance->items, source, flex_array_size(instance, items, instance->count));