He leído en Stack Overflow que algunas funciones de C son "obsoletas" o "deberían evitarse". ¿Puede darme algunos ejemplos de este tipo de función y el motivo?
¿Qué alternativas existen a esas funciones?
¿Podemos utilizarlos de forma segura? ¿Alguna buena práctica?
c
standard-library
obsolete
Andrei Ciobanu
fuente
fuente
strncpy()
como un reemplazo general destrcpy()
, y no lo usaríastrncat()
nunca porque tiene la interfaz más intuitiva imaginable: ¿sabe USTED lo que especifica el parámetro de longitud?Respuestas:
Funciones obsoletas
inseguras
Un ejemplo perfecto de una función de este tipo es gets () , porque no hay forma de saber qué tan grande es el búfer de destino. En consecuencia, cualquier programa que lea la entrada usando gets () tiene una vulnerabilidad de desbordamiento de búfer . Por razones similares, se debe usar strncpy () en lugar de strcpy () y strncat () en lugar de strcat () .
Sin embargo, algunos ejemplos más incluyen la función tmpfile () y mktemp () debido a posibles problemas de seguridad con la sobrescritura de archivos temporales y que son reemplazados por la función mkstemp () 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 subprocesos) y han sido reemplazados por los reentrantes getaddrinfo () y freeaddrinfo () .
Es posible que esté notando 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 fuentes comunes de desaprobación.
Desactualizado, 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 reentrada de subprocesos. 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 pasa a la función, y los búferes necesarios también se pasan a la función (en lugar de compartirlos con todas las llamadas a la función), entonces es reentrante. Eso significa que los diferentes subprocesos, mediante el uso de parámetros independientes, no corren el riesgo de compartir accidentalmente el estado. La reentrada es una garantía más sólida que la seguridad del hilo. Una función es segura para subprocesos si puede ser utilizada por varios subprocesos al mismo tiempo. Una función es segura para subprocesos si:
En general, en la Especificación Única de UNIX e IEEE 1003.1 (es decir, "POSIX"), no se garantiza que cualquier función que no esté garantizada para ser reentrante sea segura para subprocesos. Por lo tanto, en otras palabras, solo las funciones cuya reentrada está garantizada se pueden utilizar de forma portátil en aplicaciones multiproceso (sin bloqueo externo). Sin embargo, eso no significa que las implementaciones de estos estándares no puedan optar por hacer que una función no reentrante sea segura para subprocesos. Por ejemplo, Linux frecuentemente agrega sincronización a funciones no reentrantes para agregar una garantía (más allá de la Especificación Única de UNIX) de seguridad de subprocesos.
Cadenas (y búferes de memoria, en general)
También preguntó si hay algún defecto fundamental con cadenas / matrices. Algunos podrían argumentar que este es el caso, pero yo diría que no, no hay 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 otros lenguajes). Esto no es un defecto per se. Cualquier desarrollador de C y C ++ puede escribir código correcto simplemente pasando la longitud como parámetro cuando sea necesario. El problema es que varias API que requerían esta información no la especificaron como parámetro. O asumió que se usaría alguna constante MAX_BUFFER_SIZE. Dichas API ahora se han desaprobado y reemplazado por API alternativas que permiten especificar los tamaños de matriz / búfer / cadena.
Scanf (en respuesta a su última pregunta)
Personalmente, uso la biblioteca iostreams de C ++ (std :: cin, std :: cout, los operadores << y >>, std :: getline, std :: istringstream, std :: ostringstream , etc.), por lo que normalmente no trato con eso. Sin embargo, si me viera obligado a usar C puro, 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 que la entrada del usuario se utilice como cadenas de formato, y utilice las macros de formato definidas en <inttypes.h> cuando corresponda. (Tenga en cuenta que snprintf () debe usarse en lugar de sprintf () , pero eso tiene que ver con no especificar el tamaño del búfer de destino y no con el uso de cadenas de formato). También debo señalar que, en C ++, boost :: format proporciona un formato similar a printf sin varargs.
fuente
strncpy
generalmente también debe evitarse. No hace lo que la mayoría de los programadores supone que hace. No garantiza la terminación (lo que provoca desbordamientos del búfer) y rellena cadenas más cortas (posiblemente degradando el rendimiento en algunos casos).strncpy()
ni lo peor,strncat()
es un reemplazo sensato para las variantes sin n.Una vez más la gente repite, como un mantra, la ridícula afirmación de que la versión "n" de las funciones str son versiones seguras.
Si eso era para lo que estaban destinados, siempre terminarían en nulo las cadenas.
Las versiones "n" de las funciones se escribieron para su uso con campos de longitud fija (como entradas de directorio en los primeros sistemas de archivos) donde el terminador nulo solo es necesario si la cadena no llena el campo. Esta es también la razón por la que las funciones tienen efectos secundarios extraños que son inútilmente ineficientes si solo se usan como reemplazos; tome strncpy () por ejemplo:
Como los búferes asignados para manejar nombres de archivos suelen ser de 4 kbytes, esto puede conducir a un deterioro masivo del rendimiento.
Si desea versiones "supuestamente" seguras, obtenga, o escriba las suyas propias, rutinas strl (strlcpy, strlcat, etc.) que siempre terminan en nul 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; este rara vez es el mejor curso de acción en cualquier programa del mundo real. Hay ocasiones en las que esto está bien, pero también hay muchas circunstancias en las que podría dar lugar a resultados catastróficos (por ejemplo, imprimir recetas médicas).
fuente
strncpy()
, pero se equivocastrncat()
.strncat()
no fue diseñado para su uso con campos de longitud fija; en realidad, fue diseñado parastrcat()
limitar la cantidad de caracteres concatenados. Es bastante fácil usar esto como "segurostrcat()
" al hacer un seguimiento del espacio que queda en el búfer cuando se realizan múltiples concatenaciones, y aún más fácil usarlo como un "segurostrcpy()
" (estableciendo el primer carácter del búfer de destino en'\0'
antes llamándolo).strncat()
siempre termina la cadena de destino y no escribe'\0'
s adicionales .strncat()
no siempre termina en nulo el destino y que fue diseñado para usarse con campos de longitud fija, los cuales son incorrectos.strncat()
funcionará correctamente independientemente de la longitud de la cadena de origen, mientrasstrcat()
que no lo hará. El problemastrlcat()
aquí es que no es una función C estándar.Varias respuestas aquí sugieren usar
strncat()
overstrcat()
; Sugeriría questrncat()
(ystrncpy()
) también deberían evitarse. Tiene problemas que dificultan su uso correcto y dan lugar a errores:strncat()
está relacionado con (pero no exactamente, consulte el tercer punto) el número máximo de caracteres que se pueden copiar en el destino en lugar del tamaño del búfer de destino. Esto hacestrncat()
que su uso sea más difícil de lo que debería ser, especialmente si se concatenarán varios elementos al destino.s1
esstrlen(s1)+n+1
" para una llamada que parecestrncat( s1, s2, n)
strncpy()
también tiene un problema que puede resultar en errores si intenta usarlo de una manera intuitiva; no garantiza que el destino tenga una terminación nula. Para asegurarse de que tiene que asegurarse de manejar específicamente esa caja de la esquina, coloque'\0'
usted mismo en la última ubicación del búfer (al menos en ciertas situaciones).Sugeriría usar algo como OpenBSD
strlcat()
ystrlcpy()
(aunque sé que a algunas personas no les gustan esas funciones; creo que son mucho más fáciles de usar de forma segura questrncat()
/strncpy()
).Aquí hay un poco de lo que Todd Miller y Theo de Raadt dijeron sobre los problemas con
strncat()
ystrncpy()
:La auditoría de seguridad de OpenBSD encontró que los errores con estas funciones eran "desenfrenados". A diferencia
gets()
, estas funciones se pueden utilizar de forma segura, pero en la práctica existen muchos problemas porque la interfaz es confusa, poco intuitiva y difícil de utilizar correctamente. Sé que Microsoft también ha realizado análisis (aunque no sé cuántos de sus datos pueden haber publicado) y, como resultado, ha prohibido (o al menos muy fuertemente desalentado; la 'prohibición' podría no ser absoluta) uso destrncat()
ystrncpy()
(entre otras funciones).Algunos enlaces con más información:
fuente
memmove()
. (Bueno, puede usarlomemcpy()
en el caso normal cuando las cadenas son independientes).strncat()
siempre termina la cadena de destino.strncat()
. Sin embargo, es correcto parastrncpy()
, que tiene algunos otros problemas.strncat()
es un reemplazo razonable destrcat()
, perostrncpy()
no un reemplazo razonable destrcpy()
.strncat()
no siempre termina en nula. Estaba confundiendo los comportamientos destrncat()
ystrncpy()
un poco (otra razón por la que son funciones que deben evitarse: tienen nombres que implican comportamientos similares, pero de hecho se comportan de manera diferente en formas importantes ...). He modificado mi respuesta para corregir esto y para agregar información adicional.char str[N] = ""; strncat(str, "long string", sizeof(str));
es un desbordamiento de búfer si N no es lo suficientemente grande. Lastrncat()
función es demasiado fácil de usar incorrectamente; No debería ser usado. Si puede usarlo destrncat()
manera segura, podría haber usadomemmove()
o en sumemcpy()
lugar (y esos serían más eficientes).Funciones de biblioteca estándar que nunca deben usarse:
setjmp.h
setjmp()
. Junto conlongjmp()
, estas funciones son ampliamente reconocidas como increíblemente peligrosas de usar: conducen a la programación espagueti, vienen con numerosas formas de comportamiento indefinido, pueden causar efectos secundarios no deseados en el entorno del programa, como afectar los valores almacenados en la pila. Referencias: MISRA-C: 2012 regla 21.4, CERT C MSC22-C .longjmp()
. Versetjmp()
.stdio.h
gets()
. La función se ha eliminado del lenguaje C (según C11), ya que no era segura según el diseño. La función ya estaba marcada como obsoleta en C99. Úselo en sufgets()
lugar. Referencias: ISO 9899: 2011 K.3.5.4.1, ver también la nota 404.stdlib.h
atoi()
familia de funciones. Estos no tienen manejo de errores pero invocan un comportamiento indefinido siempre que ocurren errores. Funciones completamente superfluas que se pueden reemplazar con lastrtol()
familia de funciones. 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. Consulte también los comentarios destrncpy()
.strncpy()
. La intención de esta función nunca fue ser una versión más segura destrcpy()
. Su único propósito fue siempre manejar un formato de cadena antiguo en sistemas Unix, y que se incluyera 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 programadores a menudo la usan incorrectamente. Referencias: ¿Por qué strlcpy y strlcat se consideran inseguros? .Funciones de biblioteca estándar que deben usarse con precaución:
afirmar.h
assert()
. Viene con gastos generales y generalmente no debe usarse en código de producción. Es mejor utilizar un controlador de errores específico de la aplicación que muestre errores pero no cierre necesariamente todo el programa.señal.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 programa C es casi siempre una indicación de un diseño deficiente del programa. Debe evitarse a menos que tenga requisitos muy específicos.stdio.h
Generalmente, esta biblioteca completa no se recomienda para código de producción , ya que viene con numerosos casos de comportamiento mal definido y seguridad de tipos deficiente.
fflush()
. Perfectamente bien para usar en flujos de salida. Invoca un comportamiento indefinido si se usa para flujos de entrada.gets_s()
. Versión segura degets()
incluida en la interfaz de verificación de límites C11. Se prefiere usarfgets()
en su lugar, según la recomendación estándar C. Referencias: ISO 9899: 2011 K.3.5.4.1.printf()
familia de funciones. Funciones con muchos recursos que vienen con mucho comportamiento indefinido y seguridad de tipos deficiente.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 sobreprintf()
. Además, -scanf()
es vulnerable a saturaciones de búfer si no se usa correctamente.fgets()
se prefiere usar 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
malloc()
familia de funciones. Perfectamente bien para usar en sistemas alojados, aunque tenga en cuenta los problemas conocidos en C90 y, por lo tanto , no emita el resultado . losmalloc()
familia de funciones 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 anterior con el resultado derealloc()
. En caso de que la función falle, crea una fuga.system()
. Viene con mucha sobrecarga y, aunque es portátil, a menudo es mejor usar funciones API específicas del sistema. Viene con varios comportamientos mal definidos. Referencias: CERT C ENV33-C .string.h
strcat()
. Ver comentarios parastrcpy()
.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 comprueba el tamaño de los datos entrantes, puede haber desbordes de búfer. Lo cual no es culpa destrcpy()
sí mismo, sino de la aplicación questrcpy()
realiza la llamada, que no es seguro es principalmente un mito creado por Microsoft .strtok()
. Altera la cadena de llamada y utiliza variables de estado internas, lo que podría hacerla insegura en un entorno de subprocesos múltiples.fuente
static_assert()
en lugar deassert()
, si la condición se puede resolver en tiempo de compilación. Además,sprintf()
casi siempre se puede reemplazar, losnprintf()
que es un poco más seguro.strtok()
en una función A significa que (a) la función no puede llamar a ninguna otra función que también usestrtok()
mientras A la está usando, y (b) significa que ninguna función que llame a A puede estar usandostrtok()
cuando llama a A. En otras palabras, usarstrtok()
envenena la cadena de llamadas; no se puede utilizar de forma segura en el código de la biblioteca porque debe documentar que utilizastrtok()
para evitar que otros usuariosstrtok()
llamen al código de la biblioteca.strncpy
es la función correcta de usar cuando se escribe un cero acolchado búfer de cadena con datos tomados de una cadena terminada en cero o un tampón con relleno de ceros cuyo tamaño es al menos tan grande como el destino. Los búferes con relleno cero no son muy comunes, pero tampoco son exactamente oscuros.Algunas personas afirmarían que
strcpy
ystrcat
debería evitarse, a favor destrncpy
ystrncat
. Esto es algo subjetivo, en mi opinión.Definitivamente deben evitarse cuando se trata de la entrada del usuario, sin duda aquí.
En el código "lejos" del usuario, cuando simplemente se sabe que los búferes son lo suficientemente largos,
strcpy
ystrcat
pueden ser un poco más eficientes porque calcular eln
para pasar a sus primos puede ser superfluo.fuente
strlcat
ystrlcpy
, dado que la versión 'n' no garantiza la terminación NULL de la cadena de destino.strncpy()
escribirá exactamenten
bytes de escritura , usando caracteres nulos si es necesario. Como Dan, usar una versión segura es la mejor opción en mi opinión.strncat()
puede ser difícil de usar de manera correcta y segura (y debe evitarse) está en el tema.Evitar
strtok
para programas multiproceso ya que no es seguro para subprocesos.gets
ya que podría causar un desbordamiento del búferfuente
strtok()
va un poco más allá de la seguridad de los subprocesos: no es seguro ni siquiera en un programa de un solo subproceso a menos que sepa con certeza que ninguna función que su código pueda llamar mientras se usastrtok()
, no lo use también (o estropearán elstrtok()
estado correcto de debajo de ti). De hecho, la mayoría de los compiladores que se dirigen a plataformas de subprocesos múltiples se encargan destrtok()
los problemas potenciales en lo que respecta a los subprocesos mediante el uso de almacenamiento local de subprocesos parastrtok()
los datos estáticos de '. Pero eso todavía no soluciona el problema de que otras funciones lo usen mientras usted está (en el mismo hilo).strtok()
es que mantiene precisamente un búfer para trabajar, por lo que la mayoría de los buenos reemplazos requieren mantener un valor de búfer y pasarlo.strcspn
hace la mayor parte de lo que necesita: busque el siguiente separador de tokens. Puede volver a implementar una variante cuerdastrtok
con él.Probablemente valga la pena agregar nuevamente que
strncpy()
no es el reemplazo de propósito general parastrcpy()
que su nombre podría sugerir. Está diseñado para campos de longitud fija que no necesitan un terminador nulo (originalmente fue diseñado para usarse con entradas de directorio UNIX, pero puede ser útil para cosas como campos de clave de cifrado).Sin embargo, es fácil de usar
strncat()
como reemplazo destrcpy()
:(La
if
prueba, obviamente, puede descartarse en el caso común, donde sabe quedest_size
definitivamente es distinto de cero).fuente
Consulte también la lista de API prohibidas de Microsoft . Estas son API (incluidas muchas que ya se enumeran aquí) que están prohibidas en el código de Microsoft porque a menudo se usan de manera incorrecta y generan problemas de seguridad.
Puede que no esté de acuerdo con todos ellos, pero vale la pena considerarlos todos. Añaden una API a la lista cuando su mal uso ha provocado una serie de errores de seguridad.
fuente
Casi cualquier función que se ocupe de 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.
fuente
No te olvides de sprintf, es la causa de muchos problemas. Esto es cierto porque la alternativa, snprintf, a veces tiene diferentes implementaciones que pueden hacer que su código no sea portátil.
linux: http://linux.die.net/man/3/snprintf
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 todo el búfer (si es más pequeño que el tamaño del búfer dado, la salida se truncó)
En el caso 2 (ventanas), el valor devuelto es un número negativo en caso de que la salida esté truncada.
Por lo general, debe evitar funciones que no sean:
seguro de desbordamiento de búfer (ya se mencionan muchas funciones aquí)
hilo seguro / no reentrante (strtok por ejemplo)
En el manual de cada función debe buscar palabras clave como: seguro, sincronización, asíncrono, hilo, búfer, errores
fuente
_sprintf()
fue creado por Microsoft antes de que llegara el estándarsnprintf()
, IIRC. ´StringCbPrintf () ´ es bastante similar asnprintf()
aunque.sprintf
alguna manera de manera segura en algunos casos:sprintf(buffer,"%10s",input);
limita los nb bytes copiados a 10 (sibuffer
eschar buffer[11]
seguro, incluso si los datos pueden terminar truncados.Es muy difícil de usar de
scanf
forma segura. El buen uso descanf
puede evitar desbordamientos de búfer, pero aún es vulnerable a un comportamiento indefinido al leer números que no encajan en el tipo solicitado. En la mayoría de los casos,fgets
seguido de auto-análisis (usandosscanf
,strchr
, etc.) es una mejor opción.Pero yo no diría "evitar
scanf
todo el tiempo".scanf
tiene sus usos. Como ejemplo, digamos que desea leer la entrada del usuario en unachar
matriz 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, desea almacenar los primeros 9 caracteres en el búfer y descartar todo hasta la siguiente línea nueva. Tu puedes hacer:Una vez que te acostumbras a este idioma, es más corto y de alguna manera más limpio que:
fuente
En todos los escenarios de copia / movimiento de cadenas, strcat (), strncat (), strcpy (), strncpy (), etc., las cosas van mucho mejor ( más seguras ) si se aplican un par de heurísticas simples:
1. Siempre relleno NUL su (s) búfer (s) antes de agregar datos.
2. Declare los búferes de caracteres como [SIZE + 1], con una macro-constante.
Por ejemplo, dado:
podemos usar código como:
relativamente seguro. Memset () debería aparecer antes de strncpy (), aunque inicializamos Buffer en tiempo de compilación, porque no sabemos qué basura colocó otro código en él antes de que se llamara a nuestra función. Strncpy () truncará los datos copiados a "1234567890" y no los terminará en NUL. Sin embargo, dado que ya hemos llenado con NUL todo el búfer, tamaño de (búfer), en lugar de BUFSIZE, se garantiza que habrá un NUL final "fuera de alcance" final de todos modos, siempre que restrinjamos nuestras escrituras usando BUFSIZE constante, en lugar de sizeof (Buffer).
Buffer y BUFSIZE también funcionan bien para snprintf ():
Aunque snprintf () escribe específicamente solo caracteres BUFIZE-1, seguidos de NUL, esto funciona de forma segura. Así que "desperdiciamos" un byte NUL extraño al final del búfer ... evitamos tanto el desbordamiento del búfer como las condiciones de cadena sin terminar, por un costo de memoria bastante pequeño.
Mi llamada a strcat () y strncat () es más estricta: no los use. Es difícil usar strcat () de forma segura, y la API para strncat () es tan contraintuitiva que el esfuerzo necesario para usarla correctamente niega cualquier beneficio. Propongo el siguiente drop-in:
Es tentador crear un strcat () drop-in, pero no es una buena idea:
porque el objetivo puede ser un puntero (por lo tanto, sizeof () no devuelve la información que necesitamos). No tengo una buena solución "universal" para las instancias de strcat () en su código.
Un problema que encuentro con frecuencia de los programadores "strFunc () - consciente" es un intento de proteger contra desbordamientos de búfer mediante strlen (). Esto está bien si se garantiza que el contenido tiene terminación NUL. De lo contrario, strlen () en sí mismo puede causar un error de saturación del búfer (que generalmente conduce a una violación de segmentación u otra situación de volcado de núcleo), antes de que llegue al código "problemático" que está tratando de proteger.
fuente
atoi no es seguro para subprocesos. En su lugar, uso strtol, por recomendación de la página de manual.
fuente
strtol()
que sea seguro para subprocesos yatoi()
no lo sea.man atoi
(aunque debería haberla).