I don't really get, what you are trying to accomplish here, but the vulnerability comes from letting the userhacker supply a format string. He can then supply %n which causes the printf-function-family to write to memory (the number of bytes written so far).

int answer = 42;
printf("Hello %nworld\n", &answer);
// "Hello " is 6 bytes long
printf(
    "The answer to the ultimate question of life, "
    "the universe and everything is: %i\n"
    , answer
);

Will print 6 as answer, instead of 42.

Answer from Bodo Thiesen on Stack Overflow
🌐
Invicti
invicti.com › blog › web-security › format-string-vulnerabilities
What Are Format String Vulnerabilities?
However, if used incorrectly, printf() format strings can be vulnerable to a variety of attacks. In fact, printf() is just one of a whole family of format functions that also includes fprintf(), sprintf(), snprintf(), vsprintf(), vprintf(), ...
🌐
Buffer Overflows
bufferoverflows.net › home › format string vulnerability: what, when and how?
Format String Vulnerability: What, When and How? | Buffer Overflows
May 9, 2019 - Format strings vulnerability exists in most of the printf family functions like: printf · fprintf · vsprintf · vsnprintf · sprintf · snprintf · vfprintf · vprintf ·
🌐
Exploit-DB
exploit-db.com › papers › 13180
Exploiting non-classical Format String Vulnerability
May 30, 2006 - In this tool exist format string vulnerability in syslog() function. This bug was founded by my nice friend - CoKi from No System Group. He already exploited it with Pascal's method. Link to his exploit you can see at the end of article. Let's explore his bug... Vulnerability exist in src/log.c: void tipxd_log(int priority, char *format, ... ) { va_list ap; char log_entry[LOG_ENTRY_SIZE]; va_start(ap,format); vsnprintf(log_entry,LOG_ENTRY_SIZE-1,format,ap); if (sysinfo.opt_flags & OPT_STDERR) { fprintf(stderr,"[TIPXD LOG] %s\n",log_entry); } else { syslog(priority,log_entry); <------ format string bug } return; } Ok, let's searching where this function are using...
🌐
Ttu
discl.cs.ttu.edu › cybersecurity › doku.php
vulnerability_case_study:format_string_vulnerabilities - Summer Workshop on Cyber Security
July 3, 2014 - This functionality eventually calls vsnprintfO and passes the buffer created from the setproctitle0 as the format string. The developers didn't use a format specifier when they used vsnprintf(). The initial advisory for this vulnerability was reported in August 2000 — four months prior to ...
🌐
Medium
infosecwriteups.com › exploiting-format-string-vulnerability-97e3d588da1b
EXPLOITING FORMAT STRING VULNERABILITY | by AidenPearce369 | InfoSec Write-ups
January 18, 2024 - By Format String vulnerability, an attacker can execute code, read the stack values, or cause a segmentation fault in the application · %c — Formats a single character · %d — Formats an integer in decimal value · %f — Formats float ...
🌐
Exploit-DB
exploit-db.com › docs › english › 28476-linux-format-string-exploitation.pdf pdf
Format String Exploitation-Tutorial By Saif El-Sherei www.elsherei.com
Format strings vulnerability exists in most of the printf family below is some. Printf · vsprintf · Fprintf · vsnprintf · Sprint · vfprintf · Snprintf · vprintf ·
🌐
OWASP Foundation
owasp.org › www-community › attacks › Format_string_attack
Format string attack | OWASP Foundation
The printf in the first line will not interpret the “%s%s%s%s%s%s” in the input string, and the output will be: “Hello World %s%s%s%s%s%s” · The line printf(argv[1]); in the example is vulnerable, if you compile the program and run it:
Find elsewhere
🌐
Hacking Lab
hackinglab.cz › en › blog › format-string-vulnerability
Format String Vulnerability - Hacking Lab
October 21, 2024 - The problem occurs when there are multiple formatting parameters in the format string. The macro va_arg does not recognize that it has already passed all arguments, so it continues reading data from the stack and moves the pointer further [3], ...
🌐
Stanford
cs155.stanford.edu › papers › formatstring-1.2.pdf pdf
Exploiting Format String Vulnerabilities scut / team teso September 1, 2001
September 1, 2001 - • vsnprintf — prints to a string with length checking from a va_arg · structure · Relatives: • setproctitle — set argv[] • syslog — output to the syslog facility · • others like err*, verr*, warn*, vwarn* 6 · 2 · THE FORMAT FUNCTIONS · 2.3 · Use of format functions · To understand where this vulnerability is common in C code, we have to ·
🌐
Synacktiv
synacktiv.com › en › publications › exploiting-a-blind-format-string-vulnerability-in-modern-binaries-a-case-study-from
Exploiting a Blind Format String Vulnerability in Modern Binaries: A
The print_debug_msg function allows an attacker to control the format string passed to vsnprintf, leading to potential arbitrary memory writes. This blog post outlines our successful exploitation of this format string vulnerability, employing ...
🌐
MIT CSAIL
people.csail.mit.edu › alinush › cse409-fall-2011 › 07-format-string-attacks.pdf pdf
Format string attacks
September 26, 2011 - those arguments to be, just above the fmt string, at location 0x1111 EC0D, which happens to be the address of x. ... When snprintf executes, it will store 0x1111 EC0D in the first 4 bytes of buff (note this is the address of x). ... Then, the �d specifier will print (with 96 space-padding) the first format string argument, which according to ARGP
🌐
Ian
ian.nl › blog › format-string-vulnerability-analysis
Format String Vulnerability: Reading Memory Without Buffers | Ian | Hacking
September 4, 2025 - vsnprintf(buffer, size, user_input, args) - variadic version of snprintf · syslog(priority, user_input) - system logging · err(eval, user_input) - error reporting · errx(eval, user_input) - error reporting without errno · warn(user_input) - warning messages · warnx(user_input) - warning messages without errno · Always use a format specifier when printing user-controlled data: // Vulnerable printf(name); // Safe printf("%s", name); Now that you understand the basics of format string vulnerabilities, explore these advanced techniques: Memory Writing Techniques: Format String GOT Overwrite Attack - Use %n to overwrite the Global Offset Table and hijack function calls ·
🌐
Sourcery
sourcery.ai › vulnerabilities › c-lang-security-insecure-use-printf-fn
Injection from user-controlled format string in printf-family functions | Security Vulnerability Database | Sourcery
Replace sprintf and vsprintf with ... and vsnprintf that include buffer size limits to prevent overflows. ... Validate and sanitize user input to remove or escape format specifiers before using it in any string formatting operations. ... A critical memory safety vulnerability where C code ...
🌐
Developer Community
developercommunity.visualstudio.com › content › problem › 427602 › vsnprintf-functionality-missing-in-more-secure-ver.html
vsnprintf functionality missing in more secure versions
Skip to main content · Visual Studio · Guidelines Problems Suggestions Code of Conduct · Downloads · Visual Studio IDE Visual Studio Code Azure DevOps Team Foundation Server Accounts and Subscriptions · Subscriber Access · Microsoft Security Azure Dynamics 365 Microsoft 365 Microsoft ...
🌐
University of Washington
homes.cs.washington.edu › ~djg › papers › format_string.pdf pdf
Preventing Format-String Attacks via Automatic and Efficient Dynamic Checking
The essence of the vulnerability is straightforward: • User-supplied input is frequently used as the format- string argument to a printing function (such as sprintf, snprintf, fprintf, vprintf, vsprintf, vsnprintf, vfprintf, syslog, or vsyslog). • The format-string argument to printing ...
Top answer
1 of 2
2

