¿Por qué es la función gets tan peligrosa que no debe ser utilizada?


Cuando intento compilar código C que usa la función gets() con GCC,

Entiendo esto

aviso:

(.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 por qué?

¿Puede alguien ayudarme a eliminar esta advertencia y explicar por qué existe tal advertencia sobre el uso de gets()?

Si gets() es tan peligroso entonces ¿por qué no podemos eliminarlo?

Author: Joe, 2009-11-07

11 answers

Para usar gets de forma segura, debes saber exactamente cuántos caracteres leerás, para que puedas hacer que tu buffer sea lo suficientemente grande. Solo lo sabrás si sabes exactamente qué datos leerás.

En lugar de usar gets, desea 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.)

Siguió siendo una parte oficial de la lengua subir según la norma ISO C de 1999, pero fue oficialmente eliminado por el estándar de 2011. La mayoría de las implementaciones de C todavía lo soportan, pero al menos gcc emite una advertencia para cualquier código que lo use.

 137
Author: Thomas Owens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-05 00:18:33

¿Por qué es gets() peligroso

El primer gusano de Internet (el Gusano de Internet Morris) escapó hace unos 30 años (1988-11-02), y utilizó gets() y un desbordamiento de búfer como uno de sus métodos de propagación de sistema a sistema. El problema básico es que la función no sabe cuán 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.

Debes olvidar que alguna vez escuchaste eso gets() existió.

El estándar C11 ISO/IEC 9899:2011 eliminó gets() como una función estándar, lo cual es Una Buena Cosa™ (fue formalmente marcado como 'obsoleto' y 'obsoleto' en ISO/IEC 9899:1999/Cor.3: 2007-Corrección técnica 3 para C99, y luego eliminada en C11). Lamentablemente, permanecerá en las bibliotecas durante muchos años (lo que significa "décadas") por razones de compatibilidad con versiones anteriores. Si dependiera de 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 será crash de todos modos, tarde o temprano, es mejor para la cabeza del problema fuera 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 enlaza 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 a gets() es fgets() especificando stdin como el flujo de archivos.

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 necesite usar un envoltorio alrededor de 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;
}

También, como caf señala en un comentario y paxdiablo muestra en su respuesta, con fgets() usted podría tener datos sobrantes en una línea. Mi código de envoltura deja esos datos para ser leídos la próxima vez; puede modificarlos 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 reportar los tres estados de resultado diferentes - EOF o error, línea leída y no truncada, y línea leída parcial pero los datos fueron truncados.

Este problema no surge con 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, a menudo desordenando la pila de retorno (un Desbordamiento de pila ) si el búfer asignado en la pila, o pisoteando la información de control si el búfer está asignado dinámicamente, o copiando datos sobre otras preciosas variables globales (o módulos) 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 a una variedad de funciones, incluyendo gets():

§6.5.4.1 La función gets_s

Sinopsis

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Runtime-constraints

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 error de fin de archivo o de lectura dentro de la lectura n-1 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 se descartan desde stdin hasta que se lea un carácter de nueva línea, o se produce un error de lectura.

Descripción

4 La función gets_s lee como máximo uno menos que el número de caracteres especificado por n desde la corriente apuntada por stdin, hacia la matriz apuntada por s. No adicional los caracteres se leen 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 Leer. Un el 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 el array, o si error se produce durante la operación, entonces s[0] se establece en el carácter nulo, y el otro los elementos de s toman valores no especificados.

Práctica recomendada

6 La función fgets permite que los programas escritos correctamente procesen también de forma segura las líneas de entrada largo para almacenar en la matriz de resultados. En general, esto requiere que las personas que llaman de fgets paguen atención a la presencia o ausencia de un carácter de nueva línea en la matriz de resultados. Considerar usando fgets (junto con cualquier procesamiento necesario basado en caracteres de nueva línea) en lugar de gets_s.

25) La función gets_s, a diferencia de gets, hace que sea una violación de restricción de tiempo de ejecución para una línea de entrada a desbordar el búfer para almacenarlo. A diferencia de fgets, gets_s mantiene un one-to-one relación entre líneas de entrada y 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 hay 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 Unix-like sistema.


getline() - POSIX

POSIX 2008 también proporciona una alternativa segura a gets() llamado getline(). Asigna espacio para la línea dinámicamente, por lo que terminas 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' llamado getdelim(); esto puede ser útil si está tratando con la salida de find -print0 donde los extremos de los nombres de archivo están marcados con un carácter ASCII NUL '\0', por ejemplo.

 119
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-31 22:13:58

Porque gets no hace ningún tipo de comprobación mientras obtiene bytes de stdin y los coloca en algún lugar. Un ejemplo sencillo:

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

Ahora, en primer lugar, se le permite introducir cuántos caracteres desea, gets no le importará. En segundo lugar, los bytes sobre el tamaño de la matriz en la que los pones (en este caso array1) sobrescribirán lo que encuentren en la memoria porque gets los escribirá. En el ejemplo anterior esto significa que si introduces "abcdefghijklmnopqrts" tal vez, impredeciblemente, también sobrescribirá array2 o lo que sea.

