382 lines
18 KiB
ReStructuredText
382 lines
18 KiB
ReStructuredText
|
.. 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));
|