Skip to content

Commit 3cdeb38

Browse files
authored
Merge pull request #305 from microsoft/brodes/sizeof_query_fp_fixes
Fix FPs in sizeof queries
2 parents eba5208 + c6b48c3 commit 3cdeb38

File tree

9 files changed

+137
-538
lines changed

9 files changed

+137
-538
lines changed

cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.ql

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,7 @@
1111
import cpp
1212
import SizeOfTypeUtils
1313

14-
/**
15-
* Windows SDK corecrt_math.h defines a macro _CLASS_ARG that
16-
* intentionally misuses sizeof to determine the size of a floating point type.
17-
* Explicitly ignoring any hit in this macro.
18-
*/
19-
predicate isPartOfCrtFloatingPointMacroExpansion(Expr e) {
20-
exists(MacroInvocation mi |
21-
mi.getMacroName() = "_CLASS_ARG" and
22-
mi.getMacro().getFile().getBaseName() = "corecrt_math.h" and
23-
mi.getAnExpandedElement() = e
24-
)
25-
}
26-
27-
/**
28-
* Determines if the sizeOfExpr is ignorable.
29-
*/
30-
predicate ignorableSizeof(SizeofExprOperator sizeofExpr) {
31-
// a common pattern found is to sizeof a binary operation to check a type
32-
// to then perfomr an onperaiton for a 32 or 64 bit type.
33-
// these cases often look like sizeof(x) >=4
34-
// more generally we see binary operations frequently used in different type
35-
// checks, where the sizeof is part of some comparison operation of a switch statement guard.
36-
// sizeof as an argument is also similarly used, but seemingly less frequently.
37-
exists(ComparisonOperation comp | comp.getAnOperand() = sizeofExpr)
38-
or
39-
exists(ConditionalStmt s | s.getControllingExpr() = sizeofExpr)
40-
or
41-
// another common practice is to use bit-wise operations in sizeof to allow the compiler to
42-
// 'pack' the size appropriate but get the size of the result out of a sizeof operation.
43-
sizeofExpr.getExprOperand() instanceof BinaryBitwiseOperation
44-
}
45-
46-
from SizeofExprOperator sizeofExpr, string message, Expr op
14+
from CandidateSizeofCall sizeofExpr, string message, Expr op
4715
where
4816
exists(string tmpMsg |
4917
(
@@ -55,8 +23,6 @@ where
5523
then message = tmpMsg + "(in a macro expansion)"
5624
else message = tmpMsg
5725
) and
58-
op = sizeofExpr.getExprOperand() and
59-
not isPartOfCrtFloatingPointMacroExpansion(op) and
60-
not ignorableSizeof(sizeofExpr)
26+
op = sizeofExpr.getExprOperand()
6127
select sizeofExpr, "$@: $@ of $@ inside sizeof.", sizeofExpr, message,
6228
sizeofExpr.getEnclosingFunction(), "Usage", op, message

cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.ql

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,44 +11,67 @@
1111
import cpp
1212
import SizeOfTypeUtils
1313

14-
predicate isExprAConstInteger(Expr e, MacroInvocation mi) {
14+
/**
15+
* Holds if `type` is a `Type` that typically should not be used for `sizeof` in macros or function return values.
16+
*/
17+
predicate isTypeDangerousForSizeof(Expr e) {
1518
exists(Type type |
16-
type = e.getExplicitlyConverted().getType() and
17-
isTypeDangerousForSizeof(type) and
18-
// Special case for wide-char literals when the compiler doesn't recognize wchar_t (i.e. L'\\', L'\0')
19-
// Accounting for parenthesis "()" around the value
20-
not exists(Macro m | m = mi.getMacro() |
21-
m.getBody().toString().regexpMatch("^[\\s(]*L'.+'[\\s)]*$")
22-
) and
23-
// Special case for token pasting operator
24-
not exists(Macro m | m = mi.getMacro() | m.getBody().toString().regexpMatch("^.*\\s*##\\s*.*$")) and
25-
// Special case for multichar literal integers that are exactly 4 character long (i.e. 'val1')
26-
not exists(Macro m | m = mi.getMacro() |
27-
e.getType().toString() = "int" and
28-
m.getBody().toString().regexpMatch("^'.{4}'$")
29-
) and
30-
e.isConstant()
19+
(
20+
if e.getImplicitlyConverted().hasExplicitConversion()
21+
then type = e.getExplicitlyConverted().getType()
22+
else type = e.getUnspecifiedType()
23+
)
24+
|
25+
type instanceof IntegralOrEnumType and
26+
// ignore string literals
27+
not type instanceof WideCharType and
28+
not type instanceof CharType
3129
)
3230
}
3331

3432
int countMacros(Expr e) { result = count(MacroInvocation mi | mi.getExpr() = e | mi) }
3533

3634
predicate isSizeOfExprOperandMacroInvocationAConstInteger(
37-
SizeofExprOperator sizeofExpr, MacroInvocation mi
35+
CandidateSizeofCall sizeofExpr, MacroInvocation mi, Literal l
3836
) {
39-
exists(Expr e |
40-
e = mi.getExpr() and
41-
e = sizeofExpr.getExprOperand() and
42-
isExprAConstInteger(e, mi) and
43-
// Special case for FPs that involve an inner macro that resolves to 0 such as _T('\0')
44-
not exists(int macroCount | macroCount = countMacros(e) |
45-
macroCount > 1 and e.(Literal).getValue().toInt() = 0
46-
)
37+
isTypeDangerousForSizeof(sizeofExpr.getExprOperand()) and
38+
l = mi.getExpr() and
39+
l = sizeofExpr.getExprOperand() and
40+
mi.getExpr() = l and
41+
// Special case for FPs that involve an inner macro that resolves to 0 such as _T('\0')
42+
// i.e., if a macro resolves to 0, the same 0 expression cannot be the macro
43+
// resolution of another macro invocation (a nested invocation).
44+
// Count the number of invocations resolving to the same literal, if >1, ignore.
45+
not exists(int macroCount | macroCount = countMacros(l) |
46+
macroCount > 1 and l.getValue().toInt() = 0
47+
) and
48+
// Special case for wide-char literals when the compiler doesn't recognize wchar_t (i.e. L'\\', L'\0')
49+
// Accounting for parenthesis "()" around the value
50+
not exists(Macro m | m = mi.getMacro() |
51+
m.getBody().toString().regexpMatch("^[\\s(]*L'.+'[\\s)]*$")
52+
) and
53+
// Special case for token pasting operator
54+
not exists(Macro m | m = mi.getMacro() | m.getBody().toString().regexpMatch("^.*\\s*##\\s*.*$")) and
55+
// Special case for multichar literal integers that are exactly 4 character long (i.e. 'val1')
56+
// in these cases, the precompiler turns the string value into an integer, making it appear to be
57+
// a const macro of interest, but strings should be ignored.
58+
not exists(Macro m | m = mi.getMacro() | m.getBody().toString().regexpMatch("^'.{4}'$")) and
59+
// Special case macros that are known to be used in buffer streams
60+
// where it is common index into a buffer or allocate a buffer size based on a constant
61+
// this includes known protocol constants and magic numbers
62+
not (
63+
// ignoring any string looking like a magic number, part of the smb2 protocol or csc protocol
64+
mi.getMacroName().toLowerCase().matches(["%magic%", "%smb2%", "csc_%"]) and
65+
// but only ignore if the macro does not also appear to be a size or length macro
66+
not mi.getMacroName().toLowerCase().matches(["%size%", "%length%"])
4767
)
4868
}
4969

50-
from SizeofExprOperator sizeofExpr, MacroInvocation mi
51-
where isSizeOfExprOperandMacroInvocationAConstInteger(sizeofExpr, mi)
70+
from CandidateSizeofCall sizeofExpr, MacroInvocation mi, string inMacro
71+
where
72+
isSizeOfExprOperandMacroInvocationAConstInteger(sizeofExpr, mi, _) and
73+
(if sizeofExpr.isInMacroExpansion() then inMacro = " (in a macro expansion) " else inMacro = " ")
5274
select sizeofExpr,
53-
"$@: sizeof of integer macro $@ will always return the size of the underlying integer type.",
54-
sizeofExpr, sizeofExpr.getEnclosingFunction().getName(), mi.getMacro(), mi.getMacro().getName()
75+
"$@: sizeof" + inMacro +
76+
"of integer macro $@ will always return the size of the underlying integer type.", sizeofExpr,
77+
sizeofExpr.getEnclosingFunction().getName(), mi.getMacro(), mi.getMacro().getName()
Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,83 @@
11
import cpp
22

33
/**
4-
* Holds if `type` is a `Type` that typically should not be used for `sizeof` in macros or function return values.
4+
* Determines if the sizeOfExpr is ignorable.
55
*/
6-
predicate isTypeDangerousForSizeof(Type type) {
7-
(
8-
type instanceof IntegralOrEnumType and
9-
// ignore string literals
10-
not type instanceof WideCharType and
11-
not type instanceof CharType
6+
predicate ignorableSizeof(SizeofExprOperator sizeofExpr) {
7+
// a common pattern found is to sizeof a binary operation to check a type
8+
// to then perfomr an onperaiton for a 32 or 64 bit type.
9+
// these cases often look like sizeof(x) >=4
10+
// more generally we see binary operations frequently used in different type
11+
// checks, where the sizeof is part of some comparison operation of a switch statement guard.
12+
// sizeof as an argument is also similarly used, but seemingly less frequently.
13+
exists(ComparisonOperation comp | comp.getAnOperand() = sizeofExpr)
14+
or
15+
exists(ConditionalStmt s | s.getControllingExpr() = sizeofExpr)
16+
or
17+
// another common practice is to use bit-wise operations in sizeof to allow the compiler to
18+
// 'pack' the size appropriate but get the size of the result out of a sizeof operation.
19+
sizeofExpr.getExprOperand() instanceof BinaryBitwiseOperation
20+
or
21+
// Known intentional misuses in corecrt_math.h
22+
// Windows SDK corecrt_math.h defines a macro _CLASS_ARG that
23+
// intentionally misuses sizeof to determine the size of a floating point type.
24+
// Explicitly ignoring any hit in this macro.
25+
exists(MacroInvocation mi |
26+
mi.getMacroName() = "_CLASS_ARG" and
27+
mi.getMacro().getFile().getBaseName() = "corecrt_math.h" and
28+
mi.getAnExpandedElement() = sizeofExpr
1229
)
13-
}
14-
15-
/**
16-
* Holds if `type` is a `Type` that typically should not be used for `sizeof` in macros or function return values.
17-
* This predicate extends the types detected in exchange of precision.
18-
* For higher precision, please use `isTypeDangerousForSizeof`
19-
*/
20-
predicate isTypeDangerousForSizeofLowPrecision(Type type) {
21-
(
22-
// UINT8/BYTE are typedefs to char, so we treat them separately.
23-
// WCHAR is sometimes a typedef to UINT16, so we treat it separately too.
24-
type.getName() = "UINT8"
25-
or
26-
type.getName() = "BYTE"
27-
or
28-
not type.getName() = "WCHAR" and
29-
exists(Type ut |
30-
ut = type.getUnderlyingType() and
31-
ut instanceof IntegralOrEnumType and
32-
not ut instanceof WideCharType and
33-
not ut instanceof CharType
34-
)
30+
or
31+
// the linux minmax.h header has macros that intentionally miuse sizeof,
32+
// for type checking, see __typecheck
33+
// This code has been observed in kernel.h as well.
34+
// Ignoring cases in linux build_bug.h and bug.h see BUILD_BUG_ON_INVALID
35+
// Ignoring cases of uses of FP_XSTATE_MAGIC2_SIZE found in sigcontext.h
36+
// which uses sizeof a constant as a way to get an architecturally agnostic size by
37+
// using the special magic number constant already defined
38+
exists(MacroInvocation mi |
39+
(
40+
// Generally ignore anything from these linux headers
41+
mi.getMacro().getFile().getBaseName() in [
42+
"minmax.h", "build_bug.h", "kernel.h", "bug.h", "sigcontext.h"
43+
] and
44+
mi.getMacro().getFile().getRelativePath().toLowerCase().matches("%linux%")
45+
or
46+
// Sometimes the same macros are copied into other files, so also check the macro name
47+
// this is redundant, but the first check above blocks all macros in these headers
48+
// while this second check blocks any copies of these specific macros if found elsewhere.
49+
mi.getMacroName() = "FP_XSTATE_MAGIC2_SIZE"
50+
or
51+
mi.getMacroName() = "__typecheck"
52+
) and
53+
mi.getAnExpandedElement() = sizeofExpr
54+
)
55+
or
56+
// if the operand is a macro invocation of something resembling "null"
57+
// assume sizeof is intended for strings, and ignore as this is a
58+
// potential null pointer issue, not a misuse of sizeof.
59+
exists(MacroInvocation mi |
60+
mi.getAnExpandedElement() = sizeofExpr.getExprOperand() and
61+
mi.getMacroName().toLowerCase().matches("%null%")
62+
)
63+
or
64+
// LLVM has known test cases under gcc-torture, ignore any hits under any matching directory
65+
// see for example 20020226-1.c
66+
sizeofExpr.getFile().getRelativePath().toLowerCase().matches("%gcc-%torture%")
67+
or
68+
// The user seems to be ignoring the output of the sizeof by casting the sizeof to void
69+
// this has been observed as a common pattern in assert macros (I believe for disabling asserts in release builds).
70+
// NOTE: having to check the conversion's type rather than the conversion itself
71+
// i.e., checking if VoidConversion
72+
// as nesting in parenthesis creats a ParenConversion
73+
sizeofExpr.getExplicitlyConverted().getUnspecifiedType() instanceof VoidType
74+
or
75+
// A common macro seen that gets size of arguments, considered ignorable
76+
exists(MacroInvocation mi |
77+
mi.getMacroName() = "_SDT_ARGSIZE" and mi.getAnExpandedElement() = sizeofExpr
3578
)
3679
}
3780

38-
/**
39-
* Holds if the `Function` return type is dangerous as input for `sizeof`.
40-
*/
41-
class FunctionWithTypeDangerousForSizeofLowPrecision extends Function {
42-
FunctionWithTypeDangerousForSizeofLowPrecision() {
43-
exists(Type type | type = this.getType() | isTypeDangerousForSizeofLowPrecision(type))
44-
}
81+
class CandidateSizeofCall extends SizeofExprOperator {
82+
CandidateSizeofCall() { not ignorableSizeof(this) }
4583
}

0 commit comments

Comments
 (0)