When `printf(user_input)` is called without a format argument, the user controls the format string. `%x` reads the next value off the stack (leaking addresses or canaries). `%n` writes the number of characters printed so far to the next pointer argument. An attacker who controls the format string can read stack contents with a chain of `%x` and overwrite arbitrary memory with `%n`. The fix is always `printf("%s", user_input)`: never pass untrusted input as the first argument.
/* Vulnerable: user controls the format specifiers */
char buf[128];
fgets(buf, sizeof(buf), stdin);
printf(buf); /* format string vulnerability */
/* Safe: format is a string literal under programmer control */
printf("%s", buf); /* user input treated as pure data */
/* Enable the compiler warning to catch this at build time */
/* gcc -Wformat-security -Werror=format-security */Enable -Wformat-security: GCC will warn on printf(var) even without -Wall
GCC FORTIFY_SOURCE=2 adds runtime checks that catch some format string misuse, but it is not a substitute for correct code. The fix is always the same: never pass user-controlled data as a format string.