Skip to content

Commit 884c4a6

Browse files
authored
Merge pull request #19171 from geoffw0/badalloc
Rust: Query for uncontrolled allocation size
2 parents 8631371 + c821f27 commit 884c4a6

17 files changed

+1122
-12
lines changed

rust/ql/lib/codeql/rust/frameworks/libc.model.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,12 @@ extensions:
33
pack: codeql/rust-all
44
extensible: sourceModel
55
data:
6-
# Alloc
76
- ["repo:https://github.com/rust-lang/libc:libc", "::free", "Argument[0]", "pointer-invalidate", "manual"]
7+
- addsTo:
8+
pack: codeql/rust-all
9+
extensible: sinkModel
10+
data:
11+
- ["repo:https://github.com/rust-lang/libc:libc", "::malloc", "Argument[0]", "alloc-size", "manual"]
12+
- ["repo:https://github.com/rust-lang/libc:libc", "::aligned_alloc", "Argument[1]", "alloc-size", "manual"]
13+
- ["repo:https://github.com/rust-lang/libc:libc", "::calloc", "Argument[0,1]", "alloc-size", "manual"]
14+
- ["repo:https://github.com/rust-lang/libc:libc", "::realloc", "Argument[1]", "alloc-size", "manual"]

rust/ql/lib/codeql/rust/frameworks/stdlib/lang-alloc.model.yml

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
11
extensions:
2+
- addsTo:
3+
pack: codeql/rust-all
4+
extensible: sourceModel
5+
data:
6+
# Alloc
7+
- ["lang:alloc", "crate::alloc::dealloc", "Argument[0]", "pointer-invalidate", "manual"]
8+
- addsTo:
9+
pack: codeql/rust-all
10+
extensible: sinkModel
11+
data:
12+
# Alloc
13+
- ["lang:alloc", "crate::alloc::alloc", "Argument[0]", "alloc-layout", "manual"]
14+
- ["lang:alloc", "crate::alloc::alloc_zeroed", "Argument[0]", "alloc-layout", "manual"]
15+
- ["lang:alloc", "crate::alloc::realloc", "Argument[2]", "alloc-size", "manual"]
16+
- ["lang:std", "<crate::alloc::System as crate::alloc::global::GlobalAlloc>::alloc", "Argument[0]", "alloc-layout", "manual"]
17+
- ["lang:std", "<crate::alloc::System as crate::alloc::global::GlobalAlloc>::alloc_zeroed", "Argument[0]", "alloc-layout", "manual"]
18+
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::allocate", "Argument[0]", "alloc-layout", "manual"]
19+
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::allocate_zeroed", "Argument[0]", "alloc-layout", "manual"]
20+
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::grow", "Argument[2]", "alloc-layout", "manual"]
21+
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::grow_zeroed", "Argument[2]", "alloc-layout", "manual"]
22+
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::global::GlobalAlloc>::alloc", "Argument[0]", "alloc-layout", "manual"]
23+
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::global::GlobalAlloc>::alloc_zeroed", "Argument[0]", "alloc-layout", "manual"]
24+
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::allocate", "Argument[0]", "alloc-layout", "manual"]
25+
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::allocate_zeroed", "Argument[0]", "alloc-layout", "manual"]
26+
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::grow", "Argument[2]", "alloc-layout", "manual"]
27+
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::grow_zeroed", "Argument[2]", "alloc-layout", "manual"]
228
- addsTo:
329
pack: codeql/rust-all
430
extensible: summaryModel
@@ -9,9 +35,3 @@ extensions:
935
- ["lang:alloc", "<crate::string::String>::as_str", "Argument[self]", "ReturnValue", "taint", "manual"]
1036
- ["lang:alloc", "<crate::string::String>::as_bytes", "Argument[self]", "ReturnValue", "taint", "manual"]
1137
- ["lang:alloc", "<_ as crate::string::ToString>::to_string", "Argument[self]", "ReturnValue", "taint", "manual"]
12-
- addsTo:
13-
pack: codeql/rust-all
14-
extensible: sourceModel
15-
data:
16-
# Alloc
17-
- ["lang:alloc", "crate::alloc::dealloc", "Argument[0]", "pointer-invalidate", "manual"]

rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml

