¿Cuáles son las funciones de la biblioteca estándar que deben / deben evitarse?

He leído en Desbordamiento de stack que algunas funciones C son “obsoletas” o “deben evitarse”. ¿Pueden darme algunos ejemplos de este tipo de funciones y por qué?

¿Qué alternativas existen?

¿Podemos usarlos de forma segura, cualquier buena práctica?

Funciones obsoletas
Inseguro
Un ejemplo perfecto de dicha función es get () , porque no hay forma de decir qué tan grande es el búfer de destino. En consecuencia, cualquier progtwig que lea entrada utilizando gets () tiene una vulnerabilidad de desbordamiento de búfer . Por razones similares, uno debe usar strncpy () en lugar de strcpy () y strncat () en lugar de strcat () .

Sin embargo, algunos ejemplos más incluyen las funciones tmpfile () y mktemp () debido a posibles problemas de seguridad con la sobreescritura de archivos temporales y que son sustituidos por la función mkstemp (), que es más segura.

No reentrante
Otros ejemplos incluyen gethostbyaddr () y gethostbyname () que no son reentrantes (y, por lo tanto, no se garantiza que sean seguros para la ejecución de subprocesos) y han sido reemplazados por la reentrada getaddrinfo () y freeaddrinfo () .

Puede notar un patrón aquí … la falta de seguridad (posiblemente al no incluir suficiente información en la firma para posiblemente implementarla de forma segura) o la no reentrada son fonts comunes de desaprobación.

Anticuado, no portátil
Algunas otras funciones simplemente se vuelven obsoletas porque duplican la funcionalidad y no son tan portátiles como otras variantes. Por ejemplo, bzero () está en desuso en favor de memset () .

Seguridad y reentrada de subprocesos
En su publicación, preguntó sobre la seguridad y la reentrada de hilos. Hay una pequeña diferencia. Una función es reentrante si no usa ningún estado mutable compartido. Entonces, por ejemplo, si toda la información que necesita se transfiere a la función, y los almacenamientos intermedios necesarios también se pasan a la función (en lugar de ser compartidos por todas las llamadas a la función), entonces se vuelve a ingresar. Eso significa que diferentes subprocesos, mediante el uso de parámetros independientes, no se arriesgan a compartir estado accidentalmente. La reentrada es una garantía más sólida que la seguridad de hilos. Una función es segura para subprocesos si puede ser utilizada por múltiples subprocesos al mismo tiempo. Una función es segura para subprocesos si:

  • Es reentrante (es decir, no comparte ningún estado entre llamadas), o:
  • No es reentrante, pero usa la sincronización / locking según sea necesario para el estado compartido.

En general, en la especificación individual de UNIX y IEEE 1003.1 (es decir, “POSIX”), no se garantiza que ninguna función que no se garantiza que sea reentrante sea segura para la ejecución de subprocesos. Entonces, en otras palabras, solo las funciones que se garantiza que serán reentrantes se pueden usar de forma portátil en aplicaciones multiproceso (sin locking externo). Sin embargo, eso no significa que las implementaciones de estos estándares no puedan elegir hacer que una función no reentrante sea segura para el hilo. Por ejemplo, Linux frecuentemente agrega sincronización a funciones no reentrantes para agregar una garantía (más allá de la especificación Single UNIX) de threadsafety.

Cadenas (y memorias intermedias, en general)
También preguntó si hay alguna falla fundamental con cadenas / matrices. Algunos podrían argumentar que este es el caso, pero yo diría que no, que no existe un defecto fundamental en el lenguaje. C y C ++ requieren que pase la longitud / capacidad de una matriz por separado (no es una propiedad “.length” como en algunos otros idiomas). Esto no es un defecto, per se. Cualquier desarrollador de C y C ++ puede escribir el código correcto simplemente pasando la longitud como un parámetro donde sea necesario. El problema es que varias API que requieren esta información no pudieron especificarlo como parámetro. O asumió que se usaría alguna constante MAX_BUFFER_SIZE. Dichas API ahora han quedado en desuso y reemplazadas por API alternativas que permiten especificar los tamaños de matriz / buffer / cadena.