La función no es segura porque asume una entrada consistente. NUNCA LO USE!

 21
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-07 19:03:26

No debe usar gets ya que no tiene forma de detener un desbordamiento de búfer. Si el usuario escribe más datos de los que caben en su búfer, lo más probable es que termine con corrupción o algo peor.

De hecho, ISO realmente han dado el paso de eliminar gets del estándar C (a partir de C11, aunque fue obsoleto en C99) que, dada la alta tasa de compatibilidad hacia atrás, debería ser una indicación de lo mala que era esa función.

Lo correcto es usar la función fgets con el controlador de archivo stdin ya que puede limitar los caracteres leídos del usuario.

Pero esto también tiene sus problemas tales como:

  • los caracteres adicionales introducidos por el usuario se recogerán la próxima vez.
  • no hay una notificación rápida de que el usuario haya introducido demasiados datos.

Con ese fin, casi todos los codificadores de C en algún momento de su carrera escribirán un envoltorio más útil alrededor de fgets también. Aca mine:

#include <stdio.h>
#include <string.h>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

Con algún código de prueba:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        printf ("No input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long\n");
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

Proporciona las mismas protecciones que fgets ya que evita los desbordamientos de búfer, pero también notifica al llamante lo que sucedió y borra el exceso de caracteres para que no afecten a su próxima operación de entrada.

Siéntase libre de usarlo como desee, por la presente lo libero bajo la licencia" haz lo que quieras": -)

 16
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-10 04:59:31

Fgets .

Para leer de la stdin:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
 10
Author: Thiago Silveira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-10 19:33:12

No puede eliminar funciones de la API sin romper la API. Si usted, muchas aplicaciones ya no se puede compilar o ejecutar a todos.

Esta es la razón por la que una referencia da:

Leyendo una línea que desborda array apuntado por s resultados en comportamiento indefinido. El uso de fgets() se recomienda.

 6
Author: Gerd Klima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-07 18:58:21

Leí recientemente, en un USENET post a comp.lang.c, que gets() está siendo eliminado del Estándar. WOOHOO

Usted estará feliz de saber que el la comisión acaba de votar (por unanimidad, resulta que) para eliminar gets () de el borrador también.

 4
Author: pmg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-07 19:21:20

En C11(ISO/IEC 9899:201x), gets() se ha eliminado. (Está en desuso en ISO/IEC 9899: 1999 / Cor.3: 2007 E))

Además de fgets(), C11 introduce una nueva alternativa segura gets_s():

C11 K. 3.5.4. 1 La función gets_s

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Sin embargo, en la sección Práctica recomendada, se sigue prefiriendo fgets().

La función fgets permite que los programas escritos correctamente procesen también de forma segura las líneas de entrada largo para almacenar en el resultado matriz. En general, esto requiere que las personas que llaman de fgets paguen atención a la presencia o ausencia de un carácter de nueva línea en la matriz de resultados. Considerar usando fgets (junto con cualquier procesamiento necesario basado en caracteres de nueva línea) en lugar de gets_s.

 4
Author: Yu Hao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-06 06:15:49

Me gustaría extender una seria invitación a cualquier mantenedor de bibliotecas de C que todavía esté incluyendo gets en sus bibliotecas "por si acaso alguien sigue dependiendo de ello": Por favor reemplace su implementación con el equivalente de

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

Esto ayudará a asegurarse de que nadie siga dependiendo de él. Agradecer.

 3
Author: Steve Summit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-31 21:52:25

gets() es peligroso porque es posible que el usuario bloquee el programa escribiendo demasiado en el prompt. No puede detectar el final de la memoria disponible, por lo que si asigna una cantidad de memoria demasiado pequeña para el propósito, puede causar un fallo de seg y un bloqueo. A veces parece muy poco probable que un usuario escriba 1000 letras en un mensaje destinado al nombre de una persona, pero como programadores, necesitamos hacer que nuestros programas sean a prueba de balas. (también puede ser un riesgo de seguridad si un usuario puede bloquear un sistema programa enviando demasiados datos).

fgets() permite especificar cuántos caracteres se extraen del búfer de entrada estándar, para que no sobrepasen la variable.

 3
Author: Aradhana Mohanty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-22 10:09:05

La función C gets es peligrosa y ha sido un error muy costoso. Tony Hoare lo singulariza para una mención específica en su charla "Null References: The Billion Dollar Mistake":

Http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Vale la pena ver toda la hora, pero para su vista de comentarios de 30 minutos en adelante con el específico recibe críticas alrededor de 39 minutos.

Espero que esto te despierte el apetito por toda la charla, lo que llama la atención sobre cómo necesitamos pruebas de corrección más formales en idiomas y cómo se debe culpar a los diseñadores de idiomas por los errores en sus idiomas, no al programador. Esta parece haber sido toda la dudosa razón para que los diseñadores de lenguajes malos echen la culpa a los programadores bajo la apariencia de 'libertad de programación'.

 2
Author: user3717661,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-01 01:00:46