Skip to content

Commit aaae717

Browse files
committed
Merge branch 'main' into weak_crypto
2 parents e985204 + c793ac9 commit aaae717

File tree

138 files changed

+5340
-659
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+5340
-659
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm
2+
* The "Tainted allocation size" query (cpp/uncontrolled-allocation-size) has been improved to produce fewer false positives.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm
2+
* The "Static buffer overflow" query (cpp/static-buffer-overflow) has been improved to produce fewer false positives.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The query "Uncontrolled arithmetic" (`cpp/uncontrolled-arithmetic`) has been improved to produce fewer false positives.

cpp/ql/src/Critical/OverflowStatic.ql

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import cpp
1616
import semmle.code.cpp.commons.Buffer
17+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
1718
import LoopBounds
1819

1920
private predicate staticBufferBase(VariableAccess access, Variable v) {
@@ -51,6 +52,8 @@ predicate overflowOffsetInLoop(BufferAccess bufaccess, string msg) {
5152
loop.getStmt().getAChild*() = bufaccess.getEnclosingStmt() and
5253
loop.limit() >= bufaccess.bufferSize() and
5354
loop.counter().getAnAccess() = bufaccess.getArrayOffset() and
55+
// Ensure that we don't have an upper bound on the array index that's less than the buffer size.
56+
not upperBound(bufaccess.getArrayOffset().getFullyConverted()) < bufaccess.bufferSize() and
5457
msg =
5558
"Potential buffer-overflow: counter '" + loop.counter().toString() + "' <= " +
5659
loop.limit().toString() + " but '" + bufaccess.buffer().getName() + "' has " +
@@ -94,17 +97,22 @@ class CallWithBufferSize extends FunctionCall {
9497
}
9598

9699
int statedSizeValue() {
97-
exists(Expr statedSizeSrc |
98-
DataFlow::localExprFlow(statedSizeSrc, statedSizeExpr()) and
99-
result = statedSizeSrc.getValue().toInt()
100-
)
100+
// `upperBound(e)` defaults to `exprMaxVal(e)` when `e` isn't analyzable. So to get a meaningful
101+
// result in this case we pick the minimum value obtainable from dataflow and range analysis.
102+
result =
103+
upperBound(statedSizeExpr())
104+
.minimum(min(Expr statedSizeSrc |
105+
DataFlow::localExprFlow(statedSizeSrc, statedSizeExpr())
106+
|
107+
statedSizeSrc.getValue().toInt()
108+
))
101109
}
102110
}
103111

104112
predicate wrongBufferSize(Expr error, string msg) {
105113
exists(CallWithBufferSize call, int bufsize, Variable buf, int statedSize |
106114
staticBuffer(call.buffer(), buf, bufsize) and
107-
statedSize = min(call.statedSizeValue()) and
115+
statedSize = call.statedSizeValue() and
108116
statedSize > bufsize and
109117
error = call.statedSizeExpr() and
110118
msg =

cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,99 @@ import cpp
1515
import semmle.code.cpp.security.Overflow
1616
import semmle.code.cpp.security.Security
1717
import semmle.code.cpp.security.TaintTracking
18+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
1819
import TaintedWithPath
1920

20-
predicate isRandCall(FunctionCall fc) { fc.getTarget().getName() = "rand" }
21+
predicate isUnboundedRandCall(FunctionCall fc) {
22+
fc.getTarget().getName() = "rand" and not bounded(fc)
23+
}
24+
25+
/**
26+
* An operand `e` of a division expression (i.e., `e` is an operand of either a `DivExpr` or
27+
* a `AssignDivExpr`) is bounded when `e` is the left-hand side of the division.
28+
*/
29+
pragma[inline]
30+
predicate boundedDiv(Expr e, Expr left) { e = left }
31+
32+
/**
33+
* An operand `e` of a remainder expression `rem` (i.e., `rem` is either a `RemExpr` or
34+
* an `AssignRemExpr`) with left-hand side `left` and right-ahnd side `right` is bounded
35+
* when `e` is `left` and `right` is upper bounded by some number that is less than the maximum integer
36+
* allowed by the result type of `rem`.
37+
*/
38+
pragma[inline]
39+
predicate boundedRem(Expr e, Expr rem, Expr left, Expr right) {
40+
e = left and
41+
upperBound(right.getFullyConverted()) < exprMaxVal(rem.getFullyConverted())
42+
}
2143

22-
predicate isRandCallOrParent(Expr e) {
23-
isRandCall(e) or
24-
isRandCallOrParent(e.getAChild())
44+
/**
45+
* An operand `e` of a bitwise and expression `andExpr` (i.e., `andExpr` is either an `BitwiseAndExpr`
46+
* or an `AssignAndExpr`) with operands `operand1` and `operand2` is the operand that is not `e` is upper
47+
* bounded by some number that is less than the maximum integer allowed by the result type of `andExpr`.
48+
*/
49+
pragma[inline]
50+
predicate boundedBitwiseAnd(Expr e, Expr andExpr, Expr operand1, Expr operand2) {
51+
operand1 != operand2 and
52+
e = operand1 and
53+
upperBound(operand2.getFullyConverted()) < exprMaxVal(andExpr.getFullyConverted())
2554
}
2655

27-
predicate isRandValue(Expr e) {
28-
isRandCall(e)
56+
/**
57+
* Holds if `fc` is a part of the left operand of a binary operation that greatly reduces the range
58+
* of possible values.
59+
*/
60+
predicate bounded(Expr e) {
61+
// For `%` and `&` we require that `e` is bounded by a value that is strictly smaller than the
62+
// maximum possible value of the result type of the operation.
63+
// For example, the function call `rand()` is considered bounded in the following program:
64+
// ```
65+
// int i = rand() % (UINT8_MAX + 1);
66+
// ```
67+
// but not in:
68+
// ```
69+
// unsigned char uc = rand() % (UINT8_MAX + 1);
70+
// ```
71+
exists(RemExpr rem | boundedRem(e, rem, rem.getLeftOperand(), rem.getRightOperand()))
72+
or
73+
exists(AssignRemExpr rem | boundedRem(e, rem, rem.getLValue(), rem.getRValue()))
74+
or
75+
exists(BitwiseAndExpr andExpr |
76+
boundedBitwiseAnd(e, andExpr, andExpr.getAnOperand(), andExpr.getAnOperand())
77+
)
78+
or
79+
exists(AssignAndExpr andExpr |
80+
boundedBitwiseAnd(e, andExpr, andExpr.getAnOperand(), andExpr.getAnOperand())
81+
)
82+
or
83+
// Optimitically assume that a division always yields a much smaller value.
84+
boundedDiv(e, any(DivExpr div).getLeftOperand())
85+
or
86+
boundedDiv(e, any(AssignDivExpr div).getLValue())
87+
}
88+
89+
predicate isUnboundedRandCallOrParent(Expr e) {
90+
isUnboundedRandCall(e)
91+
or
92+
isUnboundedRandCallOrParent(e.getAChild())
93+
}
94+
95+
predicate isUnboundedRandValue(Expr e) {
96+
isUnboundedRandCall(e)
2997
or
3098
exists(MacroInvocation mi |
3199
e = mi.getExpr() and
32-
isRandCallOrParent(e)
100+
isUnboundedRandCallOrParent(e)
33101
)
34102
}
35103

36104
class SecurityOptionsArith extends SecurityOptions {
37105
override predicate isUserInput(Expr expr, string cause) {
38-
isRandValue(expr) and
39-
cause = "rand" and
40-
not expr.getParent*() instanceof DivExpr
106+
isUnboundedRandValue(expr) and
107+
cause = "rand"
41108
}
42109
}
43110

44-
predicate isDiv(VariableAccess va) { exists(AssignDivExpr div | div.getLValue() = va) }
45-
46111
predicate missingGuard(VariableAccess va, string effect) {
47112
exists(Operation op | op.getAnOperand() = va |
48113
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
@@ -52,29 +117,15 @@ predicate missingGuard(VariableAccess va, string effect) {
52117
}
53118

54119
class Configuration extends TaintTrackingConfiguration {
55-
override predicate isSink(Element e) {
56-
isDiv(e)
57-
or
58-
missingGuard(e, _)
59-
}
60-
}
120+
override predicate isSink(Element e) { missingGuard(e, _) }
61121

62-
/**
63-
* A value that undergoes division is likely to be bounded within a safe
64-
* range.
65-
*/
66-
predicate guardedByAssignDiv(Expr origin) {
67-
exists(VariableAccess va |
68-
taintedWithPath(origin, va, _, _) and
69-
isDiv(va)
70-
)
122+
override predicate isBarrier(Expr e) { super.isBarrier(e) or bounded(e) }
71123
}
72124

73125
from Expr origin, VariableAccess va, string effect, PathNode sourceNode, PathNode sinkNode
74126
where
75127
taintedWithPath(origin, va, sourceNode, sinkNode) and
76-
missingGuard(va, effect) and
77-
not guardedByAssignDiv(origin)
128+
missingGuard(va, effect)
78129
select va, sourceNode, sinkNode,
79130
"$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", origin,
80131
"Uncontrolled value"

cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313

1414
import cpp
15+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
1516
import semmle.code.cpp.security.TaintTracking
1617
import TaintedWithPath
1718

@@ -27,6 +28,27 @@ predicate allocSink(Expr alloc, Expr tainted) {
2728

2829
class TaintedAllocationSizeConfiguration extends TaintTrackingConfiguration {
2930
override predicate isSink(Element tainted) { allocSink(_, tainted) }
31+
32+
override predicate isBarrier(Expr e) {
33+
super.isBarrier(e)
34+
or
35+
// There can be two separate reasons for `convertedExprMightOverflow` not holding:
36+
// 1. `e` really cannot overflow.
37+
// 2. `e` isn't analyzable.
38+
// If we didn't rule out case 2 we would place barriers on anything that isn't analyzable.
39+
(
40+
e instanceof UnaryArithmeticOperation or
41+
e instanceof BinaryArithmeticOperation or
42+
e instanceof AssignArithmeticOperation
43+
) and
44+
not convertedExprMightOverflow(e)
45+
or
46+
// Subtracting two pointers is either well-defined (and the result will likely be small), or
47+
// terribly undefined and dangerous. Here, we assume that the programmer has ensured that the
48+
// result is well-defined (i.e., the two pointers point to the same object), and thus the result
49+
// will likely be small.
50+
e = any(PointerDiffExpr diff).getAnOperand()
51+
}
3052
}
3153

3254
predicate taintedAllocSize(

cpp/ql/src/Summary/LinesOfCode.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* @description The total number of lines of C/C++ code across all files, including system headers, libraries, and auto-generated files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments.
55
* @kind metric
66
* @tags summary
7+
* lines-of-code
78
*/
89

910
import cpp

cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -748,16 +748,10 @@ private predicate modelFlow(Operand opFrom, Instruction iTo) {
748748
)
749749
or
750750
exists(int index, ReadSideEffectInstruction read |
751-
modelIn.isParameterDeref(index) and
751+
modelIn.isParameterDerefOrQualifierObject(index) and
752752
read = getSideEffectFor(call, index) and
753753
opFrom = read.getSideEffectOperand()
754754
)
755-
or
756-
exists(ReadSideEffectInstruction read |
757-
modelIn.isQualifierObject() and
758-
read = getSideEffectFor(call, -1) and
759-
opFrom = read.getSideEffectOperand()
760-
)
761755
)
762756
)
763757
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,23 @@ void test21(bool cond)
586586
if (ptr[-1] == 0) { return; } // GOOD: accesses buffer[1]
587587
}
588588

589+
void test22(bool b, const char* source) {
590+
char buffer[16];
591+
int k;
592+
for (k = 0; k <= 100; k++) {
593+
if(k < 16) {
594+
buffer[k] = 'x'; // GOOD
595+
}
596+
}
597+
598+
char dest[128];
599+
int n = b ? 1024 : 132;
600+
if (n >= 128) {
601+
return;
602+
}
603+
memcpy(dest, source, n); // GOOD
604+
}
605+
589606
int main(int argc, char *argv[])
590607
{
591608
long long arr17[19];
@@ -609,6 +626,7 @@ int main(int argc, char *argv[])
609626
test19(argc == 0);
610627
test20();
611628
test21(argc == 0);
629+
test22(argc == 0, argv[0]);
612630

613631
return 0;
614632
}

0 commit comments

Comments
 (0)