Scanf (en respuesta a su última pregunta)
Personalmente, utilizo la biblioteca iostreams de C ++ (std :: cin, std :: cout, los operadores << y >>, std :: getline, std :: istringstream, std :: ostringstream, etc.), así que no típicamente lidiar con eso. Sin embargo, si me viera obligado a usar C pura, personalmente usaría fgetc () o getchar () en combinación con strtol () , strtoul () , etc. y analizaría las cosas manualmente, ya que no soy un gran fan de varargs o cadenas de formato. Dicho esto, a mi leal saber y entender, no hay ningún problema con [f] scanf () , [f] printf () , etc. siempre que cree las cadenas de formato usted mismo, nunca pase cadenas de formato arbitrarias ni permita al usuario entrada que se utilizará como cadenas de formato, y utiliza las macros de formateo definidas en según corresponda. (Tenga en cuenta que snprintf () se debe usar en lugar de sprintf () , pero eso tiene que ver con no especificar el tamaño del búfer de destino y no el uso de cadenas de formato). También debo señalar que, en C ++, el formato boost :: proporciona un formato tipo printf sin varargs.

Una vez más las personas están repitiendo, como un mantra, la absurda afirmación de que la versión “n” de las funciones str son versiones seguras.

Si eso era para lo que estaban destinados, siempre anularían las cadenas.

Las versiones “n” de las funciones se escribieron para su uso con campos de longitud fija (como las entradas de directorio en los primeros sistemas de archivos) donde el terminador nul solo es necesario si la cadena no llena el campo. Esta es también la razón por la cual las funciones tienen efectos secundarios extraños que son inútiles ineficaces si solo se usan como reemplazos: tome strncpy () por ejemplo:

Si la matriz apuntada por s2 es una cadena que es más corta que n bytes, los bytes nulos se anexan a la copia en la matriz apuntada por s1, hasta que se escriben n bytes en total.

Como los búferes asignados para manejar nombres de archivos son típicamente 4kbytes, esto puede conducir a un deterioro masivo en el rendimiento.

Si desea versiones “supuestamente” seguras, obtenga -o escriba las suyas- rutinas strl (strlcpy, strlcat, etc.) que siempre terminan las cadenas y no tienen efectos secundarios. Sin embargo, tenga en cuenta que estos no son realmente seguros, ya que pueden truncar silenciosamente la cadena; rara vez es el mejor curso de acción en cualquier progtwig del mundo real. Hay ocasiones en que esto está bien, pero también hay muchas circunstancias en las que podría dar lugar a resultados catastróficos (por ejemplo, impresión de recetas médicas).

Varias respuestas aquí sugieren usar strncat() sobre strcat() ; Sugeriría que strncat() (y strncpy() ) también se deben evitar. Tiene problemas que dificultan el uso correcto y conducen a errores:

  • el parámetro de longitud a strncat() está relacionado con (pero no del todo exacto, vea el 3er punto) la cantidad máxima de caracteres que pueden copiarse al destino en lugar del tamaño del búfer de destino. Esto hace que strncat() más difícil de usar de lo que debería ser, especialmente si se concatenarán varios elementos al destino.
  • puede ser difícil determinar si el resultado fue truncado (lo que puede ser importante o no)
  • es fácil tener un error “uno por uno”. Como señala el estándar C99, “Por lo tanto, la cantidad máxima de caracteres que puede terminar en la matriz apuntada por s1 es strlen(s1)+n+1 ” para una llamada que se parece a strncat( s1, s2, n)

strncpy() también tiene un problema que puede provocar errores al tratar de usarlo de forma intuitiva: no garantiza que el destino caduque. Para asegurarte de que tienes que asegurarte de manejar específicamente ese caso de la esquina dejando caer un '\0' en la última ubicación del búfer (al menos en ciertas situaciones).

Sugiero usar algo como strlcat() y strlcpy() aunque sé que a algunas personas no les gustan esas funciones, creo que son mucho más fáciles de usar con seguridad que strncat() / strncpy() ).

Aquí hay un poco de lo que Todd Miller y Theo de Raadt tuvieron que decir acerca de los problemas con strncat() y strncpy() :

Existen varios problemas cuando strncpy() y strncat() se utilizan como versiones seguras de strcpy() y strcat() . Ambas funciones se ocupan de la terminación de NUL y el parámetro de longitud de formas diferentes y no intuitivas que confunden incluso a los progtwigdores experimentados. Tampoco proporcionan una forma fácil de detectar cuándo se produce el truncamiento. … De todos estos problemas, la confusión causada por los parámetros de longitud y la cuestión relacionada con la terminación NUL son los más importantes. Cuando auditamos el árbol de fonts de OpenBSD en busca de potenciales agujeros de seguridad, encontramos el uso indebido desenfrenado de strncpy() y strncat() . Si bien no todos estos resultaron en agujeros de seguridad explotables, dejaron en claro que las reglas para usar strncpy() y strncat() en operaciones de cadenas seguras son ampliamente malentendidas.

