|
1 | 1 | /**
|
2 | 2 | * @name Overrunning write
|
3 |
| - * @description TODO |
| 3 | + * @description Exceeding the size of a static array during write or access operations |
| 4 | + * may result in a buffer overflow. |
4 | 5 | * @kind path-problem
|
5 | 6 | * @problem.severity error
|
6 | 7 | * @id cpp/overrun-write
|
7 | 8 | * @tags reliability
|
8 | 9 | * security
|
| 10 | + * external/cwe/cwe-119 |
| 11 | + * external/cwe/cwe-131 |
9 | 12 | */
|
10 | 13 |
|
11 | 14 | import cpp
|
12 | 15 | import experimental.semmle.code.cpp.dataflow.ProductFlow
|
13 | 16 | import semmle.code.cpp.ir.IR
|
14 |
| -import semmle.code.cpp.valuenumbering.GlobalValueNumbering |
15 | 17 | import semmle.code.cpp.models.interfaces.Allocation
|
16 | 18 | import semmle.code.cpp.models.interfaces.ArrayFunction
|
| 19 | +import experimental.semmle.code.cpp.semantic.analysis.RangeAnalysis |
| 20 | +import experimental.semmle.code.cpp.semantic.SemanticBound |
| 21 | +import experimental.semmle.code.cpp.semantic.SemanticExprSpecific |
17 | 22 | import DataFlow::PathGraph
|
18 | 23 |
|
| 24 | +pragma[nomagic] |
| 25 | +Instruction getABoundIn(SemBound b, IRFunction func) { |
| 26 | + result = b.getExpr(0) and |
| 27 | + result.getEnclosingIRFunction() = func |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * Holds if `i <= b + delta`. |
| 32 | + */ |
| 33 | +pragma[nomagic] |
| 34 | +predicate bounded(Instruction i, Instruction b, int delta) { |
| 35 | + exists(SemBound bound, IRFunction func | |
| 36 | + semBounded(getSemanticExpr(i), bound, delta, true, _) and |
| 37 | + b = getABoundIn(bound, func) and |
| 38 | + i.getEnclosingIRFunction() = func |
| 39 | + ) |
| 40 | +} |
| 41 | + |
| 42 | +/** |
| 43 | + * Holds if the combination of `n` and `state` represents an appropriate |
| 44 | + * source for the expression `e` suitable for use-use flow. |
| 45 | + */ |
| 46 | +private predicate hasSizeImpl(Expr e, DataFlow::Node n, string state) { |
| 47 | + // The simple case: If the size is a variable access with no qualifier we can just use the |
| 48 | + // dataflow node for that expression and no state. |
| 49 | + exists(VariableAccess va | |
| 50 | + va = e and |
| 51 | + not va instanceof FieldAccess and |
| 52 | + n.asConvertedExpr() = va.getFullyConverted() and |
| 53 | + state = "0" |
| 54 | + ) |
| 55 | + or |
| 56 | + // If the size is a choice between two expressions we allow both to be nodes representing the size. |
| 57 | + exists(ConditionalExpr cond | cond = e | hasSizeImpl([cond.getThen(), cond.getElse()], n, state)) |
| 58 | + or |
| 59 | + // If the size is an expression plus a constant, we pick the dataflow node of the expression and |
| 60 | + // remember the constant in the state. |
| 61 | + exists(Expr const, Expr nonconst | |
| 62 | + e.(AddExpr).hasOperands(const, nonconst) and |
| 63 | + state = const.getValue() and |
| 64 | + hasSizeImpl(nonconst, n, _) |
| 65 | + ) |
| 66 | + or |
| 67 | + exists(Expr const, Expr nonconst | |
| 68 | + e.(SubExpr).hasOperands(const, nonconst) and |
| 69 | + state = "-" + const.getValue() and |
| 70 | + hasSizeImpl(nonconst, n, _) |
| 71 | + ) |
| 72 | +} |
| 73 | + |
| 74 | +/** |
| 75 | + * Holds if `(n, state)` pair represents the source of flow for the size |
| 76 | + * expression associated with `alloc`. |
| 77 | + */ |
| 78 | +predicate hasSize(AllocationExpr alloc, DataFlow::Node n, string state) { |
| 79 | + hasSizeImpl(alloc.getSizeExpr(), n, state) |
| 80 | +} |
| 81 | + |
| 82 | +predicate isSinkPairImpl( |
| 83 | + CallInstruction c, DataFlow::Node bufSink, DataFlow::Node sizeSink, int delta, Expr eBuf, |
| 84 | + Expr eSize |
| 85 | +) { |
| 86 | + exists(int bufIndex, int sizeIndex, Instruction sizeInstr, Instruction bufInstr | |
| 87 | + bufInstr = bufSink.asInstruction() and |
| 88 | + c.getArgument(bufIndex) = bufInstr and |
| 89 | + sizeInstr = sizeSink.asInstruction() and |
| 90 | + c.getStaticCallTarget().(ArrayFunction).hasArrayWithVariableSize(bufIndex, sizeIndex) and |
| 91 | + bounded(c.getArgument(sizeIndex), sizeInstr, delta) and |
| 92 | + eSize = sizeInstr.getUnconvertedResultExpression() and |
| 93 | + eBuf = bufInstr.getUnconvertedResultExpression() and |
| 94 | + delta >= 1 |
| 95 | + ) |
| 96 | +} |
| 97 | + |
19 | 98 | class StringSizeConfiguration extends ProductFlow::Configuration {
|
20 | 99 | StringSizeConfiguration() { this = "StringSizeConfiguration" }
|
21 | 100 |
|
22 |
| - override predicate isSourcePair(DataFlow::Node bufSource, DataFlow::Node sizeSource) { |
23 |
| - bufSource.asConvertedExpr().(AllocationExpr).getSizeExpr() = sizeSource.asConvertedExpr() |
| 101 | + override predicate isSourcePair( |
| 102 | + DataFlow::Node bufSource, DataFlow::FlowState state1, DataFlow::Node sizeSource, |
| 103 | + DataFlow::FlowState state2 |
| 104 | + ) { |
| 105 | + // In the case of an allocation like |
| 106 | + // ```cpp |
| 107 | + // malloc(size + 1); |
| 108 | + // ``` |
| 109 | + // we use `state2` to remember that there was an offset (in this case an offset of `1`) added |
| 110 | + // to the size of the allocation. This state is then checked in `isSinkPair`. |
| 111 | + state1 instanceof DataFlow::FlowStateEmpty and |
| 112 | + hasSize(bufSource.asConvertedExpr(), sizeSource, state2) |
24 | 113 | }
|
25 | 114 |
|
26 |
| - override predicate isSinkPair(DataFlow::Node bufSink, DataFlow::Node sizeSink) { |
27 |
| - exists(CallInstruction c, int bufIndex, int sizeIndex | |
28 |
| - c.getStaticCallTarget().(ArrayFunction).hasArrayWithVariableSize(bufIndex, sizeIndex) and |
29 |
| - c.getArgument(bufIndex) = bufSink.asInstruction() and |
30 |
| - c.getArgument(sizeIndex) = sizeSink.asInstruction() |
| 115 | + override predicate isSinkPair( |
| 116 | + DataFlow::Node bufSink, DataFlow::FlowState state1, DataFlow::Node sizeSink, |
| 117 | + DataFlow::FlowState state2 |
| 118 | + ) { |
| 119 | + state1 instanceof DataFlow::FlowStateEmpty and |
| 120 | + state2 = [0 .. 32].toString() and // An arbitrary bound because we need to bound `state2` |
| 121 | + exists(int delta | |
| 122 | + isSinkPairImpl(_, bufSink, sizeSink, delta, _, _) and |
| 123 | + delta > state2.toInt() |
31 | 124 | )
|
32 | 125 | }
|
33 | 126 | }
|
34 | 127 |
|
35 |
| -// we don't actually check correctness yet. Right now the query just finds relevant source/sink pairs. |
36 | 128 | from
|
37 | 129 | StringSizeConfiguration conf, DataFlow::PathNode source1, DataFlow2::PathNode source2,
|
38 |
| - DataFlow::PathNode sink1, DataFlow2::PathNode sink2 |
39 |
| -where conf.hasFlowPath(source1, source2, sink1, sink2) |
40 |
| -// TODO: pull delta out and display it |
41 |
| -select sink1.getNode(), source1, sink1, "Overrunning write allocated at $@ bounded by $@.", source1, |
42 |
| - source1.toString(), sink2, sink2.toString() |
| 130 | + DataFlow::PathNode sink1, DataFlow2::PathNode sink2, int k, CallInstruction c, |
| 131 | + DataFlow::Node sourceNode, Expr bound, Expr buffer, string element |
| 132 | +where |
| 133 | + conf.hasFlowPath(source1, source2, sink1, sink2) and |
| 134 | + k > sink2.getState().toInt() and |
| 135 | + isSinkPairImpl(c, sink1.getNode(), sink2.getNode(), k, buffer, bound) and |
| 136 | + sourceNode = source1.getNode() and |
| 137 | + if k = 1 then element = " element." else element = " elements." |
| 138 | +select c.getUnconvertedResultExpression(), source1, sink1, |
| 139 | + "This write may overflow $@ by " + k + element, buffer, buffer.toString() |
0 commit comments