format-string · diff
git:20260914.1ef9fbc to git:20261016.df7fb32
3 added, 0 removed. Audit A to A.
---
name: format-string
description: Classify format string vulnerability evidence — printf-family called with non-literal format argument. Maps to CWE-134.
license: MIT
compatibility: opencode,claude code,DSH
metadata:
cwe: CWE-134
severity: HIGH
domain: security
---
## Format String Vulnerability Analysis (CWE-134)
### Evidence Pattern
A format-string candidate has:
- **format_string**: A printf-family function is called where the format argument is not a string literal
+ - **api**: the format function called
+ - **format_arg**: the non-literal format argument expression
+ - **taint_source**: where the user-controlled value behind the format argument originates (when the taint filter proved one)
- **call_path**: The function is reachable from an entry point
### Functions Checked
`printf`, `fprintf`, `sprintf`, `snprintf`, `vprintf`, `vfprintf`, `vsprintf`, `vsnprintf`, `syslog`, `err`, `warn`, `errx`, `warnx`
### Detection Logic
1. Find all calls to printf-family functions
2. Extract the format argument (first argument for printf/sprintf, second for fprintf)
3. If the format argument is NOT a string literal (doesn't start with `"` or `L"`), emit FORMAT_STRING event
4. Skip calls that use `sizeof` in the expression (likely safe bounded writes)
### Classification Rules
| Condition | Classification |
|-----------|---------------|
| printf(user_input) — format is external input | **confirmed** |
| printf(log_msg) — format is a variable | **dismissed** (may be safe if controlled) |
| printf("%s", user_input) — format is literal | **false-positive** (safe) |
| snprintf(buf, sizeof(buf), fmt, ...) — bounded with sizeof | **false-positive** (safe) |
### Common False Positives
- `printf("%s\n", msg)` — format is a string literal (safe)
- `snprintf(buf, sizeof(buf), "%d", val)` — bounded write with literal format (safe)
- `printf(const_format_string)` — format is a compile-time constant (safe, but detector can't verify)
### Fix Suggestions
- Always use string literal as format: `printf("%s", user_input)` instead of `printf(user_input)`
- Use `fputs()` instead of `printf()` when no formatting is needed
- Use `snprintf()` with bounded buffer size to prevent overflow
- Never pass user-controlled data as the format argument
### Severity Matrix
`status` and `severity` are independent: `status` = evidence verdict
(confirmed/dismissed), `severity` = impact (low/medium/high/critical).
`metadata.severity` is the type default, not a ceiling. The verdict is binary (confirmed/dismissed); dismissed → `low`.
| Shape | Severity |
|-------|----------|
| `printf`/`sprintf` with user-controlled format | CRITICAL (arbitrary read/write) |
| Non-literal format variable, source unknown | HIGH (dismissed) |
| `syslog`/`err`/`warn` with non-literal format | HIGH (stack data leak) |