1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
/**
* @name Unsafe vector access
* @description std::vector::operator[] does not do any runtime
* bounds-checking, so it is safer to use std::vector::at()
* @kind problem
* @problem.severity warning
* @id cpp/unsafe-vector-access
* @tags security
* external/cwe/cwe-125
*/
import cpp
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
import semmle.code.cpp.valuenumbering.HashCons
// A call to `operator[]`.
class ArrayIndexCall extends FunctionCall {
ClassTemplateInstantiation ti;
TemplateClass tc;
ArrayIndexCall() {
this.getTarget().getName() = "operator[]" and
ti = this.getQualifier().getType().getUnspecifiedType() and
tc = ti.getTemplate() and
tc.getSimpleName() != "map" and
tc.getSimpleName() != "match_results"
}
ClassTemplateInstantiation getClassTemplateInstantiation() { result = ti }
TemplateClass getTemplateClass() { result = tc }
}
// A call to `size` or `length`.
class SizeCall extends FunctionCall {
string fname;
SizeCall() {
fname = this.getTarget().getName() and
(fname = "size" or fname = "length")
}
}
// `x[i]` is safe if `x` is a `std::array` (fixed-size array)
// and `i` within the bounds of the array.
predicate indexK_with_fixedarray(ClassTemplateInstantiation t, ArrayIndexCall call) {
t = call.getClassTemplateInstantiation() and
exists(Expr idx |
t.getSimpleName() = "array" and
idx = call.getArgument(0) and
lowerBound(idx) >= 0 and
upperBound(idx) < lowerBound(t.getTemplateArgument(1))
)
}
// Holds if `cond` is a Boolean condition that checks the size of
// the array. It handles the following code patterns:
//
// 1. `if (!x.empty()) { ... }`
// 2. `if (x.length()) { ... }`
// 3. `if (x.size() > 2) { ... }`
// 4. `if (x.size() == 2) { ... }`
// 5. `if (x.size() != 0) { ... }`
//
// If it safe to assume that `x.size() >= minsize` on the exit `branch`.
predicate minimum_size_cond(Expr cond, Expr arrayExpr, int minsize, boolean branch) {
// `if (!x.empty()) { ...x[0]... }`
exists(FunctionCall emptyCall |
cond = emptyCall and
arrayExpr = emptyCall.getQualifier() and
emptyCall.getTarget().getName() = "empty" and
minsize = 1 and
branch = false
)
or
// `if (x.length()) { ...x[0]... }`
exists(SizeCall sizeCall |
cond = sizeCall and
arrayExpr = sizeCall.getQualifier() and
minsize = 1 and
branch = true
)
or
// `if (x.size() > 2) { ...x[2]... }`
exists(SizeCall sizeCall, Expr k, RelationStrictness strict |
arrayExpr = sizeCall.getQualifier() and
relOpWithSwapAndNegate(cond, sizeCall, k, Greater(), strict, branch)
|
strict = Strict() and minsize = 1 + k.getValue().toInt()
or
strict = Nonstrict() and minsize = k.getValue().toInt()
)
or
// `if (x.size() == 2) { ...x[1]... }`
exists(SizeCall sizeCall, Expr k |
arrayExpr = sizeCall.getQualifier() and
eqOpWithSwapAndNegate(cond, sizeCall, k, true, branch) and
minsize = k.getValue().toInt()
)
or
// `if (x.size() != 0) { ...x[0]... }`
exists(SizeCall sizeCall, Expr k |
arrayExpr = sizeCall.getQualifier() and
eqOpWithSwapAndNegate(cond, sizeCall, k, false, branch) and
k.getValue().toInt() = 0 and
minsize = 1
)
}
// Array accesses like these are safe:
// `if (!x.empty()) { ... x[0] ... }`
// `if (x.size() > 2) { ... x[2] ... }`
predicate indexK_with_check(GuardCondition guard, ArrayIndexCall call) {
exists(Expr arrayExpr, BasicBlock block, int i, int minsize, boolean branch |
minimum_size_cond(guard, arrayExpr, minsize, branch) and
(
globalValueNumber(arrayExpr) = globalValueNumber(call.getQualifier()) or
hashCons(arrayExpr) = hashCons(call.getQualifier())
) and
guard.controls(block, branch) and
block.contains(call) and
i = call.getArgument(0).getValue().toInt() and
0 <= i and
i < minsize
)
}
// Array accesses like this are safe:
// `if (i < x.size()) { ... x[i] ... }`
predicate indexI_with_check(GuardCondition guard, ArrayIndexCall call) {
exists(Expr idx, SizeCall sizeCall, BasicBlock block, boolean branch |
relOpWithSwapAndNegate(guard, idx, sizeCall, Lesser(), Strict(), branch) and
(
globalValueNumber(sizeCall.getQualifier()) = globalValueNumber(call.getQualifier()) and
globalValueNumber(idx) = globalValueNumber(call.getArgument(0))
or
hashCons(sizeCall.getQualifier()) = hashCons(call.getQualifier()) and
hashCons(idx) = hashCons(call.getArgument(0))
) and
guard.controls(block, branch) and
block.contains(call)
)
}
// Array accesses like this are safe:
// `if (!x.empty()) { ... x[x.size() - 1] ... }`
predicate index_last_with_check(GuardCondition guard, ArrayIndexCall call) {
exists(Expr arrayExpr, SubExpr minusExpr, SizeCall sizeCall, BasicBlock block, boolean branch |
minimum_size_cond(guard, arrayExpr, _, branch) and
(
globalValueNumber(arrayExpr) = globalValueNumber(call.getQualifier()) or
hashCons(arrayExpr) = hashCons(call.getQualifier())
) and
guard.controls(block, branch) and
block.contains(call) and
minusExpr = call.getArgument(0) and
minusExpr.getRightOperand().getValue().toInt() = 1 and
DataFlow::localExprFlow(sizeCall, minusExpr.getLeftOperand()) and
(
globalValueNumber(sizeCall.getQualifier()) = globalValueNumber(call.getQualifier()) or
hashCons(sizeCall.getQualifier()) = hashCons(call.getQualifier())
)
)
}
from ArrayIndexCall call
where
not indexK_with_fixedarray(_, call) and
not indexK_with_check(_, call) and
not indexI_with_check(_, call) and
not index_last_with_check(_, call) and
// Ignore accesses like this: `vsnprintf(&buffer[0], buffer.size(), format, args)`
// That's pointer arithmetic, not a deref, so it's usually a false positive.
not exists(AddressOfExpr addrExpr | addrExpr.getOperand() = call) and
// Ignore results in the xmpsdk directory.
not call.getLocation().getFile().getRelativePath().matches("xmpsdk/%")
select call, "Unsafe use of operator[]. Use the at() method instead."
|