File: unsafe_vector_access.ql

package info (click to toggle)
exiv2 0.28.7%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 109,216 kB
  • sloc: cpp: 77,667; python: 9,619; javascript: 237; makefile: 193; sh: 172; ansic: 51; sed: 16
file content (181 lines) | stat: -rw-r--r-- 6,285 bytes parent folder | download | duplicates (2)
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."