Porque es gets()
peligroso
El primer gusano de internet ( Morris Internet Worm ) escapó hace unos 30 años (1988-11-02), y usó gets()
un desbordamiento de búfer como uno de sus métodos de propagación 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 dio.
Deberías olvidar que alguna vez escuchaste que gets()
existía.
El estándar C11 ISO / IEC 9899: 2011 se eliminó gets()
como una 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 (lo que significa 'décadas') por razones de compatibilidad con versiones anteriores. Si fuera por mí, la implementación de gets()
se convertiría en:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Dado que su código se bloqueará 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 de Linux generan advertencias si se vincula gets()
, y también para algunas otras funciones que también tienen problemas de seguridad ( mktemp()
, ...).
Alternativas a gets()
fgets ()
Como todos los demás dijeron, la alternativa canónica gets()
es fgets()
especificar stdin
como la secuencia del archivo.
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
Lo que nadie más mencionó es que gets()
no incluye la nueva línea, pero fgets()
sí. Por lo tanto, es posible que deba 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 leer la próxima vez; puede modificarlo fácilmente para engullir el resto de la línea de datos si lo prefiere:
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, lectura de línea y no truncada, y lectura de línea parcial pero los datos se truncaron.
Este problema no surge gets()
porque no sabe dónde termina su búfer y se aleja alegremente más allá del final, causando estragos en su diseño de memoria bellamente cuidado, a menudo estropeando 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 se asigna dinámicamente, o copiar datos sobre otras valiosas variables globales (o módulos) si el búfer se asigna 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é Estándar de C) que proporciona alternativas más seguras a 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 n-1
carácter de nueva línea, fin de archivo o error de lectura dentro de la lectura de
caracteres de stdin
. 25)
3 Si hay una violación de restricción de tiempo de ejecución, s[0]
se establece en el carácter nulo, y los caracteres se leen y descartan stdin
hasta que se lee un carácter de nueva línea, o al 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 por n
del flujo al que apunta stdin
, en la matriz a la que apunta s
. 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. Se escribe un carácter nulo 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, s[0]
se establece en el carácter nulo y los demás elementos s
toman valores no especificados.
Práctica recomendada
6 La fgets
función permite que los programas escritos correctamente procesen con seguridad las líneas de entrada durante demasiado tiempo para almacenarlas en la matriz de resultados. En general, esto requiere que las personas que llaman fgets
presten atención a la presencia o ausencia de un carácter de nueva línea en la matriz de resultados. Considere usar fgets
(junto con cualquier procesamiento necesario basado en caracteres de nueva línea) en lugar de
gets_s
.
25) La gets_s
función, a diferencia gets
, la convierte en una violación de restricción de tiempo de ejecución para que una línea de entrada desborde el búfer para almacenarla. A diferencia fgets
, gets_s
mantiene una relación uno a uno entre las líneas de entrada y las llamadas exitosas a gets_s
. Los programas que usan gets
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 una parte opcional de la biblioteca. Desafortunadamente, rara vez se implementa en sistemas similares a Unix.
getline()
- POSIX
POSIX 2008 también proporciona una alternativa segura a la gets()
llamada getline()
. Asigna espacio para la línea dinámicamente, por lo que terminará necesitando liberarla. Elimina la limitación en la longitud de la línea, por lo tanto. 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 'elige tu propio delimitador de un solo carácter' llamada getdelim()
; Esto puede ser útil si se trata de la salida desde find -print0
donde los extremos de los nombres de archivo están marcados con un carácter ASCII NUL '\0'
, por ejemplo.
gets()
Buffer_overflow_attack