Skip to content

Commit a8eed6b

Browse files
authored
Merge pull request #14704 from MathiasVP/fix-uninitialized-local
C++: IR'ify `cpp/uninitialized-local` and fix FPs
2 parents b632947 + 69502d0 commit a8eed6b

File tree

5 files changed

+115
-49
lines changed

5 files changed

+115
-49
lines changed

cpp/ql/lib/semmle/code/cpp/ir/dataflow/MustFlow.qll

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ abstract class MustFlowConfiguration extends string {
3131
*/
3232
abstract predicate isSink(Operand sink);
3333

34+
/**
35+
* Holds if data flow through `instr` is prohibited.
36+
*/
37+
predicate isBarrier(Instruction instr) { none() }
38+
3439
/**
3540
* Holds if the additional flow step from `node1` to `node2` must be taken
3641
* into account in the analysis.
@@ -48,18 +53,21 @@ abstract class MustFlowConfiguration extends string {
4853
*/
4954
final predicate hasFlowPath(MustFlowPathNode source, MustFlowPathSink sink) {
5055
this.isSource(source.getInstruction()) and
51-
source.getASuccessor+() = sink
56+
source.getASuccessor*() = sink
5257
}
5358
}
5459

5560
/** Holds if `node` flows from a source. */
5661
pragma[nomagic]
5762
private predicate flowsFromSource(Instruction node, MustFlowConfiguration config) {
58-
config.isSource(node)
59-
or
60-
exists(Instruction mid |
61-
step(mid, node, config) and
62-
flowsFromSource(mid, pragma[only_bind_into](config))
63+
not config.isBarrier(node) and
64+
(
65+
config.isSource(node)
66+
or
67+
exists(Instruction mid |
68+
step(mid, node, config) and
69+
flowsFromSource(mid, pragma[only_bind_into](config))
70+
)
6371
)
6472
}
6573

cpp/ql/src/Likely Bugs/Memory Management/UninitializedLocal.ql

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
*/
1414

1515
import cpp
16-
import semmle.code.cpp.controlflow.StackVariableReachability
16+
import semmle.code.cpp.ir.IR
17+
import semmle.code.cpp.ir.dataflow.MustFlow
1718

1819
/**
1920
* Auxiliary predicate: Types that don't require initialization
@@ -33,31 +34,6 @@ predicate allocatedType(Type t) {
3334
allocatedType(t.getUnspecifiedType())
3435
}
3536

36-
/**
37-
* A declaration of a local variable that leaves the
38-
* variable uninitialized.
39-
*/
40-
DeclStmt declWithNoInit(LocalVariable v) {
41-
result.getADeclaration() = v and
42-
not exists(v.getInitializer()) and
43-
/* The type of the variable is not stack-allocated. */
44-
exists(Type t | t = v.getType() | not allocatedType(t))
45-
}
46-
47-
class UninitialisedLocalReachability extends StackVariableReachability {
48-
UninitialisedLocalReachability() { this = "UninitialisedLocal" }
49-
50-
override predicate isSource(ControlFlowNode node, StackVariable v) { node = declWithNoInit(v) }
51-
52-
override predicate isSink(ControlFlowNode node, StackVariable v) { useOfVarActual(v, node) }
53-
54-
override predicate isBarrier(ControlFlowNode node, StackVariable v) {
55-
// only report the _first_ possibly uninitialized use
56-
useOfVarActual(v, node) or
57-
definitionBarrier(v, node)
58-
}
59-
}
60-
6137
pragma[noinline]
6238
predicate containsInlineAssembly(Function f) { exists(AsmStmt s | s.getEnclosingFunction() = f) }
6339

@@ -82,8 +58,33 @@ VariableAccess commonException() {
8258
containsInlineAssembly(result.getEnclosingFunction())
8359
}
8460

85-
from UninitialisedLocalReachability r, LocalVariable v, VariableAccess va
61+
predicate isSinkImpl(Instruction sink, VariableAccess va) {
62+
exists(LoadInstruction load |
63+
va = load.getUnconvertedResultExpression() and
64+
not va = commonException() and
65+
sink = load.getSourceValue()
66+
)
67+
}
68+
69+
class MustFlow extends MustFlowConfiguration {
70+
MustFlow() { this = "MustFlow" }
71+
72+
override predicate isSource(Instruction source) {
73+
source instanceof UninitializedInstruction and
74+
exists(Type t | t = source.getResultType() | not allocatedType(t))
75+
}
76+
77+
override predicate isSink(Operand sink) { isSinkImpl(sink.getDef(), _) }
78+
79+
override predicate allowInterproceduralFlow() { none() }
80+
81+
override predicate isBarrier(Instruction instr) { instr instanceof ChiInstruction }
82+
}
83+
84+
from
85+
VariableAccess va, LocalVariable v, MustFlow conf, MustFlowPathNode source, MustFlowPathNode sink
8686
where
87-
r.reaches(_, v, va) and
88-
not va = commonException()
87+
conf.hasFlowPath(source, sink) and
88+
isSinkImpl(sink.getInstruction(), va) and
89+
v = va.getTarget()
8990
select va, "The variable $@ may not be initialized at this access.", v, v.getName()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `cpp/uninitialized-local` query has been improved to produce fewer false positives.

cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/UninitializedLocal.expected

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
| test.cpp:12:6:12:8 | foo | The variable $@ may not be initialized at this access. | test.cpp:11:6:11:8 | foo | foo |
2-
| test.cpp:30:6:30:8 | foo | The variable $@ may not be initialized at this access. | test.cpp:26:6:26:8 | foo | foo |
3-
| test.cpp:46:6:46:8 | foo | The variable $@ may not be initialized at this access. | test.cpp:42:6:42:8 | foo | foo |
4-
| test.cpp:55:7:55:9 | foo | The variable $@ may not be initialized at this access. | test.cpp:50:6:50:8 | foo | foo |
5-
| test.cpp:67:7:67:9 | foo | The variable $@ may not be initialized at this access. | test.cpp:61:6:61:8 | foo | foo |
6-
| test.cpp:92:6:92:8 | foo | The variable $@ may not be initialized at this access. | test.cpp:82:6:82:8 | foo | foo |
72
| test.cpp:113:6:113:8 | foo | The variable $@ may not be initialized at this access. | test.cpp:111:6:111:8 | foo | foo |
8-
| test.cpp:132:9:132:9 | j | The variable $@ may not be initialized at this access. | test.cpp:126:6:126:6 | j | j |
93
| test.cpp:219:3:219:3 | x | The variable $@ may not be initialized at this access. | test.cpp:218:7:218:7 | x | x |
104
| test.cpp:243:13:243:13 | i | The variable $@ may not be initialized at this access. | test.cpp:241:6:241:6 | i | i |
11-
| test.cpp:329:9:329:11 | val | The variable $@ may not be initialized at this access. | test.cpp:321:6:321:8 | val | val |
125
| test.cpp:336:10:336:10 | a | The variable $@ may not be initialized at this access. | test.cpp:333:7:333:7 | a | a |
136
| test.cpp:369:10:369:10 | a | The variable $@ may not be initialized at this access. | test.cpp:358:7:358:7 | a | a |
147
| test.cpp:378:9:378:11 | val | The variable $@ may not be initialized at this access. | test.cpp:359:6:359:8 | val | val |

cpp/ql/test/query-tests/Security/CWE/CWE-457/semmle/tests/test.cpp

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void test4(bool b) {
2727
if (b) {
2828
foo = 1;
2929
}
30-
use(foo); // BAD
30+
use(foo); // BAD [NOT DETECTED]
3131
}
3232

3333
void test5() {
@@ -43,7 +43,7 @@ void test5(int count) {
4343
for (int i = 0; i < count; i++) {
4444
foo = i;
4545
}
46-
use(foo); // BAD
46+
use(foo); // BAD [NOT DETECTED]
4747
}
4848

4949
void test6(bool b) {
@@ -52,7 +52,7 @@ void test6(bool b) {
5252
foo = 42;
5353
}
5454
if (b) {
55-
use(foo); // GOOD (REPORTED, FP)
55+
use(foo); // GOOD
5656
}
5757
}
5858

@@ -64,7 +64,7 @@ void test7(bool b) {
6464
set = true;
6565
}
6666
if (set) {
67-
use(foo); // GOOD (REPORTED, FP)
67+
use(foo); // GOOD
6868
}
6969
}
7070

@@ -89,7 +89,7 @@ void test9(int count) {
8989
if (!set) {
9090
foo = 42;
9191
}
92-
use(foo); // GOOD (REPORTED, FP)
92+
use(foo); // GOOD
9393
}
9494

9595
void test10() {
@@ -129,7 +129,7 @@ int absWrong(int i) {
129129
} else if (i < 0) {
130130
j = -i;
131131
}
132-
return j; // wrong: j may not be initialized before use
132+
return j; // wrong: j may not be initialized before use [NOT DETECTED]
133133
}
134134

135135
// Example from qhelp
@@ -326,7 +326,7 @@ int test28() {
326326
a = false;
327327
c = false;
328328
}
329-
return val; // GOOD [FALSE POSITIVE]
329+
return val; // GOOD
330330
}
331331

