¿Por qué la función gets es tan peligrosa que no debería utilizarse?
Cuando intento compilar código C que usa la gets()
función con GCC, aparece esta advertencia:
(.text+0x34): advertencia: la función `gets' es peligrosa y no debe usarse.
Recuerdo que esto tiene algo que ver con la protección y seguridad de la pila, pero no estoy seguro exactamente de por qué.
¿Cómo puedo eliminar esta advertencia y por qué aparece una advertencia sobre el uso gets()
?
Si gets()
es tan peligroso ¿por qué no podemos eliminarlo?
¿Por qué es gets()
peligroso?
El primer gusano de Internet (el gusano de Internet Morris ) se escapó hace unos 30 años (2 de noviembre de 1988) y utilizó gets()
un desbordamiento de búfer como uno de sus métodos para propagarse de un sistema a otro. El problema básico es que la función no sabe qué tan grande es el búfer, por lo que continúa leyendo hasta que encuentra una nueva línea o encuentra EOF, y puede desbordar los límites del búfer que se le proporcionó.
Deberías olvidar que alguna vez escuchaste que gets()
existía.
El estándar C11 ISO/IEC 9899:2011 eliminó gets()
como función estándar, que es A Good Thing™ (se marcó formalmente como 'obsoleto' y 'obsoleto' en ISO/IEC 9899:1999/Cor.3:2007 - Corrigendum técnico 3 para C99, y luego eliminado en C11). Lamentablemente, permanecerá en las bibliotecas durante muchos años (es decir, "décadas") por razones de compatibilidad con versiones anteriores. Si fuera por mí, la implementación de gets()
sería:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Dado que su código fallará de todos modos, tarde o temprano, es mejor evitar el problema más temprano que tarde. Estaría preparado para agregar un mensaje de error:
fputs("obsolete and dangerous function gets() called\n", stderr);
Las versiones modernas del sistema de compilación Linux generan advertencias si se vincula gets()
, y también para algunas otras funciones que también tienen problemas de seguridad ( mktemp()
,…).
Alternativas agets()
fget()
Como todos dijeron, la alternativa canónica gets()
es fgets()
especificar stdin
como flujo de archivos.
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
Lo que nadie más mencionó todavía es que gets()
no incluye la nueva línea, pero fgets()
sí la incluye. Por lo tanto, es posible que necesites usar un contenedor fgets()
que elimine la nueva línea:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
O mejor:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
Además, como caf señala en un comentario y paxdiablo muestra en su respuesta , fgets()
es posible que le queden datos en una línea. Mi código contenedor deja esos datos para leerlos la próxima vez; puedes modificarlo fácilmente para devorar el resto de la línea de datos si lo prefieres:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
El problema residual es cómo informar los tres estados de resultados diferentes: EOF o error, línea leída y no truncada y línea parcial leída pero los datos fueron truncados.
Este problema no surge gets()
porque no sabe dónde termina su búfer y pisotea alegremente más allá del final, causando estragos en su diseño de memoria bellamente cuidado y, a menudo, arruinando la pila de retorno (un desbordamiento de pila ) si el búfer está asignado en la pila, o pisotear la información de control si el búfer está asignado dinámicamente, o copiar datos sobre otras valiosas variables globales (o de módulo) si el búfer está asignado estáticamente. Ninguno de estos es una buena idea: personifican la frase "comportamiento indefinido".
También existe el TR 24731-1 (Informe técnico del Comité de Normas C) que proporciona alternativas más seguras para una variedad de funciones, que incluyen gets()
:
§6.5.4.1 La
gets_s
función
###Sinopsis
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Restricciones de tiempo de ejecución
s
no será un puntero nulo.n
no será igual a cero ni mayor que RSIZE_MAX. Se producirá un carácter de nueva línea, un fin de archivo o un error de lectura al leer losn-1
caracteres destdin
. 25)
3 Si hay una infracción de la restricción de tiempo de ejecución,
s[0]
se establece en el carácter nulo y los caracteres se leen y descartan hastastdin
que se lee un carácter de nueva línea, o se produce el final del archivo o se produce un error de lectura.
Descripción
4 La
gets_s
función lee como máximo uno menos que el número de caracteres especificados porn
desde la secuencia señalada porstdin
, en la matriz señalada pors
. No se leen caracteres adicionales después de un carácter de nueva línea (que se descarta) o después del final del archivo. El carácter de nueva línea descartado no cuenta para el número de caracteres leídos. Un carácter nulo se escribe inmediatamente después del último carácter leído en la matriz.
5 Si se encuentra el final del archivo y no se han leído caracteres en la matriz, o si se produce un error de lectura durante la operación, se
s[0]
establece en el carácter nulo y los demás elementos tomans
valores no especificados.
Práctica recomendada
6 La
fgets
función permite que programas escritos correctamente procesen de forma segura líneas de entrada demasiado largas para almacenarlas en la matriz de resultados. En general, esto requiere que quienes llamanfgets
presten atención a la presencia o ausencia de un carácter de nueva línea en la matriz de resultados. Considere usarfgets
(junto con cualquier procesamiento necesario basado en caracteres de nueva línea) en lugar degets_s
.
25) La
gets_s
función, a diferencia degets
, convierte en una violación de la restricción de tiempo de ejecución que una línea de entrada desborde el búfer para almacenarla. A diferencia defgets
,gets_s
mantiene una relación uno a uno entre las líneas de entrada y las llamadas exitosas agets_s
. Los programas que utilizangets
esperan tal relación.
Los compiladores de Microsoft Visual Studio implementan una aproximación al estándar TR 24731-1, pero existen diferencias entre las firmas implementadas por Microsoft y las del TR.
El estándar C11, ISO/IEC 9899-2011, incluye TR24731 en el Anexo K como parte opcional de la biblioteca. Desafortunadamente, rara vez se implementa en sistemas tipo Unix.
getline()
— POSIX
POSIX 2008 también proporciona una alternativa segura a gets()
los llamados getline()
. Asigna espacio para la línea de forma dinámica, por lo que acabas necesitando liberarla. Por lo tanto, elimina la limitación de la longitud de la línea. También devuelve la longitud de los datos que se leyeron, o -1
(¡y no EOF
!), lo que significa que los bytes nulos en la entrada se pueden manejar de manera confiable. También hay una variación para "elige tu propio delimitador de un solo carácter" llamada getdelim()
; Esto puede ser útil si está trabajando con la salida donde find -print0
los extremos de los nombres de los archivos están marcados con un carácter ASCII NUL '\0'
, por ejemplo.
Para utilizarlo gets
de forma segura, debe saber exactamente cuántos caracteres leerá, de modo que pueda hacer que su búfer sea lo suficientemente grande. Sólo lo sabrás si sabes exactamente qué datos leerás.
En lugar de usar gets
, quieres usar fgets
, que tiene la firma
char* fgets(char *string, int length, FILE * stream);
( fgets
, si lee una línea completa, dejará el '\n'
en la cadena; tendrás que lidiar con eso).
gets
siguió siendo una parte oficial del lenguaje hasta el estándar ISO C de 1999, pero se eliminó oficialmente en el estándar de 2011 . La mayoría de las implementaciones de C todavía lo admiten, pero al menos gcc emite una advertencia para cualquier código que lo utilice.
Porque gets
no realiza ningún tipo de verificación mientras obtiene bytes de la entrada estándar y los coloca en algún lugar. Un ejemplo sencillo:
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
Ahora, primero que nada, puedes ingresar cuántos caracteres deseas, gets
no te importará. En segundo lugar, los bytes que superen el tamaño de la matriz en la que los colocó (en este caso array1
) sobrescribirán todo lo que encuentren en la memoria porque gets
los escribirán. En el ejemplo anterior, esto significa que si ingresa "abcdefghijklmnopqrts"
tal vez, de manera impredecible, también se sobrescribirá array2
o lo que sea.
La función no es segura porque supone una entrada coherente. ¡NUNCA LO UTILICES!