El peligro de gets()

La explicación que daba Pere Martra en su artículo Buffer Overflow para negados. I-Parte, es la más inteligible con las que me he topado. Naturalmente conocía desde hace mucho los problemas de desbordamiento de buffer (buffer overflow o buffer overrun). En los tiempos de DOS, cuando no había protección de memoria a nivel de sistema operativo, lo veíamos a menudo al programar en Turbo C.

Editabas tu programa, lo ejecutabas, aquella magnífica característica que ahora casi todos los entornos de desarrollo tienen, que automáticamente te hacía un make, y te lanzaba el ejecutable. Si había algún problema de overrun o underrun en tu programa, lo notabas porque el IDE acababa desestabilizándose, y tenías que acabar reiniciando MS-DOS (o DR-DOS o PC-DOS).

Como yo usaba conio.h y dirio.h, nunca llegué a plantearme lo débil que era en ese sentido gets(). Una función a la que se le pasa un buffer, y donde meterá todo lo que se haya leído por la entrada estándar (el teclado habitualmente). Es fácil pensar, que si declaro un buffer de 1024 bytes, la pila se corromperá si alguien introduce 1025 bytes por teclado. En realidad metiendo 1024 ya pasaría, pero ese es otro tema.

Es decir, cualquier programa que usase gets(), era susceptible a ese problema. Como yo usaba cgets(), que era más eficiente, y soportaba posicionamiento y colores, esto no ocurría. A cgets, se le pasaba como primer elemento del buffer, la longitud máxima a leer, y el devolvía como segundo elemento, la cantidad leída, y a partir de ahi los caracteres que se introdujeron. Si programabas bien, nunca ocurría un overflow de esta manera. Pero claro conio.h, no era una función estándar de C, sino una extensión, que si bien implementaban la mayoría de compiladores (Borland, Microsoft, Watcom, Symantec, …), no era tan portable.

Hemos tenido que llegar hasta C11 (ISO/IEC 9899:2011), el más reciente estándar de C, para que gets haya quedado obsoleta, reemplazada por su versión segura llamada gets_s(), y que Visual C++ soporta desde la versión 2005. En gets_s(), básicamente imitamos el funcionamiento de cgets, sólo que la longitud máxima a leer, se pasa como segundo argumento.

Como hay tanto código escrito que utiliza gets(), y que por tanto es vulnerable, muchos compiladores recientes, la marcan como obsoleta, y es el motivo por el que en Práctica fundamentos de la programación tuve que acudir a _CRT_SECURE_NO_DEPRECATE y a _CRT_SECURE_NO_WARNINGS. Si quieres asustarte un poco, mira esta búsqueda en searchcode.

Pero si tu compilador no soporta la nueva gets_s, el arreglo es muy sencillo, y se conoce desde hace años, porque fgets(), es también una función segura, al aceptar el máximo de caracteres que queremos leer. Pensarás que fgets es para ficheros (o flujos, o streams, como los llames), pero quizás recuerdes, que en C, el teclado (stdin), igual que la pantalla (stdout), son también un flujo, así que podemos hacer algo así:

fgets(acString, sizeof(acString), stdin);

Y si nos es más cómodo, aplicarlo mediante un macro de preprocesador:

#define gets_s(pacString, piSize) fgets((pacString), (piSize), stdin)

El mismo problema tienen otras funciones también de uso habitual tales como strcpy (strcpy_s), strncpy (strncpy_s), strcat (strcat_s), strncat (strncat_s), strlen (strlen_s), strtok (strtok_s), memcpy (memcpy_s), sprintf (sprintf_s), scanf (scanf_s), itoa (_itoa_s), itoa (_itoa_s) y otras más específicas como makepath (_makepath_s) y _splitpath (_splitpath_s).

Es curioso que aunque las funciones que ya tienen un límite de caracteres a procesar, como strncpy, o memcpy, tengan su “versión segura”. Por lo que dicen en Microsoft, es porque hay tanto código mal escrito usándolas, que es mejor migrarlo a las nuevas con _s.

El peligro de gets()

11 comentarios en “El peligro de gets()”

  1. Eres muy malo. Porque no recuerdo nada de C y este post me hace recordar que recuerdo todavia menos de lo que creía recordar 😀

  2. Javier Gutiérrez Chamorro (Guti)

    Entonces es que me he explicado fatal BiaNamaran. La cosa es sencilla, gets acepta como parámetro de entrada, un array de caracteres. En realidad un puntero a éste, pero a efectos prácticos, da igual.

    Ocurre, que gets no tiene un límite del máximo de caracteres que puede leer, así que si nuestro array es de 100 caracteres, y a gets, le vamos introduciendo más y más por el teclado, al final sobrepasamos ese array, y empezamos a llenar otras direcciones de memoria.

  3. no, no es que te expliques mal. si te digo los años que hace que no tipeo en C… Bueno, no te lo digo para no asustarte.
    Cada vez que pasa mas el tiempo y comparo otros lenguajes de programacion con VB o Pascal, mas me distancia les veo. Puede ser porque cada vez lo poco que recordaba de los otros se va diluyendo mas y mas… Creo que de C solo recuerdo que habia algo como “void” o una cosa asi, y de java que hasta para respirar tenias que crear una clase. Y delphi y VB pasando de todo, jajaja! Con VB le das una patada a una puerta y tienes un programa gestor de bases de datos o un navegador de archivos sin despeinarte. Y los de java y c# sudando. Las consultoras son masoquistas (o yo no entiendo nada, que probablemente sera mas bien eso).

  4. Javier Gutiérrez Chamorro (Guti)

    Es lo que me gustaba de VB. Que puedes programar tan bien, o tan mal como quieras. El lenguaje no te fuerza a nada. No es como Delphi, donde todo es tan estrictamente tipado, que hay muchas guarrerías que no puedes hacer. En C, aunque no lo parezca, sí que se pueden hacer, lo único que tienes que saber bastante.

  5. Es lo que me gustaba de VB. Que puedes programar tan bien, o tan mal como quieras. El lenguaje no te fuerza a nada.
    —————
    bueno y malo, si no eres específico puede intentar sumar y no concatenar 🙁

  6. Javier Gutiérrez Chamorro (Guti)

    Cierto. Y también te permite no declarar variables, que por defecto serán Variant, y hacer algo como:
    a = “3”
    a = a + “Hola”
    a = a + 2

    Pero lo que me gusta, es que también puedes declararlas, y tiparlas, si es lo que necesitas. Es flexible, te permite ambas metodologías.

  7. Muchas gracias por la cita Javier 🙂

    Acabo de descubrir tu blog y veo que…ohhhh sopresa… está lleno de artículos de Casio! Y unos zapatos made in spain, que no recuerdo ahora la marca… gran descubrimiento 🙂 seguramente le dedicaré unas cuantas horas mensuales a leer lo que me he perdido,

    Saludos.

  8. Javier Gutiérrez Chamorro (Guti)

    Gracias Pere Martra. Espero verte a menudo por aquí, ¡aunque sólo sea por los viejos tiempos en Aventia!

Deja un comentario