La auditoría de seguridad de OpenBSD descubrió que los errores con estas funciones eran “desenfrenados”. A diferencia de gets() , estas funciones se pueden usar de forma segura, pero en la práctica hay muchos problemas porque la interfaz es confusa, poco intuitiva y difícil de usar correctamente. Sé que Microsoft también ha hecho análisis (aunque no sé cuántos de sus datos pueden haber publicado), y como resultado han prohibido (o al menos desaconsejado enérgicamente, la ‘prohibición’ podría no ser absoluta) uso de strncat() y strncpy() (entre otras funciones).

Algunos enlaces con más información:

Algunas personas afirman que strcpy y strcat deben evitarse, a favor de strncpy y strncat . Esto es algo subjetivo, en mi opinión.

Definitivamente deberían evitarse al tratar con la entrada del usuario, sin duda aquí.

En el código “lejos” del usuario, cuando sabe que los búferes son lo suficientemente largos, strcpy y strcat pueden ser un poco más eficientes porque calcular el n para pasar a sus primos puede ser superfluo.

Evitar

  • strtok para progtwigs multiproceso ya que no es seguro para subprocesos.
  • gets como podría causar desbordamiento de búfer

Funciones de biblioteca estándar que nunca deben usarse:

setjmp.h

  • setjmp() . Junto con longjmp() , estas funciones son ampliamente reconocidas como increíblemente peligrosas de usar: conducen a la progtwigción de spaghetti, vienen con numerosas formas de comportamiento indefinido, pueden causar efectos secundarios no deseados en el entorno del progtwig, como afectar los valores almacenados en la stack. Referencias: MISRA-C: 2012 regla 21.4, CERT C MSC22-C .
  • longjmp() . Ver setjmp() .

stdio.h

  • gets() . La función se eliminó del lenguaje C (según C11), ya que no era seguro según el diseño. La función ya estaba marcada como obsoleta en C99. Use fgets() lugar. Referencias: ISO 9899: 2011 K.3.5.4.1, también ver nota 404).

stdlib.h

  • atoi() familia de funciones. No tienen un manejo de errores pero invocan un comportamiento indefinido cada vez que ocurren errores. Funciones completamente superfluas que pueden ser reemplazadas por la familia de funciones strtol() . Referencias: MISRA-C: 2012 regla 21.7.

string.h

  • strncat() . Tiene una interfaz incómoda que a menudo se usa mal. Es sobre todo una función superflua. También vea los comentarios para strncpy() .
  • strncpy() . La intención de esta función nunca fue una versión más segura de strcpy() . Su único propósito siempre fue manejar un antiguo formato de cadena en sistemas Unix, y que se incluyó en la biblioteca estándar es un error conocido. Esta función es peligrosa porque puede dejar la cadena sin terminación nula y se sabe que los progtwigdores suelen utilizarla incorrectamente. Referencias: ¿Por qué strlcpy y strlcat se consideran inseguros? .

Funciones de biblioteca estándar que deben usarse con precaución:

assert.h

  • assert() Viene con gastos generales y generalmente no debe usarse en el código de producción. Es mejor utilizar un controlador de error específico de la aplicación que muestre errores pero no cierre necesariamente todo el progtwig.

signal.h

  • signal() . Referencias: MISRA-C: 2012 regla 21.5, CERT C SIG32-C .

stdarg.h

  • va_arg() familia de funciones. La presencia de funciones de longitud variable en un progtwig C es casi siempre una indicación de un diseño de progtwig deficiente. Debe evitarse a menos que tenga requisitos muy específicos.

