tag ejemplos c fgets buffer-overflow gets

c - meta tags ejemplos



¿Por qué es tan peligrosa la función de obtención que no debería utilizarse? (11)

¿Por qué se gets() peligroso?

El primer gusano de Internet (el gusano de Internet de Morris ) escapó hace unos 30 años (1988-11-02), y utilizó get gets() y 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.

Debes olvidar que alguna vez escuchaste que gets() existía.

La norma C11 ISO / IEC 9899: 2011 eliminó gets() como una función estándar, que es A Good Thing ™ (fue marcada formalmente como ''obsolescente'' y ''obsoleta'' 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 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 se bloqueará de todos modos, tarde o temprano, es mejor evitar los problemas más pronto 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 mktemp() , y también para algunas otras funciones que también tienen problemas de seguridad ( mktemp() , ...).

Alternativas para gets()

fgets ()

Como todos los demás dijeron, la alternativa canónica a gets() es fgets() especifica 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í lo hace. Por lo tanto, es posible que necesites usar una envoltura 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; }

Además, como caf señala en un comentario y paxdiablo muestra en su respuesta, con fgets() puede que tengas datos en una línea. El código de mi envoltorio deja esos datos para leerlos la próxima vez; puede modificarlo fácilmente para engullir el resto de la línea de datos si 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 fueron truncados.

Este problema no surge con gets() porque no sabe dónde finaliza el búfer y se pisotea alegremente más allá del final, causando estragos en el diseño de la memoria bellamente atendida, a menudo estropeando la pila de retorno (un Home ) si el búfer se asigna en la pila, o pisotea la información de control si el búfer se asigna dinámicamente, o se copian datos sobre otras variables globales (o módulo) preciosas si el búfer se asigna estáticamente. Ninguno de estos es una buena idea: personifican la frase ''comportamiento indefinido''.

También está el TR 24731-1 (Informe técnico del Comité de Estándar de 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);

Restricciones de tiempo de ejecución

s no será un puntero nulo. n no será igual a cero ni será mayor que RSIZE_MAX. Se producirá un error de lectura de nueva línea, de fin de archivo o de lectura dentro de la lectura de n-1 caracteres de la 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 de la stdin hasta que se lee un carácter de nueva línea, o se produce un error de fin de archivo o de lectura.

Descripción

4 La función gets_s lee como máximo uno menos que el número de caracteres especificados por n en el flujo al que apunta stdin , en la matriz apuntada 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. Se escribe un carácter nulo inmediatamente después de la última lectura del carácter 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 de s toman valores no especificados .

Práctica recomendada

6 La función fgets permite que los programas correctamente escritos procesen de forma segura las líneas de entrada durante demasiado tiempo para almacenarlas en la matriz de resultados. En general, esto requiere que las personas que llaman a 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 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 que desborda el búfer para almacenarlo. A diferencia de fgets , gets_s mantiene una relación de 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 de TR.

La norma C11, ISO / IEC 9899-2011, incluye TR24731 en el Anexo K como 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 gets() llamada getline() . Asigna espacio para la línea dinámicamente, por lo que terminas necesitando liberarla. Se 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 de ''elige tu propio delimitador de un solo carácter'' llamada getdelim() ; esto puede ser útil si está tratando con la salida de find -print0 donde los extremos de los nombres de los archivos están marcados con un carácter ASCII NUL ''/0'' , por ejemplo.

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

entiendo esto

advertencia :

(.text + 0x34): advertencia: la función `gets ''es peligrosa y no debe utilizarse.

Recuerdo que esto tiene algo que ver con la protección de la pila y la seguridad, pero no estoy seguro de por qué.

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

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


En C11 (ISO / IEC 9899: 201x), gets() ha eliminado gets() . (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 , fgets() sigue siendo el preferido.

La función fgets 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 a 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 .


La función C obtiene es peligrosa y ha sido un error muy costoso. Tony Hoare lo destaca por su mención específica en su charla "Referencias nulas: El error de mil millones de dólares":

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

Vale la pena ver toda la hora, pero para sus comentarios, a partir de los 30 minutos, las críticas específicas son de alrededor de 39 minutos.

Afortunadamente, esto le abre el apetito a toda la charla, lo que llama la atención sobre cómo necesitamos pruebas de corrección más formales en los 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 la razón dudosa para que los diseñadores de malas lenguas culpen a los programadores bajo la apariencia de "libertad de programador".


Leí recientemente, en una publicación de USENET para comp.lang.c , que gets() se elimina de la Norma. Woohoo

Le alegrará saber que el comité acaba de votar (por unanimidad, como resultado) para eliminar también gets () del borrador.


Me gustaría extender una invitación sincera a todos los mantenedores de bibliotecas de C que todavía están incluidos en sus bibliotecas "en caso de que alguien todavía dependa de ello": reemplace su implementación con el equivalente de

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

Esto ayudará a asegurar que nadie siga dependiendo de ello. Gracias.


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

De hecho, ISO ha dado el paso de eliminar las normas del estándar C (a partir de C11, aunque estaba en desuso en C99), lo que, dado el alto grado de compatibilidad con versiones anteriores, debería ser una indicación de cuán mala era esa función.

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

Pero esto también tiene sus problemas como:

  • los caracteres adicionales ingresados ​​por el usuario serán recogidos la próxima vez.
  • no hay notificación rápida de que el usuario ingresó demasiados datos.

Con ese fin, casi todos los codificadores de C en algún momento de su carrera escribirán también un envoltorio más útil sobre fgets . Aquí está el mío:

#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 los fgets ya que evita los desbordamientos del búfer, pero también notifica a la persona que llama lo que sucedió y borra el exceso de caracteres para que no afecten su próxima operación de entrada.

Siéntase libre de usarlo como desee, por este medio lo libero bajo la licencia "haga lo que usted quiere":


No puedes eliminar las funciones de la API sin romper la API. Si lo hicieras, muchas aplicaciones ya no se compilarían o ejecutarían.

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

La lectura de una línea que desborda la matriz apuntada por s da como resultado un comportamiento indefinido. Se recomienda el uso de fgets ().


Para poder utilizarlo de forma segura, debes saber exactamente cuántos caracteres leerás, para que puedas hacer que tu búfer sea lo suficientemente grande. Solo sabrás que si sabes exactamente qué datos leerás.

En lugar de usar gets , desea utilizar 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á que lidiar con eso).

Se mantuvo como parte oficial del idioma hasta el estándar ISO C de 1999, pero fue eliminado oficialmente por 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 use.


Porque get no realiza ningún tipo de comprobación al obtener bytes de stdin y colocarlos en algún lugar. Un ejemplo simple:

char array1[] = "12345"; char array2[] = "67890"; gets(array1);

Ahora, en primer lugar, se le permite ingresar la cantidad de caracteres que desea, a los que no les importan. En segundo lugar, los bytes sobre el tamaño de la matriz en la que los coloca (en este caso array1 ) sobrescribirán lo que encuentren en la memoria, ya que gets los escribirá. En el ejemplo anterior, esto significa que si "abcdefghijklmnopqrts" tal vez, de manera impredecible, se sobrescriba también array2 o lo que sea.

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


fgets

Para leer del stdin:

char string[512]; fgets(string, sizeof(string), stdin); /* no buffer overflows here, you''re safe! */


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

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