Pre-C99 affords no simply solution to format strings with a high degree of safety of preventing buffer overruns.

It is those pesky "%s", "%[]", "%f" format specifiers that require so much careful consideration with their potential long output. Thus the need for such a function. @Jonathan Leffler

To do so with those early compilers obliges code to analyze format and the arguments to find the required size. At that point, code is nearly there to making you own complete my_vsnprintf(). I'd seek existing solutions for that. @user694733.


Even with C99, there are environmental limits for *printf().

The number of characters that can be produced by any single conversion shall be at least 4095. C11dr §7.21.6.1 15

So any code that tries to char buf[10000]; snprintf(buf, sizeof buf, "%s", long_string); risks problems even with a sufficient buf[] yet with strlen(long_string) > 4095.

This implies that a quick and dirty code could count the % and the format length and make the reasonable assumption that the size needed does not exceed:

size_t sz = 4095*percent_count + strlen(format) + 1;

Of course further analysis of the specifiers could lead to a more conservative sz. Continuing down this path we end at writing our own my_vsnprintf().


Even with your own my_vsnprintf() the safety is only so good. There is no run-time check that the format (which may be dynamic) matches the following arguments. To do so requires a new approach.

Cheeky self advertisement for a C99 solution to insure matching specifiers and arguments: Formatted print without the need to specify type matching specifiers using _Generic.

