Skip to content

Commit 69cbbff

Browse files
committed
Adding UncheckedBoundsEnumAsIndex ql, help and example
1 parent 34fe60d commit 69cbbff

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
typedef enum {
2+
exampleSomeValue,
3+
exampleSomeOtherValue,
4+
exampleValueMax
5+
} EXAMPLE_VALUES;
6+
7+
/*...*/
8+
9+
int variable = someStructure->example;
10+
if (variable >= exampleValueMax)
11+
{
12+
/* ... Some action ... */
13+
}
14+
// ...
15+
Status = someArray[variable](/*...*/);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>This rule finds code where an enumerated type (<code>enum</code>) is used to check for an upper boundary, but not the lower boundary, and the value is used as an index to access an array.</p>
7+
<p>By default an enum variable is signed, and therefore it is important to ensure that it cannot take on a negative value. When the enum is subsequently used to index an array, or worse still an array of function pointers, then a negative enum value would lead to potentially arbitrary memory being read, used and/or executed.</p>
8+
</overview>
9+
<recommendation>
10+
<p>In the majority of cases the fix is simply to add the required lower bounds check to ensure that the enum has a positive value.</p>
11+
</recommendation>
12+
13+
<example>
14+
<p>The following example a value is passed and gets cast to an enumerated type and only partially bounds checked.</p>
15+
<sample src="UncheckedBoundsEnumAsIndex.c" />
16+
<p>In this example, the result of the out-of-bounds may allow for arbitrary code execution.</p>
17+
<p>To fix the problem in this example, you need to add an additional check to the guarding if statement to verify that the index is a positive value.</p>
18+
</example>
19+
20+
21+
<references>
22+
23+
</references>
24+
</qhelp>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* @name EnumIndex
3+
* @description Code using enumerated types as indexes into arrays will often check for
4+
* an upper bound to ensure the index is not out of range.
5+
* By default an enum variable is signed, and therefore it is important to ensure
6+
* that it cannot take on a negative value. When the enum is subsequently used
7+
* to index an array, or worse still an array of function pointers, then a negative
8+
* enum value would lead to potentially arbitrary memory being read, used and/or executed.
9+
* @kind problem
10+
* @problem.severity error
11+
* @precision high
12+
* @id cpp/enum-index
13+
* @tags security
14+
* external/cwe/cwe-125
15+
* external/microsoft/c33010
16+
*/
17+
18+
import cpp
19+
import semmle.code.cpp.controlflow.Guards
20+
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
21+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
22+
23+
/**
24+
* Holds if `ec` is the upper bound of an enum
25+
*/
26+
predicate isUpperBoundEnumValue(EnumConstant ec) {
27+
not exists(EnumConstant ec2, Enum enum | enum = ec2.getEnclosingElement() |
28+
enum = ec.getEnclosingElement() and
29+
ec2.getValue().toInt() > ec.getValue().toInt()
30+
)
31+
}
32+
33+
/**
34+
* Holds if 'eca' is an access to the upper bound of an enum
35+
*/
36+
predicate isUpperBoundEnumAccess(EnumConstantAccess eca) {
37+
exists(EnumConstant ec |
38+
varbind(eca, ec) and
39+
isUpperBoundEnumValue(ec)
40+
)
41+
}
42+
43+
/**
44+
* Holds if the expression `e` is accessing the enum constant `ec`
45+
*/
46+
predicate isExpressionAccessingUpperboundEnum(Expr e, EnumConstantAccess ec) {
47+
isExpressionAccessingUpperboundEnum(e.getAChild(), ec)
48+
or
49+
ec = e and
50+
isUpperBoundEnumAccess(ec)
51+
}
52+
53+
/**
54+
* Holds if `e` is a child of an If statement
55+
*/
56+
predicate isPartOfAnIfStatement(Expr e) {
57+
isPartOfAnIfStatement(e.getAChild())
58+
or
59+
exists(IfStmt ifs | ifs.getAChild() = e)
60+
}
61+
62+
/**
63+
* Holds if the variable access `offsetExpr` upper bound is guarded by an If statement GuardCondition
64+
* that is using the upper bound of an enum to check the upper bound of `offsetExpr`
65+
*/
66+
predicate hasUpperBoundDefinedByEnum(VariableAccess offsetExpr) {
67+
exists(BasicBlock controlled, StackVariable offsetVar, SsaDefinition def |
68+
controlled.contains(offsetExpr) and
69+
linearBoundControlsEnum(controlled, def, offsetVar, Lesser()) and
70+
offsetExpr = def.getAUse(offsetVar)
71+
)
72+
}
73+
74+
pragma[noinline]
75+
predicate linearBoundControlsEnum(
76+
BasicBlock controlled, SsaDefinition def, StackVariable offsetVar, RelationDirection direction
77+
) {
78+
exists(GuardCondition guard |
79+
exists(boolean branch |
80+
guard.controls(controlled, branch) and
81+
cmpWithLinearBound(guard, def.getAUse(offsetVar), direction, branch)
82+
) and
83+
exists(EnumConstantAccess enumca | isExpressionAccessingUpperboundEnum(guard, enumca)) and
84+
isPartOfAnIfStatement(guard)
85+
)
86+
}
87+
88+
/**
89+
* Holds if the variable access `offsetExpr` lower bound is guarded
90+
*/
91+
predicate hasLowerBound(VariableAccess offsetExpr) {
92+
exists(BasicBlock controlled, StackVariable offsetVar, SsaDefinition def |
93+
controlled.contains(offsetExpr) and
94+
linearBoundControls(controlled, def, offsetVar, Greater()) and
95+
offsetExpr = def.getAUse(offsetVar)
96+
)
97+
}
98+
99+
pragma[noinline]
100+
predicate linearBoundControls(
101+
BasicBlock controlled, SsaDefinition def, StackVariable offsetVar, RelationDirection direction
102+
) {
103+
exists(GuardCondition guard, boolean branch |
104+
guard.controls(controlled, branch) and
105+
cmpWithLinearBound(guard, def.getAUse(offsetVar), direction, branch) and
106+
isPartOfAnIfStatement(guard)
107+
)
108+
}
109+
110+
from VariableAccess offset, ArrayExpr array
111+
where
112+
offset = array.getArrayOffset() and
113+
hasUpperBoundDefinedByEnum(offset) and
114+
not hasLowerBound(offset) and
115+
exists(IntegralType t |
116+
t = offset.getUnderlyingType() and
117+
not t.isUnsigned()
118+
) and
119+
lowerBound(offset.getFullyConverted()) < 0
120+
select offset,
121+
"When accessing array " + array.getArrayBase() + " with index " + offset +
122+
", the upper bound of an enum is used to check the upper bound of the array, but the lower bound is not checked."

0 commit comments

Comments
 (0)