funciona - gets() library
la función gets() en C (6)
¡Necesito ayuda otra vez! Pensé que era genial usar la función gets()
porque es como el scanf()
en el que podría obtener una entrada con espacios en blanco. Pero leí en uno de los hilos ( manejo de archivos de información del estudiante ) que no es bueno usarlo porque según ellos, es una herramienta del diablo para crear desbordamientos de búfer (que no entiendo)
Si uso la función gets()
, podría hacer esto. INGRESA TU NOMBRE: Keanu Reeves
.
Si uso scanf()
, solo podría hacer esto. INGRESA TU NOMBRE: Keanu
Así que seguí sus consejos y reemplacé todo mi código gets()
con fgets()
. El problema ahora es que algunos de mis códigos ya no funcionan ... ¿hay otras funciones además de gets()
y fgets()
que puedan leer toda la línea y que ignore el espacio en blanco?
es una herramienta del diablo para crear desbordamientos de búfer
Como gets
no toma un parámetro de longitud, no sabe qué tan grande es tu buffer de entrada. Si pasas en un buffer de 10 caracteres y el usuario ingresa 100 caracteres, obtendrás el punto.
fgets
es una alternativa más segura que gets
porque toma la longitud del buffer como parámetro, por lo que puedes llamarlo así:
fgets(str, 10, stdin);
y leerá como máximo 9 caracteres.
el problema es que ahora algunos de mis códigos ya no funcionan
Posiblemente, esto se deba a que fgets
también almacena el carácter de nueva línea final ( /n
) en el búfer: si su código no lo espera, debe eliminarlo manualmente:
int len = strlen(str);
if (len > 0 && str[len-1] == ''/n'')
str[len-1] = ''/0'';
Como otras respuestas han notado, gets()
no verifica el espacio del búfer. Además de los problemas de desbordamiento accidental, los usuarios malintencionados pueden usar esta debilidad para crear todo tipo de estragos.
Uno de los primeros gusanos diseminados, lanzado en 1988, usó gets()
para propagarse a través de Internet. Aquí hay un extracto interesante de la Programación Expert C de Peter Van Der Linden que explica cómo funcionó:
El error temprano obtiene () el gusano de Internet
Los problemas en C no se limitan solo al lenguaje. Algunas rutinas en la biblioteca estándar tienen semántica insegura. Esto fue dramáticamente demostrado en noviembre de 1988 por el programa gusano que se movió a través de miles de máquinas en la red de Internet. Cuando se despejó el humo y se completaron las investigaciones, se determinó que una de las formas en que se había propagado el gusano era a través de una debilidad en el finger
daemon, que acepta consultas a través de la red sobre quién está actualmente conectado. The finger
daemon, in.fingerd
, usó la rutina de E / S estándar gets()
.
La tarea nominal de gets()
es leer en una cadena de una secuencia. La persona que llama le dice dónde colocar los caracteres entrantes. Pero gets()
no verifica el espacio del búfer; de hecho, no puede verificar el espacio del búfer. Si la persona que llama proporciona un puntero a la pila y más información que el espacio en el búfer, gets()
sobrescribirá felizmente la pila. El finger
daemon contenía el código:
main(argc, argv)
char *argv[];
{
char line[512];
...
gets(line);
Aquí, la line
es una matriz de 512 bytes asignada automáticamente en la pila. Cuando un usuario proporciona más información que esa para el daemon de finger
, la rutina gets()
seguirá poniendo en la pila. La mayoría de las arquitecturas son vulnerables a sobrescribir una entrada existente en el medio de la pila con algo más grande, que también sobrescribe las entradas vecinas. El costo de verificar cada acceso de pila para el tamaño y el permiso sería prohibitivo en el software. Un malhechor bien informado puede enmendar la dirección de retorno en el registro de activación de procedimiento en la pila ocultando los patrones binarios correctos en la cadena de argumento. Esto desviará el flujo de ejecución no de vuelta al lugar de donde proviene, sino a una secuencia de instrucciones especiales (también depositada cuidadosamente en la pila) que llama a execv()
para reemplazar la imagen en ejecución con un caparazón. Voilà, ahora estás hablando con un shell en una máquina remota en lugar del daemon finger
, y puedes emitir comandos para arrastrar una copia del virus a otra máquina.
Irónicamente, la rutina gets()
es una función obsoleta que proporcionó compatibilidad con la primera versión de la biblioteca de E / S portátil y fue reemplazada por E / S estándar hace más de una década. La página de manual incluso recomienda encarecidamente que siempre se usen los fgets()
. La rutina fgets()
establece un límite en el número de caracteres leídos, por lo que no excederá el tamaño del búfer. El finger
daemon se hizo seguro con una corrección de dos líneas que reemplazó:
gets(line);
por las líneas:
if (fgets(line, sizeof(line), stdin) == NULL)
exit(1);
Esto absorbe una cantidad limitada de entrada y, por lo tanto, no puede ser manipulado para sobrescribir ubicaciones importantes por alguien que ejecuta el programa. Sin embargo, el estándar ANSI C no eliminó gets()
del idioma. Por lo tanto, aunque este programa en particular se hizo seguro, el defecto subyacente en la biblioteca estándar C no se eliminó.
Para leer todas las palabras usando scanf puedes hacerlo así
Ejemplo:
printf("Enter name: ");
scanf("%[^/n]s",name); //[^/n] is the trick
Puede leer más de un campo con scanf()
, por lo que puede hacer:
scanf("%s %s/n", first_name, last_name);
Sin embargo, creo que sería mejor leer una cadena y luego dividirla, ya que es posible que no hayan ingresado solo un nombre, o primero / medio / último.
¿Cuáles son los problemas que tienes con fgets()
?
El problema con gets()
es que devuelve la misma cantidad de caracteres que ingresa el usuario: usted, como quien llama, no tiene control sobre esto. Así que puede asignar 80 caracteres, el usuario puede escribir 100 caracteres y los últimos 20 se escribirán al final de la memoria que ha asignado, pisoteando quién sabe qué.
Puede usar scanf
para imitar gets
. Aunque no es lindo.
#include <stdio.h>
#define S_HELPER(X) # X
#define STRINGIZE(X) S_HELPER(X)
#define MAX_NAME_LEN 20
int flushinput(void) {
int ch;
while (((ch = getchar()) != EOF) && (ch != ''/n'')) /* void */;
return ch;
}
int main(void) {
char name[MAX_NAME_LEN + 1] = {0};
while (name[0] != ''*'') {
printf("Enter a name (* to quit): ");
fflush(stdout);
scanf("%" STRINGIZE(MAX_NAME_LEN) "[^/n]", name); /* safe gets */
if (flushinput() == EOF) break;
printf("Name: [%s]/n", name);
puts("");
}
return 0;
}
Es mucho mejor leer con fgets
y analizar (si es necesario) con sscanf
.
EDITAR explicando la llamada scanf y el código circundante.
La especificación de conversión "% [" de scanf
acepta un ancho de campo máximo que no incluye el terminador nulo. Por lo tanto, la matriz para contener la entrada debe tener 1 carácter más que la lectura con scanf.
Para hacer eso con solo una constante utilicé la macro STRINGIZE. Con esta macro puedo usar una constante # defined''d como un tamaño de matriz (para la definición de la variable) como una cadena (para el especificador).
Hay un aspecto más que merece mención: el flushinput
. Si se usa gets
, todos los datos se escriben en la memoria (incluso cuando el búfer se desborda) hasta, pero sin incluir, la nueva línea. Para imitar eso, el scanf
lee un número limitado de caracteres hasta, pero sin incluir, la nueva línea y, a diferencia de gets
, mantiene la nueva línea en el búfer de entrada . Entonces esa nueva línea necesita ser eliminada y eso es lo que hace flushinput
.
El resto del código fue principalmente para configurar un entorno de prueba.
Puedes ver esta pregunta: alternativa segura a gets()
. Hay una cantidad de respuestas útiles.
Debería ser más preciso acerca de por qué su código no funciona con fgets()
. Como explican las respuestas en la otra pregunta, debes ocuparte de la nueva línea que gets()
omite.