Regla 0x400Ch: Los formatos de printf/scanf deben coincidir y no ser variables
Archivos y E/S (0x40XX)
0x400Ch: Los formatos de printf/scanf deben coincidir y no ser variables¶
Enunciado normativo¶
Los formatos de
printfyscanfDEBEN coincidir con sus argumentos y NO DEBEN construirse con datos variables no controlados.
Síntoma en el código del estudiante¶
La máscara de formato no coincide con el tipo de los argumentos: se imprime un
double con %d, se lee un double con %f (en lugar de %lf), se pasa un
long a %d, un size_t a %u o un char * a %c. El defecto aparece tanto
en printf como en scanf.
Diagnóstico¶
Mecanismo del defecto¶
Las funciones variádicas no reciben información de tipos: printf y scanf
confían ciegamente en la máscara para saber cuántos bytes y de qué tipo hay que
leer de la lista de argumentos. Un double ocupa 8 bytes y suele pasarse en un
registro de punto flotante; %d espera un int de 4 bytes en un registro
entero. El desajuste hace que la función lea de donde no corresponde.
En scanf el problema es más grave: el especificador determina no solo cuántos
bytes leer, sino en qué tipo de puntero escribir. Pasar un float * a %lf
hace que scanf escriba 8 bytes donde solo hay 4, pisando memoria contigua.
Consecuencia observable¶
En printf, la salida muestra valores absurdos (un número enorme, 0, nan)
sin ningún error de compilación. En scanf, la escritura con el tamaño
equivocado puede corromper variables vecinas en la pila y provocar fallos
posteriores difíciles de rastrear. Con las advertencias activadas, el
compilador avisa en la mayoría de los casos.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §7.21.6.1 exige que el argumento corresponda al tipo indicado
por el especificador de conversión de printf; cualquier otra combinación es
comportamiento indefinido. §7.21.6.2 hace lo propio para fscanf, donde además
la falta de coincidencia entre el especificador y el tipo apuntado por el
argumento también es indefinida.
Corrección idiomática¶
❌ Código con el antipatrón¶
double promedio = 7.5;
printf("Promedio: %d\n", promedio);
float nota;
scanf("%f", ¬a); /* correcto para float */
double media;
scanf("%f", &media); /* incorrecto: %f espera float* */Por qué es incorrecto: el primer printf interpreta los 8 bytes del double
como un int, y el segundo scanf escribe 4 bytes en una variable de 8. Ambos
son comportamiento indefinido, aunque el primero solo produzca basura visible.
✅ Código refactorizado¶
double promedio = 7.5;
printf("Promedio: %f\n", promedio);
float nota;
scanf("%f", ¬a);
double media;
scanf("%lf", &media);Cada especificador coincide con el tipo: %f para double en printf, %f
para float y %lf para double en scanf. Para size_t se usa %zu y para
long %ld.
Errores típicos al compilar o ejecutar¶
El compilador detecta el desajuste con las advertencias de formato.
warning: format '%d' expects argument of type 'int',
but argument 2 has type 'double' [-Wformat=]En ejecución, un scanf mal tipado puede corromper la pila.
$ ./promedio
Promedio: -927489456*** stack smashing detected ***: terminated
Aborted (core dumped)Checklist de verificación¶
Cada especificador coincide con el tipo real del argumento.
Uso
%fparadoubleenprintfy%lfparadoubleenscanf.Uso
%zuparasize_ty%ld/%lldpara enteros largos.Compilé con
-Wall -Wextray revisé las advertencias de formato.
Reglas relacionadas¶
0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura — regla asociada sobre la E/S con formato.
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — los retornos de
scanf/printftambién se verifican.0x5002h: Desarrollá y compilá siempre con todas las advertencias del compilador activadas — desarrollar y compilar con todas las advertencias activadas.
0x3010h: Las variables que representan tamaños o índices de arreglos deben ser de tipo size_t — usar
size_tpara tamaños e índices.
Síntoma en el código del estudiante¶
Una variable de tipo char * o char[] se pasa como primer argumento de
printf, fprintf, sprintf o snprintf, sin una máscara de formato literal
intermedia. Suele verse en funciones de aviso o de impresión de mensajes
recibidos.
Diagnóstico¶
Mecanismo del defecto¶
El primer argumento de printf no es “texto a imprimir”, sino la cadena de
formato: la función la recorre buscando especificadores (%d, %x, %s,
%n). Si ese argumento proviene del usuario, el usuario controla el formato.
Con %x o %p puede hacer que printf interprete como argumentos valores que
quedaron en los registros o en la pila, y así leer memoria privada del
proceso. Con %n puede incluso escribir en una dirección de memoria, porque
%n guarda la cantidad de caracteres impresos en el puntero que recibe como
argumento. Es la vulnerabilidad clásica de format string.
Aunque en el ámbito de la cátedra no haya un atacante, el mismo mecanismo se
activa por accidente: un mensaje con un % suelto hace que printf espere un
argumento que no existe y lea basura.
Consecuencia observable¶
Si el texto recibido contiene %s, %x o %n, la salida muestra valores
inesperados, el programa imprime contenido de la memoria y, con %n, puede
provocar una violación de segmento o corromper variables. El defecto es
dependiente del dato de entrada, por lo que puede no aparecer en las pruebas
felices.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §7.21.6.1 describe printf: el argumento format es una
cadena de formato cuyos especificadores determinan cómo se interpretan los
argumentos restantes. Pasar una cadena arbitraria como formato le otorga al
contenido el control de esa interpretación. El uso correcto es una máscara
literal constante, y %s para insertar el texto como dato.
Corrección idiomática¶
❌ Código con el antipatrón¶
#include <stdio.h>
void mostrar_aviso(const char *texto)
{
printf(texto);
}Por qué es incorrecto: texto se interpreta como formato. Un mensaje con %s o
%n altera el comportamiento de printf, leyendo o escribiendo memoria fuera
del propósito de la función.
✅ Código refactorizado¶
#include <stdio.h>
void mostrar_aviso(const char *texto)
{
printf("%s", texto);
}La máscara es literal y constante, y texto se inserta como dato. Si solo se
quiere volcar el texto, fputs(texto, stdout) es igual de claro y evita el
formato por completo.
Errores típicos al compilar o ejecutar¶
Con las advertencias de formato activadas, el compilador detecta el patrón.
warning: format not a string literal and no format arguments
[-Wformat-security]En ejecución, un formato manipulado puede tumbar el proceso.
$ ./avisos '%n'
Segmentation fault (core dumped)Un % suelto produce salida corrupta sin error de compilación.
$ ./avisos 'avance: 50%'
avance: 50% <basura leida de la pila>Checklist de verificación¶
¿Toda llamada a
printf/fprintfusa una máscara literal constante?¿Los textos de entrada se insertan con
%s, no como formato?¿Compilé con
-Wformat -Wformat-security?¿Evité
%nen cualquier cadena que pueda venir del usuario?
Reglas relacionadas¶
0x4008h: Validación obligatoria del valor de retorno de fclose() en modo escritura — regla asociada sobre la E/S con formato y sus retornos.
0x4002h: Validá los retornos de las operaciones de lectura y escritura de archivos — el retorno de las funciones de E/S también se verifica.
0x5004h: Todas las operaciones con cadenas deben ser seguras — operaciones con cadenas seguras y acotadas.
0x0001h: La claridad y prolijidad son de máxima importancia — la claridad y la seguridad como criterio rector.