¿Por qué la función gets es tan peligrosa que no debería utilizarse?

Resuelto Vinit Dhatrak asked hace 15 años • 13 respuestas

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?

Vinit Dhatrak avatar Nov 08 '09 01:11 Vinit Dhatrak
Aceptado

¿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 stdincomo 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_sfunció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

sno será un puntero nulo. nno 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 los n-1caracteres de stdin. 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 hasta stdinque 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_sfunción lee como máximo uno menos que el número de caracteres especificados por ndesde la secuencia señalada por stdin, en la matriz señalada por 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. 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 toman svalores no especificados.

Práctica recomendada

6 La fgetsfunció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 llaman fgetspresten 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_sfunción, a diferencia de gets, 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 de fgets, gets_smantiene una relación uno a uno entre las líneas de entrada y las llamadas exitosas a gets_s. Los programas que utilizan getsesperan 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 -print0los extremos de los nombres de los archivos están marcados con un carácter ASCII NUL '\0', por ejemplo.

Jonathan Leffler avatar Nov 30 '2010 01:11 Jonathan Leffler

Para utilizarlo getsde 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).

getssiguió 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.

Thomas Owens avatar Nov 07 '2009 18:11 Thomas Owens

Porque getsno 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, getsno 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 getslos escribirán. En el ejemplo anterior, esto significa que si ingresa "abcdefghijklmnopqrts"tal vez, de manera impredecible, también se sobrescribirá array2o lo que sea.

La función no es segura porque supone una entrada coherente. ¡NUNCA LO UTILICES!

Jack avatar Nov 07 '2009 19:11 Jack