Cuando utilizas funciones como gets() o strcpy(), estás operando bajo la premisa de que el flujo de entrada siempre cabrá en el espacio de memoria que has reservado, lo cual es una falacia en sistemas reales. Estas funciones son intrínsecamente inseguras porque no tienen límites; simplemente escriben en la dirección de memoria que les indicas hasta que encuentran un carácter nulo (\0). Esto ocurre porque, en el diseño original de C, se priorizó la eficiencia extrema y la simplicidad sobre la validación de límites en tiempo de ejecución.
El problema surge cuando el flujo de datos excede el tamaño del buffer asignado, provocando un desbordamiento de memoria (buffer overflow). Esto permite que el programa escriba en zonas de memoria adyacentes, corrompiendo la pila (stack), alterando variables de control o permitiendo la ejecución de código arbitrario mediante el secuestro del puntero de retorno. En producción, nunca debes usar estas funciones; su uso es un error crítico de seguridad. La regla es simple: siempre debes pasar el tamaño del buffer como un argumento para que la función sepa dónde detenerse.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
// Definimos buffers con tamaños fijos
char nombre[32];
char comando[32];
char log_buffer[128];
printf("Introduce tu nombre de usuario: ");
/* fgets es la alternativa segura a gets().
Limitamos la lectura al tamaño del buffer para evitar desbordamientos. */
if (fgets(nombre, sizeof(nombre), stdin) == NULL) {
return EXIT_FAILURE;
}
/* fgets mantiene el carácter '\n' si hay espacio.
Usamos strcspn para encontrar la posición del salto de línea y lo anulamos. */
nombre[strcspn(nombre, "\n")] = '\0';
printf("Introduce el comando a ejecutar: ");
/* Para scanf, siempre debemos especificar la anchura máxima: %31s.
Esto asegura que no escriba más de 31 caracteres + el '\0' en un buffer de 32. */
if (scanf("%31s", comando) != 1) {
fprintf(stderr, "Error al leer el comando.\n");
return EXIT_FAILURE;
}
/* snprintf es la alternativa segura a sprintf y strcpy.
No solo evita el desbordamiento, sino que garantiza que el resultado
termine con un carácter nulo '\0', algo que strncpy no garantiza. */
int written = snprintf(log_buffer, sizeof(log_buffer), "LOG: Usuario [%s] ejecutó: %s", nombre, comando);
/* Verificamos si el mensaje fue truncado.
snprintf devuelve el número de caracteres que habrían sido escritos. */
if (written < 0) {
fprintf(stderr, "Error crítico en la generación del log.\n");
return EXIT_FAILURE;
} else if ((size_t)written >= sizeof(log_buffer)) {
fprintf(stderr, "Error: El mensaje de log es demasiado largo y fue truncado.\n");
} else {
printf("Mensaje de auditoría: %s\n", log_buffer);
}
return EXIT_SUCCESS;
}
Análisis del código
Fíjate en cómo manejamos la entrada de datos con fgets. A diferencia de la prohibida gets(), fgets requiere que le pasemos sizeof(nombre). Esto le dice al sistema operativo: “No escribas más de 32 bytes, sin importar lo que el usuario teclee”. Sin embargo, fgets tiene un comportamiento que suele pillar a los desarrolladores: si el usuario escribe algo corto, incluye el salto de línea \n en el buffer. Por eso usamos strcspn(nombre, "\n") para localizar ese carácter y reemplazarlo por un terminador nulo, dejando la cadena limpia para procesarla.
Con scanf, el peligro es el mismo: un usuario malintencionado podría introducir una cadena de 1000 caracteres para un buffer de 32. La solución es el especificador de ancho %31s. Es vital recordar que la anchura en scanf debe ser siempre tamaño_del_buffer - 1 para dejar espacio al \0.
Para la construcción del log, hemos optado por snprintf. Es la herramienta más versátil y segura. A diferencia de strcpy o strcat, snprintf te permite formatear, concatenar y copiar en una sola operación, manteniendo siempre el control sobre el límite de memoria. Un detalle de nivel senior es la comprobación del valor de retorno de snprintf. Si el valor devuelto es mayor o igual al tamaño del buffer (sizeof(log_buffer)), significa que el mensaje se truncó. En sistemas críticos, un log truncado puede ser tan inútil como un log inexistente para la auditoría forense.
El error frecuente
Un error clásico al intentar ser “seguro” es el uso de strncpy. Muchos desarrolladores creen que strncpy es el reemplazo directo de strcpy, pero tiene una trampa peligrosa: si la fuente es igual o más larga que el límite, strncpy no añade un carácter nulo al final del buffer.
char dest[10];
char src[] = "Hola Mundo"; // 11 caracteres
// ERROR: No se garantiza el null-terminator si src >= dest
strncpy(dest, src, sizeof(dest));
// 'dest' ahora contiene {'H','o','l','a',' ','M','u','n','d','o'} sin '\0'
printf("%s\n", dest); // ERROR: Seguirá leyendo memoria hasta encontrar un \0 aleatorio (Buffer Over-read)
Si usas strncpy, debes asegurarte manualmente de poner dest[sizeof(dest) - 1] = '\0';. Sin embargo, la recomendación moderna es usar snprintf incluso para copias simples o, si estás en entornos POSIX, strlcpy (aunque este último no es estándar de C).
N° 115