Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
C++: Handle size arguments in 'getOutputArgument'.
  • Loading branch information
MathiasVP committed May 15, 2026
commit fe678a9766e7dc86fef7aeca413337d4b1e45ec0
47 changes: 45 additions & 2 deletions cpp/ql/lib/semmle/code/cpp/commons/Scanf.qll
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ abstract class ScanfFunction extends Function {
* (rather than a `char*`).
*/
predicate isWideCharDefault() { exists(this.getName().indexOf("wscanf")) }

/** Holds if this is one of the `scanf_s` variants. */
predicate isSVariant() {
exists(string name | name = this.getName() |
name.matches("%\\_s")
or
name.matches("%\\_s\\_l")
)
}
}

/**
Expand Down Expand Up @@ -103,6 +112,14 @@ class Snscanf extends ScanfFunction instanceof TopLevelFunction {
int getInputLengthParameterIndex() { result = 1 }
}

private predicate isCharLike(Type t) { t instanceof CharType or t instanceof Wchar_t }

private predicate isStringLike(Type t) {
isCharLike(t.(PointerType).getBaseType())
or
isCharLike(t.(ArrayType).getBaseType())
}

/**
* A call to one of the `scanf` functions.
*/
Expand Down Expand Up @@ -136,14 +153,40 @@ class ScanfFunctionCall extends FunctionCall {
*/
predicate isWideCharDefault() { this.getScanfFunction().isWideCharDefault() }

bindingset[this, k]
pragma[inline_late]
private predicate isSizeArgument(int k) {
// The first vararg is never the size argument since a size argument must
// always follow a string buffer argument.
k > 0 and
isStringLike(this.getArgument(this.getScanfFunction().getNumberOfParameters() + k - 1)
.getUnspecifiedType())
}

/**
* Gets the output argument at position `n` in the vararg list of this call.
*
* The range of `n` is from `0` to `this.getNumberOfOutputArguments() - 1`.
*/
Expr getOutputArgument(int n) {
result = this.getArgument(this.getTarget().getNumberOfParameters() + n) and
n >= 0
exists(ScanfFunction target | target = this.getScanfFunction() |
// If this is an S variant then every string buffer argument has a
// corresponding size argument immediately following it, so we need to
// skip over those size arguments when counting the output arguments.
if target.isSVariant()
then
result =
rank[n + 1](Expr arg, int k |
k >= 0 and
arg = this.getArgument(target.getNumberOfParameters() + k) and
not this.isSizeArgument(k)
|
arg order by k
)
else (
n >= 0 and result = this.getArgument(target.getNumberOfParameters() + n)
)
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
| test.c:22:2:22:8 | call to swscanf | test.c:22:29:22:35 | wbuffer | 0 |
| test.c:23:2:23:8 | call to scanf_s | test.c:23:22:23:23 | & ... | 0 |
| test.c:23:2:23:8 | call to scanf_s | test.c:23:26:23:31 | buffer | 1 |
| test.c:23:2:23:8 | call to scanf_s | test.c:23:34:23:35 | 10 | 2 |
| test.c:23:2:23:8 | call to scanf_s | test.c:23:38:23:40 | & ... | 3 |
| test.c:23:2:23:8 | call to scanf_s | test.c:23:38:23:40 | & ... | 2 |