domingo, febrero 08, 2009

strlcpy y strlcat: Funciones en C para copia y concatenación de cadenas

Hace algún tiempo, vía Reddit.proggraming di con el artículo de Todd C. Miller y Theo de Raadt,strlcpy and strlcat - consistent, safe, string copy and concatenation., donde hablan las decisiones que les llevaron a diseñar estas dos funciones para mitigar los problemas de buffer overflow o desbordamiento de búfer tan habituales en los programas en escritos en C. El artículo definitivo para entender las consecuencias de seguridad de estos errores de programación es el Smashing The Stack For Fun And Profit que Aleph One publicó en el número 49 de la Phrack Magazine

El típico programa de un buffer overflow en C se da en la siguiente función:

int mi_funcion(char *input){
    char buf[256];
    ....
    ....
    strcat("CADENA",buf);
    ....
    ....
    return 0;
}
Si la cadena a la que apunta input es controlada por el usuario y no existe ningún límite en el número de caracteres (ya sea porque estamos leyendo de un fichero en disco, de la red o entrada del usuario vía teclado), al usar la función strcat acabaremos sobrepansando el tamaño de buf, y escribiendo en direcciones de memoria que no se debe. Si el programa donde se da este fallo se ejecuta con privilegios, puede aprovecharse para hacer una escalada desde un usuario normal a otro privilegiado.

Lo primero es usar las funciones de la librería estándar de C:strncpy y strncat.

char* strncpy(char *restrict s1, const char *restrict s2, size_t n);
char* strncat(char *restrict s1, const char *restrict s2, size_t n);
El cambio fundamental es es el parámetro n, que especifica el número de bytes del búfer apuntado por s1. Así cualquiera de esas dos funciones copiará como máximo n bytes o bien si encuentra en la cadena que esté copiando, el carácter que manda el fin de cadena en C, el \0.

Sin embargo estas dos funciones tienen dos problemas:

  • strncpy no termina con el \0 la cadena si se utiliza todo el búfer.
  • strncat rellena de ceros todo el búfer de destino hasta el tamaño que se especifica
Para evitar estos problemas Todd C. Miller y Theo de Raadt propusieron las funciones strlcat y strlcpy:
size_t strlcpy(char *dst, const char *src, size_t size);
size_t strlcat(char *dst, const char *src, size_t size);
Estas dos funciones garantizan que las cadenas están terminada con un \0 y permite detectar de manera sencilla en el caso de strlcat la existencia de que una cadena se trunque, como puede verse en este ejemplo sacado de la página de manual de MacOS X:
if (strlcpy(pname, dir, sizeof(pname)) >= sizeof(pname))
    goto toolong;
if (strlcat(pname, file, sizeof(pname)) >= sizeof(pname))
    goto toolong;
Las decisiones de diseño que llevaron a estas funciones están descritas en el artículo enlazado al principio. Aunque hace mucho tiempo del artículo, en el año 1999, es una lectura interesante para comprender la evolución de las API para evitar los problemas de seguridad.

Actualización

Me comenta mig21 que estas funciones no siempre le han gustado a otros desarrolladores. Por ejemplo, Ulrich Drepper, mantenedor de la GNU libc:
This is horribly inefficient BSD crap. Using these function only leads to other errors. Correct string handling means that you always know how long your strings are and therefore you can you memcpy (instead of strcpy).
Y es interesante el enlace a la entrada en la wikipedia de strlcpy.

Technorati Tags: ,

3 comentarios:

mig21 dijo...

El uso de strlcpy y strlcat es también polémico (¿Alguien se libra de la polémica? ¿Ni una API para manejar cadenas en C? :)

Bueno, pues Ulrich Drepper, el mantenedor de la glibc no quiso incluirlas en ella porque oculta errores en lugar de mostrarlos. Con esa delicadeza característica de algunos desarrolladores dijo:

This is horribly inefficient BSD crap. Using these function only leads to other errors. Correct string handling means that you always know how long your strings are and therefore you can you memcpy(instead of strcpy).

Beside, those who are using strcat or variants deserved to be punished.


Y otras veces, preguntado por ellas apunta a un artículo interesante, escrito por él, claro, sobre que hacer en caso de que las cosas no vayan bien, en la que se habla de asprintf, que evita el malvado truncado :) (pero que se encarga de alojar memoria que luego habrá que liberar)

Lo que ha ocurrido al final, por cierto, es que, por lo que cuentan en la wiki en inglés, la gente de la Glib, rsync se han implementado su propia versión. También en el kernel Linux lo usan, por cierto.

Drizzt dijo...

Por desgracia, el tema de gestión correcta de cadenas en C parece un oximorón. Cualquier API que permita reducir la posibilidad de error siempre es bienvenida.

Ahora actualizo las entradas con los enlaces que has puesto, para el otro punto de vista :)

Anónimo dijo...

muy buen articulos. vi que en una parte se declara a un puntero como strict. esto que efecto hace?