cybersecurity · intermediate · ~20 min

Detect format-string injection in user input

Implement a tolerant scanner that recognises real printf specifiers and ignores `%%`.

Challenge

Flag a string that contains a real printf format specifier — the pre-check that stops a user-controlled value from ever reaching printf(user_input).

Task

Implement int has_format_specifier(const char *s) that returns 1 if s contains a sequence that looks like a printf conversion specifier, else 0.

A specifier is a % followed (after optional flags -+ 0#, optional width digits, optional .precision, optional length modifier l/ll/h/z) by one of:

d i u x X o c s p n f e g a A

An escaped percent %% does NOT count, and a bare % with no following conversion letter does NOT count.

Input

  • s: a NUL-terminated string the grader passes.

Output

Returns int: 1 if a real specifier is present, else 0.

Example

has_format_specifier("hello %s")    ->   1
has_format_specifier("100%%")       ->   0   (escaped)
has_format_specifier("rate=99%")    ->   0   (% at end, no letter)
has_format_specifier("count: %5d")  ->   1
has_format_specifier("%n is bad")   ->   1   (%n can write to memory)
has_format_specifier("plain text")  ->   0

Edge cases

  • %% is an escape and does not count; %%%s still counts (the real %s follows).
  • A % at end of string with no conversion letter does not count.
  • Width, precision, and length modifiers (%5d, %.2f, %lld) must be skipped before the conversion letter.

Rules

  • Detection only. The real fix for format-string bugs is to always call printf("%s", user_input), never printf(user_input).

Why this matters

printf(user_string) is one of the oldest and most-exploited bugs in C. The fix is to refuse user-supplied format strings before they reach printf. This exercise builds a lightweight pre-check that flags any % that isn't escaped or in a known-safe position.

Input format

A NUL-terminated string s.

Output format

An int: 1 if s contains a real printf specifier, else 0.

Constraints

O(strlen); %% does not count; a bare trailing % does not count.

Starter code

int has_format_specifier(const char *s) { /* TODO */ return 0; }

Common mistakes

Returning 1 for any % (false positives on 99%). Forgetting %%. Ignoring width/precision/length modifiers.

Edge cases to handle

%% (escape); % at end; %5d; %.2f; %n (especially dangerous).

Complexity

O(strlen).

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.