A few key points (also mentioned in the README): The "%n" specifier makes printf() fetch an int* argument and write the number of characters written so far to the target location. The funky stuff happens when you abuse that to overwrite the format string itself, actually making printf() Turing-complete.
This also means that if you lazily stuff a user controller string into a printf() style function (instead of doing e.g. `printf("%s", str)`), this can be used as a gadget for arbitrary memory writes (in addition to arbitrary stack reads). For this reason, there are compiler warnings if you use a format string that isn't a string literal (IIRC now on by default in GCC 11) and some C libraries like dietlibc refuse to implement %n.
> The %n conversion specifier has serious security implications, so it was changed to no longer store the number of bytes written so far into the variable indicated by the pointer argument. Instead a syslog(3) message will be generated, after which the program is aborted with SIGABRT.
I think it's not that serious really. Using the wrong format string already has undefined behaviour so you should always hard code it as part of the logic of your program, even if %n didn't exist.
But usually using the wrong format string would just read from undefined memory locations. While that could be bad, it’s not as obviously catastrophic as writing to arbitrary memory locations.
I had my language lawyer hat on. Technically undefined behaviour means literally any behaviour could happen, so in principle you could have a printf that wrote to random memory locations when you use a bad format string (even without %d) and it would be a conforming implementation.
But looking at it now from a more practical point of view, I guess I can't see how that would happen, even with aggressive compiler optimisations (which are the usual thing to trigger really surprising consequences of undefined behaviours).
At school, out of boredom, I once coded TicTacToe in Lotus 1-2-3 (nope it was MS Multiplan for cost reasons but anyway). With "Insert coin" TUI animation and all ...
I agree the "single call to printf" sounds wrong when the loop makes repeated calls to printf, but then I realize they mean there's a single place in the source code where printf is called...
This also means that if you lazily stuff a user controller string into a printf() style function (instead of doing e.g. `printf("%s", str)`), this can be used as a gadget for arbitrary memory writes (in addition to arbitrary stack reads). For this reason, there are compiler warnings if you use a format string that isn't a string literal (IIRC now on by default in GCC 11) and some C libraries like dietlibc refuse to implement %n.