Exercise: snprintf overflow

QL for C/C++

Setup

For this example you should download:

Note

For this example, we will be analyzing rsyslog.

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

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

snprintf

  • 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
where
  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

Note

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