+15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ extensions:
1717
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::collect", "Argument[self].Element", "ReturnValue.Element", "value", "manual"]
1818
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::map", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
1919
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::for_each", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
20+
# Layout
21+
- ["lang:core", "<crate::alloc::layout::Layout>::from_size_align", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
22+
- ["lang:core", "<crate::alloc::layout::Layout>::from_size_align_unchecked", "Argument[0]", "ReturnValue", "taint", "manual"]
23+
- ["lang:core", "<crate::alloc::layout::Layout>::array", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
24+
- ["lang:core", "<crate::alloc::layout::Layout>::repeat", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
25+
- ["lang:core", "<crate::alloc::layout::Layout>::repeat", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
26+
- ["lang:core", "<crate::alloc::layout::Layout>::repeat_packed", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
27+
- ["lang:core", "<crate::alloc::layout::Layout>::repeat_packed", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
28+
- ["lang:core", "<crate::alloc::layout::Layout>::extend", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
29+
- ["lang:core", "<crate::alloc::layout::Layout>::extend", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
30+
- ["lang:core", "<crate::alloc::layout::Layout>::extend_packed", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
31+
- ["lang:core", "<crate::alloc::layout::Layout>::extend_packed", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
32+
- ["lang:core", "<crate::alloc::layout::Layout>::align_to", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
33+
- ["lang:core", "<crate::alloc::layout::Layout>::pad_to_align", "Argument[self]", "ReturnValue", "taint", "manual"]
34+
- ["lang:core", "<crate::alloc::layout::Layout>::size", "Argument[self]", "ReturnValue", "taint", "manual"]
2035
# Ptr
2136
- ["lang:core", "crate::ptr::read", "Argument[0].Reference", "ReturnValue", "value", "manual"]
2237
- ["lang:core", "crate::ptr::read_unaligned", "Argument[0].Reference", "ReturnValue", "value", "manual"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Provides classes and predicates for reasoning about uncontrolled allocation
3+
* size vulnerabilities.
4+
*/
5+
6+
import rust
7+
private import codeql.rust.Concepts
8+
private import codeql.rust.dataflow.DataFlow
9+
private import codeql.rust.dataflow.FlowSink
10+
private import codeql.rust.controlflow.ControlFlowGraph as Cfg
11+
private import codeql.rust.controlflow.CfgNodes as CfgNodes
12+
13+
/**
14+
* Provides default sources, sinks and barriers for detecting uncontrolled
15+
* allocation size vulnerabilities, as well as extension points for adding your own.
16+
*/
17+
module UncontrolledAllocationSize {
18+
/**
19+
* A data flow sink for uncontrolled allocation size vulnerabilities.
20+
*/
21+
abstract class Sink extends QuerySink::Range {
22+
override string getSinkType() { result = "UncontrolledAllocationSize" }
23+
}
24+
25+
/**
26+
* A barrier for uncontrolled allocation size vulnerabilities.
27+
*/
28+
abstract class Barrier extends DataFlow::Node { }
29+
30+
/**
31+
* A sink for uncontrolled allocation size from model data.
32+
*/
33+
private class ModelsAsDataSink extends Sink {
34+
ModelsAsDataSink() { sinkNode(this, ["alloc-size", "alloc-layout"]) }
35+
}
36+
37+
/**
38+
* A barrier for uncontrolled allocation size that is an upper bound check / guard.
39+
*/
40+
private class UpperBoundCheckBarrier extends Barrier {
41+
UpperBoundCheckBarrier() {
42+
this = DataFlow::BarrierGuard<isUpperBoundCheck/3>::getABarrierNode()
43+
}
44+
}
45+
46+
/**
47+
* Gets the operand on the "greater" (or "greater-or-equal") side
48+
* of this relational expression, that is, the side that is larger
49+
* if the overall expression evaluates to `true`; for example on
50+
* `x <= 20` this is the `20`, and on `y > 0` it is `y`.
51+
*/
52+
private Expr getGreaterOperand(BinaryExpr op) {
53+
op.getOperatorName() = ["<", "<="] and
54+
result = op.getRhs()
55+
or
56+
op.getOperatorName() = [">", ">="] and
57+
result = op.getLhs()
58+
}
59+
60+
/**
61+
* Gets the operand on the "lesser" (or "lesser-or-equal") side
62+
* of this relational expression, that is, the side that is smaller
63+
* if the overall expression evaluates to `true`; for example on
64+
* `x <= 20` this is `x`, and on `y > 0` it is the `0`.
65+
*/
66+
private Expr getLesserOperand(BinaryExpr op) {
67+
op.getOperatorName() = ["<", "<="] and
68+
result = op.getLhs()
69+
or
70+
op.getOperatorName() = [">", ">="] and
71+
result = op.getRhs()
72+
}
73+
74+
/**
75+
* Holds if comparison `g` having result `branch` indicates an upper bound for the sub-expression
76+
* `node`. For example when the comparison `x < 10` is true, we have an upper bound for `x`.
77+
*/
78+
private predicate isUpperBoundCheck(CfgNodes::AstCfgNode g, Cfg::CfgNode node, boolean branch) {
79+
exists(BinaryExpr cmp | g = cmp.getACfgNode() |
80+
node = getLesserOperand(cmp).getACfgNode() and
81+
branch = true
82+
or
83+
node = getGreaterOperand(cmp).getACfgNode() and
84+
branch = false
85+
or
86+
cmp.getOperatorName() = "==" and
87+
[cmp.getLhs(), cmp.getRhs()].getACfgNode() = node and
88+
branch = true
89+
or
90+
cmp.getOperatorName() = "!=" and
91+
[cmp.getLhs(), cmp.getRhs()].getACfgNode() = node and
92+
branch = false
93+
)
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
7+
<p>Allocating memory with a size based on user input may allow arbitrary amounts of memory to be
8+
allocated, leading to a crash or a denial-of-service (DoS) attack.</p>
9+
10+
<p>If the user input is multiplied by a constant, such as the size of a type, the result may
11+
overflow. In a build with the <code>--release</code> flag, Rust performs two's complement wrapping,
12+
with the result that less memory than expected may be allocated. This can lead to buffer overflow
13+
incidents.</p>
14+
15+
</overview>
16+
<recommendation>
17+
18+
<p>Implement a guard to limit the amount of memory that is allocated, and reject the request if
19+
the guard is not met. Ensure that any multiplications in the calculation cannot overflow, either
20+
by guarding their inputs, or using a multiplication routine such as <code>checked_mul</code> that
21+
does not wrap around.</p>
22+
23+
</recommendation>
24+
<example>
25+
26+
<p>In the following example, an arbitrary amount of memory is allocated based on user input. In
27+
addition, due to the multiplication operation, the result may overflow if a very large value is
28+
provided. This may lead to less memory being allocated than expected by other parts of the program.</p>
29+
<sample src="UncontrolledAllocationSizeBad.rs" />
30+
31+
<p>In the fixed example, the user input is checked against a maximum value. If the check fails, an
32+
error is returned, and both the multiplication and allocation do not take place.</p>
33+
<sample src="UncontrolledAllocationSizeGood.rs" />
34+
35+
</example>
36+
<references>
37+
38+
<li>The Rust Programming Language: <a href="https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow">Data Types - Integer Overflow</a>.</li>
39+
40+
</references>
41+
</qhelp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @name Uncontrolled allocation size
3+
* @description Allocating memory with a size controlled by an external user can result in
4+
* arbitrary amounts of memory being allocated, leading to a crash or a
5+
* denial-of-service (DoS) attack.
6+
* @kind path-problem
7+
* @problem.severity recommendation
8+
* @security-severity 7.5
9+
* @precision high
10+
* @id rust/uncontrolled-allocation-size
11+
* @tags reliability
12+
* security
13+
* external/cwe/cwe-770
14+
* external/cwe/cwe-789
15+
*/
16+
17+
import rust
18+
import codeql.rust.Concepts
19+
import codeql.rust.dataflow.DataFlow
20+
import codeql.rust.dataflow.TaintTracking
21+
import codeql.rust.dataflow.internal.DataFlowImpl
22+
import codeql.rust.security.UncontrolledAllocationSizeExtensions
23+
24+
/**
25+
* A taint-tracking configuration for uncontrolled allocation size vulnerabilities.
26+
*/
27+
module UncontrolledAllocationConfig implements DataFlow::ConfigSig {
28+
import UncontrolledAllocationSize
29+
30+
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
31+
32+
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
33+
34+
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier }
35+
}
36+
37+
module UncontrolledAllocationFlow = TaintTracking::Global<UncontrolledAllocationConfig>;
38+
39+
import UncontrolledAllocationFlow::PathGraph
40+
41+
from UncontrolledAllocationFlow::PathNode source, UncontrolledAllocationFlow::PathNode sink
42+
where UncontrolledAllocationFlow::flowPath(source, sink)
43+
select sink.getNode(), source, sink,
44+
"This allocation size is derived from a $@ and could allocate arbitrary amounts of memory.",
45+
source.getNode(), "user-provided value"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
fn allocate_buffer(user_input: String) -> Result<*mut u8, Error> {
3+
let num_bytes = user_input.parse::<usize>()? * std::mem::size_of::<u64>();
4+
5+
let layout = std::alloc::Layout::from_size_align(num_bytes, 1).unwrap();
6+
unsafe {
7+
let buffer = std::alloc::alloc(layout); // BAD: uncontrolled allocation size
8+
9+
Ok(buffer)
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
const BUFFER_LIMIT: usize = 10 * 1024;
3+
4+
fn allocate_buffer(user_input: String) -> Result<*mut u8, Error> {
5+
let size = user_input.parse::<usize>()?;
6+
if size > BUFFER_LIMIT {
7+
return Err("Size exceeds limit".into());
8+
}
9+
let num_bytes = size * std::mem::size_of::<u64>();
10+
11+
let layout = std::alloc::Layout::from_size_align(num_bytes, 1).unwrap();
12+
unsafe {
13+
let buffer = std::alloc::alloc(layout); // GOOD
14+
15+
Ok(buffer)
16+
}
17+
}

rust/ql/src/queries/summary/Stats.qll

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ private import codeql.rust.security.CleartextLoggingExtensions
2222
private import codeql.rust.security.CleartextTransmissionExtensions
2323
private import codeql.rust.security.SqlInjectionExtensions
2424
private import codeql.rust.security.TaintedPathExtensions
25+
private import codeql.rust.security.UncontrolledAllocationSizeExtensions
2526
private import codeql.rust.security.WeakSensitiveDataHashingExtensions
2627

2728
/**

0 commit comments

Comments
 (0)