stdio.h
En general, esta biblioteca completa no se recomienda para el código de producción , ya que viene con numerosos casos de comportamiento poco definido y seguridad de tipo deficiente.

  • fflush() Perfectamente bien para usar en transmisiones de salida. Invoca comportamiento indefinido si se usa para flujos de entrada.
  • gets_s() . Versión segura de gets() incluida en la interfaz de comprobación de límites C11. Se prefiere usar fgets() cambio, según la recomendación estándar de C. Referencias: ISO 9899: 2011 K.3.5.4.1.
  • printf() familia de funciones. Funciones pesadas de recursos que vienen con muchos comportamientos indefinidos y seguridad de tipo pobre. sprintf() también tiene vulnerabilidades. Estas funciones deben evitarse en el código de producción. Referencias: MISRA-C: 2012 regla 21.6.
  • scanf() familia de funciones. Ver comentarios sobre printf() . Además, – scanf() es vulnerable a los desbordamientos del búfer si no se usa correctamente. Se prefiere usar fgets() cuando sea posible. Referencias: CERT C INT05-C , MISRA-C: 2012 regla 21.6.
  • tmpfile() familia de funciones. Viene con varios problemas de vulnerabilidad. Referencias: CERT C FIO21-C .

stdlib.h

  • familia de funciones malloc() Perfectamente bien para usar en sistemas alojados, aunque tenga en cuenta los problemas conocidos en C90 y, por lo tanto , no arroje el resultado . La familia de funciones malloc() nunca debe usarse en aplicaciones independientes. Referencias: MISRA-C: 2012 regla 21.3.

    También tenga en cuenta que realloc() es peligroso en caso de que sobrescriba el puntero antiguo con el resultado de realloc() . En caso de que la función falle, usted crea una fuga.

  • system() . Viene con muchos gastos generales y, aunque es portátil, a menudo es mejor usar funciones de API específicas del sistema. Viene con varios comportamientos mal definidos. Referencias: CERT C ENV33-C .

string.h

  • strcat() Ver comentarios para strcpy() .
  • strcpy() . Perfectamente bien de usar, a menos que el tamaño de los datos a copiar sea desconocido o mayor que el búfer de destino. Si no se realiza ninguna comprobación del tamaño de los datos entrantes, puede haber sobrepasamientos del búfer. Lo cual no es culpa de strcpy() sí mismo, sino de la aplicación de llamada: que strcpy() no es seguro es sobre todo un mito creado por Microsoft .
  • strtok() . Modifica la cadena de la persona que llama y utiliza variables de estado internas, lo que podría hacer que no sea seguro en un entorno de subprocesos múltiples.

Probablemente valga la pena agregar nuevamente que strncpy() no es el reemplazo de uso general para strcpy() que su nombre podría sugerir. Está diseñado para campos de longitud fija que no necesitan un terminador nul (originalmente se diseñó para usarse con entradas de directorio UNIX, pero puede ser útil para cosas como campos de clave de encriptación).

Sin embargo, es fácil usar strncat() como reemplazo de strcpy() :

 if (dest_size > 0) { dest[0] = '\0'; strncat(dest, source, dest_size - 1); } 

(La prueba if se puede descartar obviamente en el caso común, donde se sabe que dest_size definitivamente no es cero).

También consulte la lista de Microsoft de API prohibidas . Estas son API (incluidas muchas ya enumeradas aquí) que están prohibidas en el código de Microsoft porque a menudo se usan incorrectamente y generan problemas de seguridad.

Puede que no estés de acuerdo con todos ellos, pero vale la pena considerarlos. Agregan una API a la lista cuando su uso incorrecto ha dado lugar a una serie de errores de seguridad.

Casi cualquier función que trate con cadenas terminadas en NUL es potencialmente insegura. Si está recibiendo datos del mundo exterior y manipulándolos a través de las funciones str * (), entonces se prepara para una catástrofe

No te olvides de sprintf, es la causa de muchos problemas. Esto es cierto porque la alternativa, snprintf, a veces tiene implementaciones diferentes que pueden hacer que el código sea poco práctico.

  1. linux: http://linux.die.net/man/3/snprintf

  2. Windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

En el caso 1 (Linux), el valor de retorno es la cantidad de datos necesarios para almacenar el búfer completo (si es menor que el tamaño del búfer dado, la salida se truncó)

En el caso 2 (Windows), el valor de retorno es un número negativo en caso de que la salida se trunque.

En general, debe evitar las funciones que no son:

  1. buffer overflow safe (muchas funciones ya se mencionan aquí)

  2. hilo seguro / no reentrante (strtok por ejemplo)

En el manual de cada función, debe buscar palabras clave como: seguro, sincronización, asincrónico, hilo, búfer, errores

Es muy difícil usar scanf forma segura. El buen uso de scanf puede evitar desbordamientos de búfer, pero aún es vulnerable al comportamiento indefinido cuando lee números que no se ajustan al tipo solicitado. En la mayoría de los casos, los fgets seguidos de autodiagnóstico (utilizando sscanf , strchr , etc.) es una mejor opción.

