cybersecurity · intermediate · ~15 min · safe pentest lab

Count SQL Parameter Placeholders

Understand why correct placeholder counting matters for safe parameter binding, and practice a bounds-safe single-pass scanner that tracks whether the cursor is inside a single-quoted SQL string literal (including escaped '' quotes).

Challenge

Background

Prepared statements defend against SQL injection by sending the query text and the parameter values to the database on separate channels. Each user-supplied value is represented in the SQL text by a ? placeholder, and the driver binds exactly one value per placeholder. If your binding code miscounts the placeholders, it will bind the wrong value to the wrong slot — or leave a slot unbound — which silently breaks the parameterization that was supposed to keep attacker input out of the query structure.

The insecure assumption

A naive counter treats every ? byte as a placeholder. That is wrong: a ? can legitimately appear inside a single-quoted '...' string literal (for example WHERE label = 'huh?'), where it is just data, not a bind slot. Counting those inflates the placeholder count and desynchronizes value binding — the kind of off-by-one that turns a safe query into an injectable one.

Task

Implement int count_placeholders(const char *query). Return the number of ? characters that act as real parameter placeholders — that is, every ? that is not inside a single-quoted string literal. Walk the fixed, NUL-terminated query buffer byte by byte and stay in bounds; a NULL query is not a valid input and must be rejected by returning 0.

Edge cases

  • A '' (two adjacent single quotes) inside a string is an escaped quote, not the end of the literal.
  • An unterminated '...' literal means every following ? is still inside the string and must not be counted.
  • The empty string and a NULL pointer both yield 0.

Example

count_placeholders(mixed) -> 1   /* 'a?b' literal ignored, one real ? counted */
count_placeholders(none)  -> 0

Input format

A single NUL-terminated C string query holding SQL text. It may be empty or NULL, and may contain single-quoted '...' string literals (with '' used as an escaped quote inside them).

Output format

An int: the count of '?' characters that are real bind placeholders (those NOT inside a single-quoted literal). Returns 0 for NULL or empty input.

Constraints

Read only the fixed NUL-terminated buffer; never index past the terminator. Treat NULL as invalid and return 0 (deny by default). The '?' inside single-quoted literals must be excluded; '' inside a literal is an escaped quote, not a terminator. No dynamic allocation is required.

Starter code

int count_placeholders(const char *query)
{
    /* TODO: count '?' placeholders, but skip any '?' that is inside a
       single-quoted '...' string literal. Reject NULL. Stay in bounds.
       This stub is intentionally wrong and must be replaced. */
    (void)query;
    return -1;
}

Common mistakes

Counting every '?' byte regardless of context; forgetting to reset the in-string flag on the closing quote; treating '' (escaped quote) as opening/closing and desynchronizing the state; dereferencing a NULL query; reading query[i+1] past the terminator (safe here because the NUL terminator is a valid, readable byte that is never ''').

Edge cases to handle

NULL query returns 0; empty string returns 0; a string literal containing only '?' characters counts 0; an escaped '' inside a literal keeps the scanner in-string; an unterminated literal swallows all trailing '?'; a query that is entirely '?' bytes counts each one.

Background lessons

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