blob: d52120e0d75354d0d32c33d631f9f364eba32f82 [file] [log] [blame]
.. include:: ../disclaimer-sp.rst
:Original: :ref:`Documentation/process/deprecated.rst <deprecated>`
:Translator: Sergio Gonzalez <sergio.collado@gmail.com>
.. _sp_deprecated:
============================================================================
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
<https://git.kernel.org/linus/771c035372a036f83353eef46dbb829780330234>`_
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
<https://lore.kernel.org/lkml/CA+55aFy6jNLsywVYdGp83AMrXBo_P-pkjkphPGrO=82SPKCpLQ@mail.gmail.com/>`_
y sentimientos `sobre esto
<https://lore.kernel.org/lkml/CAHk-=whDHsbK3HTOpTF=ue_o04onRwTEaK_ZoJp_fjbqq4+=Jw@mail.gmail.com/>`_.
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
<https://git.kernel.org/linus/d4689846881d160a4d12a514e991a740bcb5d65a>`_.)
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
<#zero-length-and-one-element-arrays>`_
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
<https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html>`_
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 <https://lore.kernel.org/lkml/CA+55aFwQEd_d40g4mUCSsVRZzrFPUJt74vc6PPpb675hYNXcKw@mail.gmail.com/>`_:
- 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
<https://git.kernel.org/linus/5ead723a20e0447bc7db33dc3070b420e5f80aa6>`_".
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
<https://git.kernel.org/linus/02361bc77888>`_ 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 <https://cwe.mitre.org/data/definitions/484.html>`_, 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__))
<https://gcc.gnu.org/onlinedocs/gcc/Statement-Attributes.html>`_.
(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" <https://en.wikipedia.org/wiki/Flexible_array_member>`_
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
<https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html>`_ 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 <https://git.kernel.org/linus/76497732932f15e7323dc805e8ea8dc11bb587cf>`_.
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
<https://git.kernel.org/linus/f2cd32a443da694ac4e28fbf4ac6f9d5cc63a539>`_,
`link 2
<https://git.kernel.org/linus/ab91c2a89f86be2898cee208d492816ec238b2cf>`_.
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));