A format string vulnerability is one of those bugs that looks completely harmless: you log a value, or build a string, and pass a variable straight into a formatting function. The problem is that formatting functions interpret special specifiers like %x, %@, or %s in their format string, so if that format string is something an attacker controls, they can make the function read memory it should not, leak data, crash the app, or worse. The fix is almost trivial once you see it: never let external input be the format string itself. Here is what format string vulnerabilities are, how they show up across mobile platforms, and how to avoid them.
Short answer
A format string vulnerability happens when externally controlled input is passed as the format string argument to a formatting function, such as the printf family in C, NSLog or stringWithFormat: in Objective-C, or String(format:) in Swift. Per CWE-134, an attacker who controls the format string can include format specifiers to read process memory, leak sensitive data, or crash the app, and in C the %n specifier can write memory, potentially leading to code execution. The fix is to never use external input as the format string: always pass a fixed, literal format string and supply the input as an argument, for example printf("%s", input) rather than printf(input). Treat the format string as code, not data.
What you should know
- Formatting functions interpret specifiers:
%x,%@,%s,%n, and others. - The bug is input as the format string: not as an argument.
- The impact is memory read, leak, or crash: and in C, memory write.
- It spans platforms: C, Objective-C, and Swift formatting APIs.
- The fix is a literal format string: pass input as an argument instead.
What is a format string vulnerability?
It is passing attacker-controlled data where a function expects a format string. Formatting functions take a format string containing specifiers, like %d or %s, that tell the function how to interpret and substitute the following arguments. The functions trust that format string to describe the arguments, so when the format string itself is something an external party controls, the attacker can put specifiers in it that the function then acts on, reading values off the stack or from memory that were never intended as arguments. So a call that passes a user-supplied string directly as the format, rather than as a value to be formatted, hands the attacker control over how the function reads memory. The danger is the conflation of data and format: the input should be data the function prints, but by being the format string it becomes instructions the function follows. That is why a line as innocuous-looking as logging a variable can be a real vulnerability if the variable is the format argument.
How does it manifest across platforms?
The shape is the same, the severity varies by language. The table summarizes.
| Platform | Vulnerable pattern | Impact |
|---|---|---|
| C / NDK | printf(input) and similar | Memory read and, via %n, write |
| Objective-C | NSLog(input), stringWithFormat:input | Memory read, leak, crash |
| Swift | String(format: input) | Reading unintended memory, crash |
| Java/Kotlin | String.format(input) | Exception or unintended output |
In C and native NDK code, format string bugs are the most severe, because the %n specifier can write to memory, which historically has led to crashes and even code execution, on top of the memory-reading that other specifiers allow. In Objective-C, passing input to NSLog or stringWithFormat: as the format lets an attacker read memory, leak data, or crash the app. In Swift, the format-string APIs carry the same risk if fed attacker input, though Swift's normal string interpolation is not a format string and is safe. In Java and Kotlin, String.format with attacker-controlled input is less catastrophic, there is no %n write, but it can throw or produce unintended output. The common thread across all of them is the same mistake, input used as the format string.
How do you prevent it?
Always use a constant format string and pass input as an argument. The single rule that eliminates the entire class is to never pass externally controlled data as the format string: write printf("%s", input), NSLog(@"%@", input), or the equivalent, with a literal format string and the input as a value, rather than passing the input itself as the format. This applies everywhere you format or log, including debug logging, which is a common place the mistake hides because it feels low-stakes. In Swift, prefer normal string interpolation, which is not a format string, and only use the format-string APIs with literal formats. Treat any formatting call where the format argument is a variable as suspect, and check that the variable is a trusted constant, not external input. Where you use static analysis or compiler warnings, enable the ones that flag non-literal format strings, since they catch this automatically. The principle is to keep the format string under your control as a literal and let external input only ever be an argument, so it is treated as data to print, never as instructions to follow.
What to watch out for
The first trap is logging input directly, like NSLog(userValue) or printf(userValue), which feels harmless but is the vulnerability; use a literal format and pass the value as an argument. The second is assuming it only matters in C, when Objective-C and Swift formatting APIs are affected too. The third is overlooking debug or error logging, where untrusted values often flow into format calls. Format string bugs are code-level, so a pre-submission scan such as PTKD.com (https://ptkd.com), which reads the binary against OWASP MASVS, assesses input handling broadly, while enabling compiler and static-analysis warnings for non-literal format strings is the most direct way to catch this in your own code.
What to take away
- A format string vulnerability occurs when externally controlled input is passed as the format string to a formatting function, letting an attacker read memory, leak data, crash the app, or in C write memory.
- It manifests across C, Objective-C, and Swift formatting APIs, most severely in native C code where
%nenables memory writes. - Prevent it with one rule: always use a literal format string and pass input as an argument, never as the format itself, including in debug logging.
- Enable compiler and static-analysis warnings for non-literal format strings, and use a pre-submission scan such as PTKD.com to assess your app's input handling.