2 of 2
2

Transferring comments to answer.

The main reason vsnprintf() was added to C99 was that it is hard to protect vsprintf() or similar. One workaround is to open /dev/null, use vfprintf() to format the data to it, note how big a result was needed, and then decide whether it is safe to proceed. Icky, especially if you open the device on each call.

That means your code might become:

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

extern char *formatString(const char *format, ...);

char *formatString(const char *format, ...)
{
    static FILE *fp_null = NULL;

    if (fp_null == NULL)
    {
        fp_null = fopen("/dev/null", "w");
        if (fp_null == NULL)
            return NULL;
    }

    va_list ap;

    va_start(ap, format);
    int size = vfprintf(fp_null, format, ap);
    va_end(ap);

    if (size < 0)
        return NULL;

    char *result = (char *) malloc(size + 1);
    if (result == NULL)
        return NULL;

    va_start(ap, format);
    int check = vsprintf(result, format, ap);
    va_end(ap);

    assert(check == size);

    return result;
}

int main(void)
{
    char *r1 = formatString("%d Dancing Pigs = %4.2f%% of annual GDP (grandiose dancing pigs!)\n",
                            34241562, 21.2963);
    char *r2 = formatString("%s [%-13.10s] %s is %d%% %s\n", "Peripheral",
                            "sub-atomic hyperdrive", "status", 99, "of normality");

    if (r1 != NULL)
        printf("r1 = %s", r1);

    if (r2 != NULL)
        printf("r2 = %s", r2);

    free(r1);
    free(r2);
    return 0;
}

As written with fp_null a static variable inside the function, the file stream cannot be closed. If that's a bother, make it a variable inside the file and provide a function to if (fp_null != NULL) { fclose(fp_null); fp_null = NULL; }.

I'm unapologetically assuming a Unix-like environment with /dev/null; you can translate that to NUL: if you're working on Windows.

Note that the original code in the question did not use va_start() and va_end() twice (unlike this code); that would lead to disaster. In my opinion, it is a good idea to put the va_end() as soon after the va_start() as possible — as shown in this code. Clearly, if your function is itself stepping through the va_list, then there will be a bigger gap than shown here, but when you're simply relaying the variable arguments to another function as here, there should be just the one line in between.

The code compiles cleanly on a Mac running macOS 10.14 Mojave using GCC 8.2.0 (compiled on macOS 10.13 High Sierra) with the command line:

$ gcc -O3 -g -std=c90 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes vsnp37.c -o vsnp37
$

When run, it produces:

r1 = 34241562 Dancing Pigs = 21.30% of annual GDP (grandiose dancing pigs!)
r2 = Peripheral [sub-atomic   ] status is 99% of normality