332332
int test29() {
@@ -472,4 +472,64 @@ void test44() {
472472
int y = 1;
473473

474474
void(x + y); // BAD
475+
}
476+
477+
enum class State { StateA, StateB, StateC };
478+
479+
int exhaustive_switch(State s) {
480+
int y;
481+
switch(s) {
482+
case State::StateA:
483+
y = 1;
484+
break;
485+
case State::StateB:
486+
y = 2;
487+
break;
488+
case State::StateC:
489+
y = 3;
490+
break;
491+
}
492+
return y; // GOOD (y is always initialized)
493+
}
494+
495+
int exhaustive_switch_2(State s) {
496+
int y;
497+
switch(s) {
498+
case State::StateA:
499+
y = 1;
500+
break;
501+
default:
502+
y = 2;
503+
break;
504+
}
505+
return y; // GOOD (y is always initialized)
506+
}
507+
508+
int non_exhaustive_switch(State s) {
509+
int y;
510+
switch(s) {
511+
case State::StateA:
512+
y = 1;
513+
break;
514+
case State::StateB:
515+
y = 2;
516+
break;
517+
}
518+
return y; // BAD [NOT DETECTED] (y is not initialized when s = StateC)
519+
}
520+
521+
int non_exhaustive_switch_2(State s) {
522+
int y;
523+
switch(s) {
524+
case State::StateA:
525+
y = 1;
526+
break;
527+
case State::StateB:
528+
y = 2;
529+
break;
530+
}
531+
if(s != State::StateC) {
532+
return y; // GOOD (y is not initialized when s = StateC, but if s = StateC we won't reach this point)
533+
}
534+
return 0;
475535
}

0 commit comments

Comments
 (0)