Exercise: snprintf overflow

CodeQL for C/C++


For this example you should download:


For this example, we will be analyzing rsyslog.

You can also query the project in the query console on LGTM.com.

You can download the database as a zip file by clicking the link on the slide above. To use the database in CodeQL for Visual Studio Code:

  1. Unzip the file
  2. Add the unzipped database to Visual Studio Code
  3. Upgrade the database if necessary

For further information, see Using the extension in the CodeQL for Visual Studio Code help.

Note that results generated in the query console are likely to differ to those generated in CodeQL for Visual Studio Code as LGTM.com analyzes the most recent revisions of each project that has been added–the CodeQL database available to download above is based on an historical version of the codebase.


  • printf: Returns number of characters printed.

    printf("Hello %s!", name)
  • sprintf: Returns number of characters written to buf.

    sprintf(buf, "Hello %s!", name)
  • snprintf: Returns number of characters it would have written to buf had n been sufficiently large, not the number of characters actually written.

    snprintf(buf, n, "Hello %s!", name)
  • In pre-C99 versions of glibc snprintf would return -1 if n was too small!

RCE in rsyslog

Finding the RCE yourself

  1. Write a query to find calls to snprintf

    Hint: Use class FunctionCall

  2. Restrict to calls whose result is used

    Hint: Use class ExprInVoidContext

  3. Restrict to calls where the format string contains “%s”

    Hint: Use predicates Expr.getValue and string.regexpMatch

  4. Restrict to calls where the result flows back to the size argument

    Hint: Import library semmle.code.cpp.dataflow.TaintTracking and use predicate TaintTracking::localTaint

Model answer

import cpp
import semmle.code.cpp.dataflow.TaintTracking

from FunctionCall call, DataFlow::Node source, DataFlow::Node sink
  call.getTarget().getName() = "snprintf" and
  call.getArgument(2).getValue().regexpMatch("(?s).*%s.*") and
  TaintTracking::localTaint(source, sink) and
  source.asExpr() = call and
  sink.asExpr() = call.getArgument(1)
select call


The regular expression for matching the format string uses the “(?s)” directive to ensure that “.” also matches any newline characters embedded in the string.