Skip to content

Commit bc92a99

Browse files
authored
Merge pull request #19080 from geoffw0/deallocation
Rust: Query for dereferencing an invalid pointer
2 parents 49c2f97 + d47e925 commit bc92a99

18 files changed

+1255
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/rust-all
4+
extensible: sourceModel
5+
data:
6+
# Alloc
7+
- ["repo:https://github.com/rust-lang/libc:libc", "::free", "Argument[0]", "pointer-invalidate", "manual"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/rust-all
4+
extensible: summaryModel
5+
data:
6+
# Fmt
7+
- ["lang:alloc", "crate::fmt::format", "Argument[0]", "ReturnValue", "taint", "manual"]
8+
# String
9+
- ["lang:alloc", "<crate::string::String>::as_str", "Argument[self]", "ReturnValue", "taint", "manual"]
10+
- ["lang:alloc", "<crate::string::String>::as_bytes", "Argument[self]", "ReturnValue", "taint", "manual"]
11+
- ["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

+22-7
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ extensions:
33
pack: codeql/rust-all
44
extensible: summaryModel
55
data:
6-
# Fmt
7-
- ["lang:alloc", "crate::fmt::format", "Argument[0]", "ReturnValue", "taint", "manual"]
86
# Iterator
97
- ["lang:core", "<[_]>::iter", "Argument[Self].Element", "ReturnValue.Element", "value", "manual"]
108
- ["lang:core", "<[_]>::iter_mut", "Argument[Self].Element", "ReturnValue.Element", "value", "manual"]
@@ -19,7 +17,7 @@ extensions:
1917
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::collect", "Argument[self].Element", "ReturnValue.Element", "value", "manual"]
2018
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::map", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
2119
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::for_each", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
22-
# ptr
20+
# Ptr
2321
- ["lang:core", "crate::ptr::read", "Argument[0].Reference", "ReturnValue", "value", "manual"]
2422
- ["lang:core", "crate::ptr::read_unaligned", "Argument[0].Reference", "ReturnValue", "value", "manual"]
2523
- ["lang:core", "crate::ptr::read_volatile", "Argument[0].Reference", "ReturnValue", "value", "manual"]
@@ -28,7 +26,24 @@ extensions:
2826
- ["lang:core", "crate::ptr::write_volatile", "Argument[1]", "Argument[0].Reference", "value", "manual"]
2927
# Str
3028
- ["lang:core", "<str>::parse", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
31-
# String
32-
- ["lang:alloc", "<crate::string::String>::as_str", "Argument[self]", "ReturnValue", "taint", "manual"]
33-
- ["lang:alloc", "<crate::string::String>::as_bytes", "Argument[self]", "ReturnValue", "taint", "manual"]
34-
- ["lang:alloc", "<_ as crate::string::ToString>::to_string", "Argument[self]", "ReturnValue", "taint", "manual"]
29+
- addsTo:
30+
pack: codeql/rust-all
31+
extensible: sourceModel
32+
data:
33+
# Ptr
34+
- ["lang:core", "crate::ptr::drop_in_place", "Argument[0]", "pointer-invalidate", "manual"]
35+
- ["lang:core", "crate::ptr::dangling", "ReturnValue", "pointer-invalidate", "manual"]
36+
- ["lang:core", "crate::ptr::dangling_mut", "ReturnValue", "pointer-invalidate", "manual"]
37+
- ["lang:core", "crate::ptr::null", "ReturnValue", "pointer-invalidate", "manual"]
38+
- addsTo:
39+
pack: codeql/rust-all
40+
extensible: sinkModel
41+
data:
42+
# Ptr
43+
- ["lang:core", "crate::ptr::read", "Argument[0]", "pointer-access", "manual"]
44+
- ["lang:core", "crate::ptr::read_unaligned", "Argument[0]", "pointer-access", "manual"]
45+
- ["lang:core", "crate::ptr::read_volatile", "Argument[0]", "pointer-access", "manual"]
46+
- ["lang:core", "crate::ptr::write", "Argument[0]", "pointer-access", "manual"]
47+
- ["lang:core", "crate::ptr::write_bytes", "Argument[0]", "pointer-access", "manual"]
48+
- ["lang:core", "crate::ptr::write_unaligned", "Argument[0]", "pointer-access", "manual"]
49+
- ["lang:core", "crate::ptr::write_volatile", "Argument[0]", "pointer-access", "manual"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Provides classes and predicates for reasoning about accesses to invalid
3+
* pointers.
4+
*/
5+
6+
import rust
7+
private import codeql.rust.dataflow.DataFlow
8+
private import codeql.rust.dataflow.FlowSource
9+
private import codeql.rust.dataflow.FlowSink
10+
private import codeql.rust.Concepts
11+
private import codeql.rust.dataflow.internal.Node
12+
13+
/**
14+
* Provides default sources, sinks and barriers for detecting accesses to
15+
* invalid pointers, as well as extension points for adding your own.
16+
*/
17+
module AccessInvalidPointer {
18+
/**
19+
* A data flow source for invalid pointer accesses, that is, an operation
20+
* where a pointer becomes invalid.
21+
*/
22+
abstract class Source extends DataFlow::Node { }
23+
24+
/**
25+
* A data flow sink for invalid pointer accesses, that is, a pointer
26+
* dereference.
27+
*/
28+
abstract class Sink extends QuerySink::Range {
29+
override string getSinkType() { result = "AccessInvalidPointer" }
30+
}
31+
32+
/**
33+
* A barrier for invalid pointer accesses.
34+
*/
35+
abstract class Barrier extends DataFlow::Node { }
36+
37+
/**
38+
* A pointer invalidation from model data.
39+
*
40+
* Note: we don't currently support invalidation via the object itself rather than via a pointer, such as:
41+
* ```
42+
* drop(obj)
43+
* ```
44+
*/
45+
private class ModelsAsDataSource extends Source {
46+
ModelsAsDataSource() { sourceNode(this, "pointer-invalidate") }
47+
}
48+
49+
/**
50+
* A pointer access using the unary `*` operator.
51+
*/
52+
private class DereferenceSink extends Sink {
53+
DereferenceSink() {
54+
exists(PrefixExpr p | p.getOperatorName() = "*" and p.getExpr() = this.asExpr().getExpr())
55+
}
56+
}
57+
58+
/**
59+
* A pointer access from model data.
60+
*/
61+
private class ModelsAsDataSink extends Sink {
62+
ModelsAsDataSink() { sinkNode(this, "pointer-access") }
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
7+
<p>
8+
Dereferencing an invalid or dangling pointer may cause undefined behavior. Memory may be corrupted
9+
causing the program to crash or behave incorrectly, in some cases exposing the program to
10+
potential attacks.
11+
</p>
12+
13+
</overview>
14+
<recommendation>
15+
16+
<p>
17+
When dereferencing a pointer in <code>unsafe</code> code, take care that the pointer is valid and
18+
points to the intended data. Code may need to be rearranged or additional checks added to ensure
19+
safety in all circumstances. If possible, rewrite the code using safe Rust types to avoid this
20+
kind of problem altogether.
21+
</p>
22+
23+
</recommendation>
24+
<example>
25+
26+
<p>
27+
In the following example, <code>std::ptr::drop_in_place</code> is used to execute the destructor
28+
of an object. However, a pointer to that object is dereferenced later in the program, causing
29+
undefined behavior:
30+
</p>
31+
32+
<sample src="AccessInvalidPointerBad.rs" />
33+
34+
<p>
35+
In this case, undefined behavior can be avoided by rearranging the code so that the dereferencing
36+
comes before the call to <code>std::ptr::drop_in_place</code>:
37+
</p>
38+
39+
<sample src="AccessInvalidPointerGood.rs" />
40+
41+
</example>
42+
<references>
43+
44+
<li>Rust Documentation: <a href="https://doc.rust-lang.org/reference/behavior-considered-undefined.html#dangling-pointers">Behavior considered undefined &gt;&gt; Dangling pointers</a>.</li>
45+
<li>Rust Documentation: <a href="https://doc.rust-lang.org/std/ptr/index.html#safety">Module ptr - Safety</a>.</li>
46+
<li>Massachusetts Institute of Technology: <a href="https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/second-edition/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer">Unsafe Rust - Dereferencing a Raw Pointer</a>.</li>
47+
48+
</references>
49+
</qhelp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @name Access of invalid pointer
3+
* @description Dereferencing an invalid or dangling pointer causes undefined behavior and may result in memory corruption.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 7.5
7+
* @precision high
8+
* @id rust/access-invalid-pointer
9+
* @tags reliability
10+
* security
11+
* external/cwe/cwe-476
12+
* external/cwe/cwe-825
13+
*/
14+
15+
import rust
16+
import codeql.rust.dataflow.DataFlow
17+
import codeql.rust.dataflow.TaintTracking
18+
import codeql.rust.security.AccessInvalidPointerExtensions
19+
import AccessInvalidPointerFlow::PathGraph
20+
21+
/**
22+
* A data flow configuration for accesses to invalid pointers.
23+
*/
24+
module AccessInvalidPointerConfig implements DataFlow::ConfigSig {
25+
predicate isSource(DataFlow::Node node) { node instanceof AccessInvalidPointer::Source }
26+
27+
predicate isSink(DataFlow::Node node) { node instanceof AccessInvalidPointer::Sink }
28+
29+
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof AccessInvalidPointer::Barrier }
30+
31+
predicate isBarrierOut(DataFlow::Node node) {
32+
// make sinks barriers so that we only report the closest instance
33+
isSink(node)
34+
}
35+
}
36+
37+
module AccessInvalidPointerFlow = TaintTracking::Global<AccessInvalidPointerConfig>;
38+
39+
from AccessInvalidPointerFlow::PathNode sourceNode, AccessInvalidPointerFlow::PathNode sinkNode
40+
where AccessInvalidPointerFlow::flowPath(sourceNode, sinkNode)
41+
select sinkNode.getNode(), sourceNode, sinkNode,
42+
"This operation dereferences a pointer that may be $@.", sourceNode.getNode(), "invalid"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
unsafe {
3+
std::ptr::drop_in_place(ptr); // executes the destructor of `*ptr`
4+
}
5+
6+
// ...
7+
8+
unsafe {
9+
do_something(&*ptr); // BAD: dereferences `ptr`
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
unsafe {
3+
do_something(&*ptr); // GOOD: dereferences `ptr` while it is still valid
4+
}
5+
6+
// ...
7+
8+
{
9+
std::ptr::drop_in_place(ptr); // executes the destructor of `*ptr`
10+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ private import codeql.rust.Diagnostics
1515
private import codeql.rust.security.SensitiveData
1616
private import TaintReach
1717
// import all query extensions files, so that all extensions of `QuerySink` are found
18+
private import codeql.rust.security.AccessInvalidPointerExtensions
1819
private import codeql.rust.security.CleartextLoggingExtensions
1920
private import codeql.rust.security.SqlInjectionExtensions
2021
private import codeql.rust.security.WeakSensitiveDataHashingExtensions

rust/ql/test/query-tests/security/CWE-020/RegexInjection.expected

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
| main.rs:6:25:6:30 | &regex | main.rs:4:20:4:32 | ...::var | main.rs:6:25:6:30 | &regex | This regular expression is constructed from a $@. | main.rs:4:20:4:32 | ...::var | user-provided value |
33
edges
44
| main.rs:4:9:4:16 | username | main.rs:5:25:5:44 | MacroExpr | provenance | |
5-
| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:64 |
6-
| main.rs:4:20:4:40 | ...::var(...) [Ok] | main.rs:4:20:4:66 | ... .unwrap_or(...) | provenance | MaD:1627 |
5+
| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:65 |
6+
| main.rs:4:20:4:40 | ...::var(...) [Ok] | main.rs:4:20:4:66 | ... .unwrap_or(...) | provenance | MaD:1640 |
77
| main.rs:4:20:4:66 | ... .unwrap_or(...) | main.rs:4:9:4:16 | username | provenance | |
88
| main.rs:5:9:5:13 | regex | main.rs:6:26:6:30 | regex | provenance | |
99
| main.rs:5:17:5:45 | res | main.rs:5:25:5:44 | { ... } | provenance | |
1010
| main.rs:5:25:5:44 | ...::format(...) | main.rs:5:17:5:45 | res | provenance | |
1111
| main.rs:5:25:5:44 | ...::must_use(...) | main.rs:5:9:5:13 | regex | provenance | |
12-
| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:100 |
13-
| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:3050 |
12+
| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:101 |
13+
| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:3063 |
1414
| main.rs:6:26:6:30 | regex | main.rs:6:25:6:30 | &regex | provenance | |
1515
nodes
1616
| main.rs:4:9:4:16 | username | semmle.label | username |

0 commit comments

Comments
 (0)