1111 */
1212
1313import cpp
14- import semmle.code.cpp.ir.IR
1514import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
15+ import semmle.code.cpp.controlflow.StackVariableReachability
1616
1717/**
1818 * Holds if `node` overwrites `var` (assignment or declaration with initializer).
1919 */
20- predicate isDefOf ( ControlFlowNode node , Variable var ) {
20+ predicate isDefOf ( ControlFlowNode node , StackVariable var ) {
2121 node = var .getAnAccess ( ) and node .( VariableAccess ) .isLValue ( )
2222 or
2323 node .( DeclStmt ) .getADeclaration ( ) = var and exists ( var .getInitializer ( ) )
@@ -26,62 +26,74 @@ predicate isDefOf(ControlFlowNode node, Variable var) {
2626}
2727
2828/**
29- * Find CFG paths from start to end that do not cross over a definition of var .
29+ * Identifies an unsigned postfix decrement inside a comparison against zero .
3030 */
31- predicate successorGuarded ( ControlFlowNode start , ControlFlowNode end , Variable var ) {
32- start = end
33- or
34- exists ( ControlFlowNode interm |
35- start .getASuccessor ( ) = interm and
36- not isDefOf ( interm , var ) and
37- (
38- interm .getASuccessor ( ) = end
39- or
40- successorGuarded ( interm , end , var )
41- )
42- )
43- }
44-
45-
46- from Variable var , VariableAccess varAcc , PostfixDecrExpr dec ,
47- VariableAccess varAccAfterOverflow , ComparisonOperation cmp
48- where
49- // get unsigned variable that is decremented
31+ pragma [ nomagic]
32+ predicate isDecInComparison (
33+ PostfixDecrExpr dec , VariableAccess varAcc ,
34+ ComparisonOperation cmp , StackVariable var
35+ ) {
5036 varAcc = var .getAnAccess ( ) and
51- varAccAfterOverflow = var .getAnAccess ( ) and
52- varAcc != varAccAfterOverflow and
5337 dec .getOperand ( ) = varAcc .getExplicitlyConverted ( ) and
5438 varAcc .getUnderlyingType ( ) .( IntegralType ) .isUnsigned ( ) and
55-
56- // only decrementations inside comparisons
5739 cmp .getAnOperand ( ) .getAChild * ( ) = varAcc and
5840 cmp .getAnOperand ( ) instanceof Zero and
41+ lowerBound ( varAcc ) = 0
42+ }
43+
44+ /**
45+ * Identifies a non-assignment read of a variable
46+ * (i.e., a use that could observe an overflowed value).
47+ */
48+ pragma [ nomagic]
49+ predicate isReadOf ( VariableAccess va , StackVariable var ) {
50+ va = var .getAnAccess ( ) and
51+ not exists ( AssignExpr ae | ae .getLValue ( ) = va )
52+ }
5953
60- // only if the variable is used (not just overwritten) after the comparison
61- successorGuarded ( cmp , varAccAfterOverflow , var ) and
62- cmp .getAnOperand ( ) .getAChild * ( ) != varAccAfterOverflow and
63- not exists ( AssignExpr ae | ae .getLValue ( ) = varAccAfterOverflow ) and
54+ /**
55+ * Basic-block-level reachability from a decrement comparison to a use
56+ * of the same variable, blocked by any definition of the variable.
57+ */
58+ class DecOverflowReach extends StackVariableReachability {
59+ DecOverflowReach ( ) { this = "DecOverflowReach" }
6460
65- // var-- > 0 (0 < var--) then accesses only in false branch
66- // var-- >= 0 then accesses in all branches
67- // var-- == 0 then accesses in all branches
68- // var-- != 0 then accesses in all branches
69- if (
70- ( cmp instanceof GTExpr and cmp .getRightOperand ( ) instanceof Zero )
71- or
72- ( cmp instanceof LTExpr and cmp .getLeftOperand ( ) instanceof Zero )
73- )
74- then
75- cmp .getAFalseSuccessor ( ) .getASuccessor * ( ) = varAccAfterOverflow
76- else
77- any ( )
61+ override predicate isSource ( ControlFlowNode node , StackVariable v ) {
62+ isDecInComparison ( _, _, node , v )
63+ }
7864
79- and
65+ override predicate isSink ( ControlFlowNode node , StackVariable v ) {
66+ isReadOf ( node , v )
67+ }
8068
81- // only if var may possibly be zero during comparison
82- lowerBound ( varAcc ) = 0
69+ override predicate isBarrier ( ControlFlowNode node , StackVariable v ) {
70+ isDefOf ( node , v )
71+ }
72+ }
8373
74+ from
75+ SemanticStackVariable var , VariableAccess varAcc , PostfixDecrExpr dec ,
76+ VariableAccess varAccAfterOverflow , ComparisonOperation cmp
77+ where
78+ isDecInComparison ( dec , varAcc , cmp , var ) and
79+ isReadOf ( varAccAfterOverflow , var ) and
80+ varAcc != varAccAfterOverflow and
81+ // reachable without intervening overwrite (basic-block-level analysis)
82+ any ( DecOverflowReach r ) .reaches ( cmp , var , varAccAfterOverflow ) and
83+ // exclude accesses that are part of the comparison expression itself
84+ not cmp .getAnOperand ( ) .getAChild * ( ) = varAccAfterOverflow and
85+ // var-- > 0 (0 < var--) then only accesses in false branch matter
86+ (
87+ if
88+ (
89+ cmp instanceof GTExpr and cmp .getRightOperand ( ) instanceof Zero
90+ or
91+ cmp instanceof LTExpr and cmp .getLeftOperand ( ) instanceof Zero
92+ )
93+ then cmp .getAFalseSuccessor ( ) .getASuccessor * ( ) = varAccAfterOverflow
94+ else any ( )
95+ ) and
8496 // skip vendor code
85- and not dec .getFile ( ) .getAbsolutePath ( ) .toLowerCase ( ) .matches ( [ "%vendor%" , "%third_party%" ] )
86-
87- select dec , "Unsigned decrementation in comparison ($@) - $@" , cmp , cmp . toString ( ) , varAccAfterOverflow , varAccAfterOverflow .toString ( )
97+ not dec .getFile ( ) .getAbsolutePath ( ) .toLowerCase ( ) .matches ( [ "%vendor%" , "%third_party%" ] )
98+ select dec , "Unsigned decrementation in comparison ($@) - $@" , cmp , cmp . toString ( ) ,
99+ varAccAfterOverflow , varAccAfterOverflow .toString ( )
0 commit comments