Pero yo no diría “evitar scanf todo el tiempo”. scanf tiene sus usos. Como ejemplo, supongamos que quiere leer la entrada del usuario en una matriz char de 10 bytes de longitud. Desea eliminar la nueva línea final, si corresponde. Si el usuario ingresa más de 9 caracteres antes de una nueva línea, quiere almacenar los primeros 9 caracteres en el búfer y descartar todo hasta la próxima línea nueva. Tu puedes hacer:

 char buf[10]; scanf("%9[^\n]%*[^\n]", buf)); getchar(); 

Una vez que te acostumbras a este idioma, es más corto y, de alguna manera, más limpio que:

 char buf[10]; if (fgets(buf, sizeof buf, stdin) != NULL) { char *nl; if ((nl = strrchr(buf, '\n')) == NULL) { int c; while ((c = getchar()) != EOF && c != '\n') { ; } } else { *nl = 0; } } 

En todos los escenarios de copia / movimiento de cadena – strcat (), strncat (), strcpy (), strncpy (), etc. – las cosas van mucho mejor ( más seguras ) si se aplica un par de heurísticas simples:

1. Siempre NUL: llene su (s) buffer (es) antes de agregar datos.
2. Declare búferes de caracteres como [TAMAÑO + 1], con una macro constante.

Por ejemplo, dado:

#define BUFSIZE 10
Char Buffer [BUFSIZE + 1] = {0x00}; / * El comstackdor NUL-llena el rest * /

podemos usar código como:

memset (Buffer, 0x00, sizeof (Buffer));
strncpy (Buffer, BUFSIZE, “12345678901234567890”);

relativamente seguro El memset () debería aparecer antes de strncpy (), aunque inicializamos Buffer en tiempo de comstackción, porque no sabemos qué basura ha insertado otro código antes de llamar a nuestra función. El strncpy () truncará los datos copiados a “1234567890”, y no lo terminará en NUL. Sin embargo, dado que ya hemos llenado con NUL todo el buffer-sizeof (Buffer), en lugar de BUFSIZE, se garantiza que será un NUL de terminación final “fuera del scope” de todos modos, siempre y cuando restrinjamos nuestras escrituras con el BUFSIZE constante, en lugar de sizeof (Buffer).

Buffer y BUFSIZE también funcionan bien para snprintf ():

memset (Buffer, 0x00, sizeof (Buffer));

if (snprintf (Buffer, BUFIZE, “Datos:% s”, “Demasiados datos”)> BUFSIZE) {
/ * Hacer algo de manejo de errores /
} / Si usa MFC, necesita if (… <0), en su lugar * /

Aunque snprintf () escribe específicamente solo caracteres BUFIZE-1, seguido de NUL, esto funciona de manera segura. Así que “desperdiciamos” un byte NUL extraño al final de Buffer … prevenimos tanto desbordamiento de búfer como condiciones de cadena sin terminar, por un costo de memoria bastante pequeño.

Mi llamada en strcat () y strncat () es más dura: no los use. Es difícil usar strcat () de forma segura, y la API para strncat () es tan intuitiva que el esfuerzo necesario para usarla niega adecuadamente cualquier beneficio. Propongo el siguiente drop-in:

#define strncat (target, source, bufsize) snprintf (destino, fuente, “% s% s”, destino, fuente)

Es tentador crear un drop-in strcat (), pero no es una buena idea:

#define strcat (destino, fuente), snprintf (destino, tamaño de (destino), “% s% s”, destino, origen)

porque el objective puede ser un puntero (por lo tanto, sizeof () no devuelve la información que necesitamos). No tengo una buena solución “universal” para instancias de strcat () en tu código.

Un problema que encuentro frecuentemente de los progtwigdores “strFunc () – aware” es un bash de proteger contra desbordamientos de buffer utilizando strlen (). Esto está bien si se garantiza que los contenidos serán terminados en NUL. De lo contrario, strlen () en sí mismo puede causar un error de desbordamiento del búfer (que generalmente lleva a una violación de segmentación u otra situación de volcado de núcleo), antes de llegar al código “problemático” que está tratando de proteger.

atoi no es seguro para subprocesos. Yo uso strtol en su lugar, por recomendación de la página man.

    Intereting Posts