mirror of
https://github.com/github/codeql.git
synced 2026-04-24 16:25:15 +02:00
Merge branch 'main' into blockon
This commit is contained in:
7
rust/ql/lib/codeql/rust/frameworks/libc.model.yml
Normal file
7
rust/ql/lib/codeql/rust/frameworks/libc.model.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/rust-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
# Alloc
|
||||
- ["repo:https://github.com/rust-lang/libc:libc", "::free", "Argument[0]", "pointer-invalidate", "manual"]
|
||||
@@ -0,0 +1,17 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/rust-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
# Fmt
|
||||
- ["lang:alloc", "crate::fmt::format", "Argument[0]", "ReturnValue", "taint", "manual"]
|
||||
# String
|
||||
- ["lang:alloc", "<crate::string::String>::as_str", "Argument[self]", "ReturnValue", "taint", "manual"]
|
||||
- ["lang:alloc", "<crate::string::String>::as_bytes", "Argument[self]", "ReturnValue", "taint", "manual"]
|
||||
- ["lang:alloc", "<_ as crate::string::ToString>::to_string", "Argument[self]", "ReturnValue", "taint", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/rust-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
# Alloc
|
||||
- ["lang:alloc", "crate::alloc::dealloc", "Argument[0]", "pointer-invalidate", "manual"]
|
||||
@@ -3,8 +3,6 @@ extensions:
|
||||
pack: codeql/rust-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
# Fmt
|
||||
- ["lang:alloc", "crate::fmt::format", "Argument[0]", "ReturnValue", "taint", "manual"]
|
||||
# Iterator
|
||||
- ["lang:core", "<[_]>::iter", "Argument[Self].Element", "ReturnValue.Element", "value", "manual"]
|
||||
- ["lang:core", "<[_]>::iter_mut", "Argument[Self].Element", "ReturnValue.Element", "value", "manual"]
|
||||
@@ -19,7 +17,7 @@ extensions:
|
||||
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::collect", "Argument[self].Element", "ReturnValue.Element", "value", "manual"]
|
||||
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::map", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
|
||||
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::for_each", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
|
||||
# ptr
|
||||
# Ptr
|
||||
- ["lang:core", "crate::ptr::read", "Argument[0].Reference", "ReturnValue", "value", "manual"]
|
||||
- ["lang:core", "crate::ptr::read_unaligned", "Argument[0].Reference", "ReturnValue", "value", "manual"]
|
||||
- ["lang:core", "crate::ptr::read_volatile", "Argument[0].Reference", "ReturnValue", "value", "manual"]
|
||||
@@ -28,7 +26,24 @@ extensions:
|
||||
- ["lang:core", "crate::ptr::write_volatile", "Argument[1]", "Argument[0].Reference", "value", "manual"]
|
||||
# Str
|
||||
- ["lang:core", "<str>::parse", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
|
||||
# String
|
||||
- ["lang:alloc", "<crate::string::String>::as_str", "Argument[self]", "ReturnValue", "taint", "manual"]
|
||||
- ["lang:alloc", "<crate::string::String>::as_bytes", "Argument[self]", "ReturnValue", "taint", "manual"]
|
||||
- ["lang:alloc", "<_ as crate::string::ToString>::to_string", "Argument[self]", "ReturnValue", "taint", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/rust-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
# Ptr
|
||||
- ["lang:core", "crate::ptr::drop_in_place", "Argument[0]", "pointer-invalidate", "manual"]
|
||||
- ["lang:core", "crate::ptr::dangling", "ReturnValue", "pointer-invalidate", "manual"]
|
||||
- ["lang:core", "crate::ptr::dangling_mut", "ReturnValue", "pointer-invalidate", "manual"]
|
||||
- ["lang:core", "crate::ptr::null", "ReturnValue", "pointer-invalidate", "manual"]
|
||||
- addsTo:
|
||||
pack: codeql/rust-all
|
||||
extensible: sinkModel
|
||||
data:
|
||||
# Ptr
|
||||
- ["lang:core", "crate::ptr::read", "Argument[0]", "pointer-access", "manual"]
|
||||
- ["lang:core", "crate::ptr::read_unaligned", "Argument[0]", "pointer-access", "manual"]
|
||||
- ["lang:core", "crate::ptr::read_volatile", "Argument[0]", "pointer-access", "manual"]
|
||||
- ["lang:core", "crate::ptr::write", "Argument[0]", "pointer-access", "manual"]
|
||||
- ["lang:core", "crate::ptr::write_bytes", "Argument[0]", "pointer-access", "manual"]
|
||||
- ["lang:core", "crate::ptr::write_unaligned", "Argument[0]", "pointer-access", "manual"]
|
||||
- ["lang:core", "crate::ptr::write_volatile", "Argument[0]", "pointer-access", "manual"]
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about accesses to invalid
|
||||
* pointers.
|
||||
*/
|
||||
|
||||
import rust
|
||||
private import codeql.rust.dataflow.DataFlow
|
||||
private import codeql.rust.dataflow.FlowSource
|
||||
private import codeql.rust.dataflow.FlowSink
|
||||
private import codeql.rust.Concepts
|
||||
private import codeql.rust.dataflow.internal.Node
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and barriers for detecting accesses to
|
||||
* invalid pointers, as well as extension points for adding your own.
|
||||
*/
|
||||
module AccessInvalidPointer {
|
||||
/**
|
||||
* A data flow source for invalid pointer accesses, that is, an operation
|
||||
* where a pointer becomes invalid.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for invalid pointer accesses, that is, a pointer
|
||||
* dereference.
|
||||
*/
|
||||
abstract class Sink extends QuerySink::Range {
|
||||
override string getSinkType() { result = "AccessInvalidPointer" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A barrier for invalid pointer accesses.
|
||||
*/
|
||||
abstract class Barrier extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A pointer invalidation from model data.
|
||||
*
|
||||
* Note: we don't currently support invalidation via the object itself rather than via a pointer, such as:
|
||||
* ```
|
||||
* drop(obj)
|
||||
* ```
|
||||
*/
|
||||
private class ModelsAsDataSource extends Source {
|
||||
ModelsAsDataSource() { sourceNode(this, "pointer-invalidate") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A pointer access using the unary `*` operator.
|
||||
*/
|
||||
private class DereferenceSink extends Sink {
|
||||
DereferenceSink() {
|
||||
exists(PrefixExpr p | p.getOperatorName() = "*" and p.getExpr() = this.asExpr().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pointer access from model data.
|
||||
*/
|
||||
private class ModelsAsDataSink extends Sink {
|
||||
ModelsAsDataSink() { sinkNode(this, "pointer-access") }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
Dereferencing an invalid or dangling pointer may cause undefined behavior. Memory may be corrupted
|
||||
causing the program to crash or behave incorrectly, in some cases exposing the program to
|
||||
potential attacks.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
When dereferencing a pointer in <code>unsafe</code> code, take care that the pointer is valid and
|
||||
points to the intended data. Code may need to be rearranged or additional checks added to ensure
|
||||
safety in all circumstances. If possible, rewrite the code using safe Rust types to avoid this
|
||||
kind of problem altogether.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the following example, <code>std::ptr::drop_in_place</code> is used to execute the destructor
|
||||
of an object. However, a pointer to that object is dereferenced later in the program, causing
|
||||
undefined behavior:
|
||||
</p>
|
||||
|
||||
<sample src="AccessInvalidPointerBad.rs" />
|
||||
|
||||
<p>
|
||||
In this case, undefined behavior can be avoided by rearranging the code so that the dereferencing
|
||||
comes before the call to <code>std::ptr::drop_in_place</code>:
|
||||
</p>
|
||||
|
||||
<sample src="AccessInvalidPointerGood.rs" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Rust Documentation: <a href="https://doc.rust-lang.org/reference/behavior-considered-undefined.html#dangling-pointers">Behavior considered undefined >> Dangling pointers</a>.</li>
|
||||
<li>Rust Documentation: <a href="https://doc.rust-lang.org/std/ptr/index.html#safety">Module ptr - Safety</a>.</li>
|
||||
<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>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
42
rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql
Normal file
42
rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @name Access of invalid pointer
|
||||
* @description Dereferencing an invalid or dangling pointer causes undefined behavior and may result in memory corruption.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id rust/access-invalid-pointer
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-476
|
||||
* external/cwe/cwe-825
|
||||
*/
|
||||
|
||||
import rust
|
||||
import codeql.rust.dataflow.DataFlow
|
||||
import codeql.rust.dataflow.TaintTracking
|
||||
import codeql.rust.security.AccessInvalidPointerExtensions
|
||||
import AccessInvalidPointerFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A data flow configuration for accesses to invalid pointers.
|
||||
*/
|
||||
module AccessInvalidPointerConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node instanceof AccessInvalidPointer::Source }
|
||||
|
||||
predicate isSink(DataFlow::Node node) { node instanceof AccessInvalidPointer::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof AccessInvalidPointer::Barrier }
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node) {
|
||||
// make sinks barriers so that we only report the closest instance
|
||||
isSink(node)
|
||||
}
|
||||
}
|
||||
|
||||
module AccessInvalidPointerFlow = TaintTracking::Global<AccessInvalidPointerConfig>;
|
||||
|
||||
from AccessInvalidPointerFlow::PathNode sourceNode, AccessInvalidPointerFlow::PathNode sinkNode
|
||||
where AccessInvalidPointerFlow::flowPath(sourceNode, sinkNode)
|
||||
select sinkNode.getNode(), sourceNode, sinkNode,
|
||||
"This operation dereferences a pointer that may be $@.", sourceNode.getNode(), "invalid"
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
unsafe {
|
||||
std::ptr::drop_in_place(ptr); // executes the destructor of `*ptr`
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
unsafe {
|
||||
do_something(&*ptr); // BAD: dereferences `ptr`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
unsafe {
|
||||
do_something(&*ptr); // GOOD: dereferences `ptr` while it is still valid
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
{
|
||||
std::ptr::drop_in_place(ptr); // executes the destructor of `*ptr`
|
||||
}
|
||||
@@ -15,6 +15,7 @@ private import codeql.rust.Diagnostics
|
||||
private import codeql.rust.security.SensitiveData
|
||||
private import TaintReach
|
||||
// import all query extensions files, so that all extensions of `QuerySink` are found
|
||||
private import codeql.rust.security.AccessInvalidPointerExtensions
|
||||
private import codeql.rust.security.CleartextLoggingExtensions
|
||||
private import codeql.rust.security.SqlInjectionExtensions
|
||||
private import codeql.rust.security.WeakSensitiveDataHashingExtensions
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
| main.rs:6:25:6:30 | ®ex | main.rs:4:20:4:32 | ...::var | main.rs:6:25:6:30 | ®ex | This regular expression is constructed from a $@. | main.rs:4:20:4:32 | ...::var | user-provided value |
|
||||
edges
|
||||
| main.rs:4:9:4:16 | username | main.rs:5:25:5:44 | MacroExpr | provenance | |
|
||||
| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:65 |
|
||||
| main.rs:4:20:4:40 | ...::var(...) [Ok] | main.rs:4:20:4:66 | ... .unwrap_or(...) | provenance | MaD:1628 |
|
||||
| main.rs:4:20:4:32 | ...::var | main.rs:4:20:4:40 | ...::var(...) [Ok] | provenance | Src:MaD:66 |
|
||||
| main.rs:4:20:4:40 | ...::var(...) [Ok] | main.rs:4:20:4:66 | ... .unwrap_or(...) | provenance | MaD:1641 |
|
||||
| main.rs:4:20:4:66 | ... .unwrap_or(...) | main.rs:4:9:4:16 | username | provenance | |
|
||||
| main.rs:5:9:5:13 | regex | main.rs:6:26:6:30 | regex | provenance | |
|
||||
| main.rs:5:17:5:45 | res | main.rs:5:25:5:44 | { ... } | provenance | |
|
||||
| main.rs:5:25:5:44 | ...::format(...) | main.rs:5:17:5:45 | res | provenance | |
|
||||
| main.rs:5:25:5:44 | ...::must_use(...) | main.rs:5:9:5:13 | regex | provenance | |
|
||||
| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:101 |
|
||||
| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:3051 |
|
||||
| main.rs:5:25:5:44 | MacroExpr | main.rs:5:25:5:44 | ...::format(...) | provenance | MaD:102 |
|
||||
| main.rs:5:25:5:44 | { ... } | main.rs:5:25:5:44 | ...::must_use(...) | provenance | MaD:3064 |
|
||||
| main.rs:6:26:6:30 | regex | main.rs:6:25:6:30 | ®ex | provenance | |
|
||||
nodes
|
||||
| main.rs:4:9:4:16 | username | semmle.label | username |
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#select
|
||||
| deallocation.rs:26:15:26:16 | m1 | deallocation.rs:20:3:20:21 | ...::dealloc | deallocation.rs:26:15:26:16 | m1 | This operation dereferences a pointer that may be $@. | deallocation.rs:20:3:20:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:37:14:37:33 | ...::read::<...> | deallocation.rs:20:3:20:21 | ...::dealloc | deallocation.rs:37:14:37:33 | ...::read::<...> | This operation dereferences a pointer that may be $@. | deallocation.rs:20:3:20:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:44:6:44:7 | m1 | deallocation.rs:20:3:20:21 | ...::dealloc | deallocation.rs:44:6:44:7 | m1 | This operation dereferences a pointer that may be $@. | deallocation.rs:20:3:20:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:49:5:49:25 | ...::write::<...> | deallocation.rs:20:3:20:21 | ...::dealloc | deallocation.rs:49:5:49:25 | ...::write::<...> | This operation dereferences a pointer that may be $@. | deallocation.rs:20:3:20:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:76:16:76:17 | m2 | deallocation.rs:70:3:70:21 | ...::dealloc | deallocation.rs:76:16:76:17 | m2 | This operation dereferences a pointer that may be $@. | deallocation.rs:70:3:70:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:81:16:81:17 | m2 | deallocation.rs:70:3:70:21 | ...::dealloc | deallocation.rs:81:16:81:17 | m2 | This operation dereferences a pointer that may be $@. | deallocation.rs:70:3:70:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:86:7:86:8 | m2 | deallocation.rs:70:3:70:21 | ...::dealloc | deallocation.rs:86:7:86:8 | m2 | This operation dereferences a pointer that may be $@. | deallocation.rs:70:3:70:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:90:7:90:8 | m2 | deallocation.rs:70:3:70:21 | ...::dealloc | deallocation.rs:90:7:90:8 | m2 | This operation dereferences a pointer that may be $@. | deallocation.rs:70:3:70:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:95:5:95:31 | ...::write::<...> | deallocation.rs:70:3:70:21 | ...::dealloc | deallocation.rs:95:5:95:31 | ...::write::<...> | This operation dereferences a pointer that may be $@. | deallocation.rs:70:3:70:21 | ...::dealloc | invalid |
|
||||
| deallocation.rs:115:13:115:18 | my_ptr | deallocation.rs:112:3:112:12 | ...::free | deallocation.rs:115:13:115:18 | my_ptr | This operation dereferences a pointer that may be $@. | deallocation.rs:112:3:112:12 | ...::free | invalid |
|
||||
| deallocation.rs:130:14:130:15 | p1 | deallocation.rs:123:23:123:40 | ...::dangling | deallocation.rs:130:14:130:15 | p1 | This operation dereferences a pointer that may be $@. | deallocation.rs:123:23:123:40 | ...::dangling | invalid |
|
||||
| deallocation.rs:131:14:131:15 | p2 | deallocation.rs:124:21:124:42 | ...::dangling_mut | deallocation.rs:131:14:131:15 | p2 | This operation dereferences a pointer that may be $@. | deallocation.rs:124:21:124:42 | ...::dangling_mut | invalid |
|
||||
| deallocation.rs:132:14:132:15 | p3 | deallocation.rs:125:23:125:36 | ...::null | deallocation.rs:132:14:132:15 | p3 | This operation dereferences a pointer that may be $@. | deallocation.rs:125:23:125:36 | ...::null | invalid |
|
||||
| deallocation.rs:180:15:180:16 | p1 | deallocation.rs:176:3:176:25 | ...::drop_in_place | deallocation.rs:180:15:180:16 | p1 | This operation dereferences a pointer that may be $@. | deallocation.rs:176:3:176:25 | ...::drop_in_place | invalid |
|
||||
| deallocation.rs:248:18:248:20 | ptr | deallocation.rs:242:3:242:25 | ...::drop_in_place | deallocation.rs:248:18:248:20 | ptr | This operation dereferences a pointer that may be $@. | deallocation.rs:242:3:242:25 | ...::drop_in_place | invalid |
|
||||
edges
|
||||
| deallocation.rs:20:3:20:21 | ...::dealloc | deallocation.rs:20:23:20:24 | [post] m1 | provenance | Src:MaD:3 MaD:3 |
|
||||
| deallocation.rs:20:23:20:24 | [post] m1 | deallocation.rs:26:15:26:16 | m1 | provenance | |
|
||||
| deallocation.rs:20:23:20:24 | [post] m1 | deallocation.rs:37:35:37:36 | m1 | provenance | |
|
||||
| deallocation.rs:20:23:20:24 | [post] m1 | deallocation.rs:44:6:44:7 | m1 | provenance | |
|
||||
| deallocation.rs:20:23:20:24 | [post] m1 | deallocation.rs:49:27:49:28 | m1 | provenance | |
|
||||
| deallocation.rs:37:35:37:36 | m1 | deallocation.rs:37:14:37:33 | ...::read::<...> | provenance | MaD:1 Sink:MaD:1 |
|
||||
| deallocation.rs:49:27:49:28 | m1 | deallocation.rs:49:5:49:25 | ...::write::<...> | provenance | MaD:2 Sink:MaD:2 |
|
||||
| deallocation.rs:70:3:70:21 | ...::dealloc | deallocation.rs:70:23:70:35 | [post] m2 as ... | provenance | Src:MaD:3 MaD:3 |
|
||||
| deallocation.rs:70:23:70:35 | [post] m2 as ... | deallocation.rs:76:16:76:17 | m2 | provenance | |
|
||||
| deallocation.rs:70:23:70:35 | [post] m2 as ... | deallocation.rs:81:16:81:17 | m2 | provenance | |
|
||||
| deallocation.rs:70:23:70:35 | [post] m2 as ... | deallocation.rs:86:7:86:8 | m2 | provenance | |
|
||||
| deallocation.rs:70:23:70:35 | [post] m2 as ... | deallocation.rs:90:7:90:8 | m2 | provenance | |
|
||||
| deallocation.rs:70:23:70:35 | [post] m2 as ... | deallocation.rs:95:33:95:34 | m2 | provenance | |
|
||||
| deallocation.rs:95:33:95:34 | m2 | deallocation.rs:95:5:95:31 | ...::write::<...> | provenance | MaD:2 Sink:MaD:2 |
|
||||
| deallocation.rs:112:3:112:12 | ...::free | deallocation.rs:112:14:112:40 | [post] my_ptr as ... | provenance | Src:MaD:8 MaD:8 |
|
||||
| deallocation.rs:112:14:112:40 | [post] my_ptr as ... | deallocation.rs:115:13:115:18 | my_ptr | provenance | |
|
||||
| deallocation.rs:123:6:123:7 | p1 | deallocation.rs:130:14:130:15 | p1 | provenance | |
|
||||
| deallocation.rs:123:23:123:40 | ...::dangling | deallocation.rs:123:23:123:42 | ...::dangling(...) | provenance | Src:MaD:4 MaD:4 |
|
||||
| deallocation.rs:123:23:123:42 | ...::dangling(...) | deallocation.rs:123:6:123:7 | p1 | provenance | |
|
||||
| deallocation.rs:124:6:124:7 | p2 | deallocation.rs:131:14:131:15 | p2 | provenance | |
|
||||
| deallocation.rs:124:21:124:42 | ...::dangling_mut | deallocation.rs:124:21:124:44 | ...::dangling_mut(...) | provenance | Src:MaD:5 MaD:5 |
|
||||
| deallocation.rs:124:21:124:44 | ...::dangling_mut(...) | deallocation.rs:124:6:124:7 | p2 | provenance | |
|
||||
| deallocation.rs:125:6:125:7 | p3 | deallocation.rs:132:14:132:15 | p3 | provenance | |
|
||||
| deallocation.rs:125:23:125:36 | ...::null | deallocation.rs:125:23:125:38 | ...::null(...) | provenance | Src:MaD:7 MaD:7 |
|
||||
| deallocation.rs:125:23:125:38 | ...::null(...) | deallocation.rs:125:6:125:7 | p3 | provenance | |
|
||||
| deallocation.rs:176:3:176:25 | ...::drop_in_place | deallocation.rs:176:27:176:28 | [post] p1 | provenance | Src:MaD:6 MaD:6 |
|
||||
| deallocation.rs:176:27:176:28 | [post] p1 | deallocation.rs:180:15:180:16 | p1 | provenance | |
|
||||
| deallocation.rs:242:3:242:25 | ...::drop_in_place | deallocation.rs:242:27:242:29 | [post] ptr | provenance | Src:MaD:6 MaD:6 |
|
||||
| deallocation.rs:242:27:242:29 | [post] ptr | deallocation.rs:248:18:248:20 | ptr | provenance | |
|
||||
models
|
||||
| 1 | Sink: lang:core; crate::ptr::read; pointer-access; Argument[0] |
|
||||
| 2 | Sink: lang:core; crate::ptr::write; pointer-access; Argument[0] |
|
||||
| 3 | Source: lang:alloc; crate::alloc::dealloc; pointer-invalidate; Argument[0] |
|
||||
| 4 | Source: lang:core; crate::ptr::dangling; pointer-invalidate; ReturnValue |
|
||||
| 5 | Source: lang:core; crate::ptr::dangling_mut; pointer-invalidate; ReturnValue |
|
||||
| 6 | Source: lang:core; crate::ptr::drop_in_place; pointer-invalidate; Argument[0] |
|
||||
| 7 | Source: lang:core; crate::ptr::null; pointer-invalidate; ReturnValue |
|
||||
| 8 | Source: repo:https://github.com/rust-lang/libc:libc; ::free; pointer-invalidate; Argument[0] |
|
||||
nodes
|
||||
| deallocation.rs:20:3:20:21 | ...::dealloc | semmle.label | ...::dealloc |
|
||||
| deallocation.rs:20:23:20:24 | [post] m1 | semmle.label | [post] m1 |
|
||||
| deallocation.rs:26:15:26:16 | m1 | semmle.label | m1 |
|
||||
| deallocation.rs:37:14:37:33 | ...::read::<...> | semmle.label | ...::read::<...> |
|
||||
| deallocation.rs:37:35:37:36 | m1 | semmle.label | m1 |
|
||||
| deallocation.rs:44:6:44:7 | m1 | semmle.label | m1 |
|
||||
| deallocation.rs:49:5:49:25 | ...::write::<...> | semmle.label | ...::write::<...> |
|
||||
| deallocation.rs:49:27:49:28 | m1 | semmle.label | m1 |
|
||||
| deallocation.rs:70:3:70:21 | ...::dealloc | semmle.label | ...::dealloc |
|
||||
| deallocation.rs:70:23:70:35 | [post] m2 as ... | semmle.label | [post] m2 as ... |
|
||||
| deallocation.rs:76:16:76:17 | m2 | semmle.label | m2 |
|
||||
| deallocation.rs:81:16:81:17 | m2 | semmle.label | m2 |
|
||||
| deallocation.rs:86:7:86:8 | m2 | semmle.label | m2 |
|
||||
| deallocation.rs:90:7:90:8 | m2 | semmle.label | m2 |
|
||||
| deallocation.rs:95:5:95:31 | ...::write::<...> | semmle.label | ...::write::<...> |
|
||||
| deallocation.rs:95:33:95:34 | m2 | semmle.label | m2 |
|
||||
| deallocation.rs:112:3:112:12 | ...::free | semmle.label | ...::free |
|
||||
| deallocation.rs:112:14:112:40 | [post] my_ptr as ... | semmle.label | [post] my_ptr as ... |
|
||||
| deallocation.rs:115:13:115:18 | my_ptr | semmle.label | my_ptr |
|
||||
| deallocation.rs:123:6:123:7 | p1 | semmle.label | p1 |
|
||||
| deallocation.rs:123:23:123:40 | ...::dangling | semmle.label | ...::dangling |
|
||||
| deallocation.rs:123:23:123:42 | ...::dangling(...) | semmle.label | ...::dangling(...) |
|
||||
| deallocation.rs:124:6:124:7 | p2 | semmle.label | p2 |
|
||||
| deallocation.rs:124:21:124:42 | ...::dangling_mut | semmle.label | ...::dangling_mut |
|
||||
| deallocation.rs:124:21:124:44 | ...::dangling_mut(...) | semmle.label | ...::dangling_mut(...) |
|
||||
| deallocation.rs:125:6:125:7 | p3 | semmle.label | p3 |
|
||||
| deallocation.rs:125:23:125:36 | ...::null | semmle.label | ...::null |
|
||||
| deallocation.rs:125:23:125:38 | ...::null(...) | semmle.label | ...::null(...) |
|
||||
| deallocation.rs:130:14:130:15 | p1 | semmle.label | p1 |
|
||||
| deallocation.rs:131:14:131:15 | p2 | semmle.label | p2 |
|
||||
| deallocation.rs:132:14:132:15 | p3 | semmle.label | p3 |
|
||||
| deallocation.rs:176:3:176:25 | ...::drop_in_place | semmle.label | ...::drop_in_place |
|
||||
| deallocation.rs:176:27:176:28 | [post] p1 | semmle.label | [post] p1 |
|
||||
| deallocation.rs:180:15:180:16 | p1 | semmle.label | p1 |
|
||||
| deallocation.rs:242:3:242:25 | ...::drop_in_place | semmle.label | ...::drop_in_place |
|
||||
| deallocation.rs:242:27:242:29 | [post] ptr | semmle.label | [post] ptr |
|
||||
| deallocation.rs:248:18:248:20 | ptr | semmle.label | ptr |
|
||||
subpaths
|
||||
@@ -0,0 +1,4 @@
|
||||
query: queries/security/CWE-825/AccessInvalidPointer.ql
|
||||
postprocess:
|
||||
- utils/test/PrettyPrintModels.ql
|
||||
- utils/test/InlineExpectationsTestQuery.ql
|
||||
311
rust/ql/test/query-tests/security/CWE-825/deallocation.rs
Normal file
311
rust/ql/test/query-tests/security/CWE-825/deallocation.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
|
||||
// --- std::alloc ---
|
||||
|
||||
pub fn test_alloc(mode: i32) {
|
||||
let layout = std::alloc::Layout::new::<i64>();
|
||||
unsafe {
|
||||
let m1 = std::alloc::alloc(layout); // *mut u8
|
||||
let m2 = m1 as *mut i64; // *mut i64
|
||||
*m2 = 1; // GOOD
|
||||
|
||||
let v1 = *m1; // GOOD
|
||||
let v2 = *m2; // GOOD
|
||||
let v3 = std::ptr::read::<u8>(m1); // GOOD
|
||||
let v4 = std::ptr::read::<i64>(m2); // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
println!(" v3 = {v3}");
|
||||
println!(" v4 = {v4}");
|
||||
|
||||
std::alloc::dealloc(m1, layout); // $ Source=dealloc
|
||||
// (m1, m2 are now dangling)
|
||||
|
||||
match mode {
|
||||
0 => {
|
||||
// reads
|
||||
let v5 = *m1; // $ Alert[rust/access-invalid-pointer]=dealloc
|
||||
let v6 = *m2; // $ MISSING: Alert
|
||||
println!(" v5 = {v5} (!)"); // corrupt in practice
|
||||
println!(" v6 = {v6} (!)"); // corrupt in practice
|
||||
|
||||
// test repeat reads (we don't want lots of very similar results for the same dealloc)
|
||||
let v5b = *m1;
|
||||
let v5c = *m1;
|
||||
},
|
||||
100 => {
|
||||
// more reads
|
||||
let v7 = std::ptr::read::<u8>(m1); // $ Alert[rust/access-invalid-pointer]=dealloc
|
||||
let v8 = std::ptr::read::<i64>(m2); // $ MISSING: Alert
|
||||
println!(" v7 = {v7} (!)"); // corrupt in practice
|
||||
println!(" v8 = {v8} (!)"); // corrupt in practice
|
||||
},
|
||||
101 => {
|
||||
// writes
|
||||
*m1 = 2; // $ Alert[rust/access-invalid-pointer]=dealloc
|
||||
*m2 = 3; // $ MISSING: Alert
|
||||
},
|
||||
102 => {
|
||||
// more writes
|
||||
std::ptr::write::<u8>(m1, 4); // $ Alert[rust/access-invalid-pointer]=dealloc
|
||||
std::ptr::write::<i64>(m2, 5); // $ MISSING: Alert
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_alloc_array(mode: i32) {
|
||||
let layout = std::alloc::Layout::new::<[u8; 10]>();
|
||||
unsafe {
|
||||
let m1 = std::alloc::alloc(layout);
|
||||
let m2 = m1 as *mut [u8; 10];
|
||||
(*m2)[0] = 4; // GOOD
|
||||
(*m2)[1] = 5; // GOOD
|
||||
|
||||
let v1 = (*m2)[0]; // GOOD
|
||||
let v2 = (*m2)[1]; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
|
||||
std::alloc::dealloc(m2 as *mut u8, layout); // $ Source=dealloc_array
|
||||
// m1, m2 are now dangling
|
||||
|
||||
match mode {
|
||||
0 => {
|
||||
// read
|
||||
let v3 = (*m2)[0]; // $ Alert[rust/access-invalid-pointer]=dealloc_array
|
||||
println!(" v3 = {v3} (!)"); // corrupt in practice
|
||||
},
|
||||
110 => {
|
||||
// another read
|
||||
let v4 = (*m2)[1]; // $ Alert[rust/access-invalid-pointer]=dealloc_array
|
||||
println!(" v4 = {v4} (!)"); // corrupt in practice
|
||||
},
|
||||
111 => {
|
||||
// write
|
||||
(*m2)[0] = 3; // $ Alert[rust/access-invalid-pointer]=dealloc_array
|
||||
},
|
||||
112 => {
|
||||
// another write
|
||||
(*m2)[1] = 4; // $ Alert[rust/access-invalid-pointer]=dealloc_array
|
||||
},
|
||||
113 => {
|
||||
// more writes
|
||||
std::ptr::write::<u8>(m1, 5); // $ MISSING: Alert
|
||||
std::ptr::write::<[u8; 10]>(m2, [6; 10]); // $ Alert[rust/access-invalid-pointer]=dealloc_array
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- libc::malloc ---
|
||||
|
||||
pub fn test_libc() {
|
||||
unsafe {
|
||||
let my_ptr = libc::malloc(256) as *mut i64;
|
||||
*my_ptr = 10;
|
||||
|
||||
let v1 = *my_ptr; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
|
||||
libc::free(my_ptr as *mut libc::c_void); // $ Source=free
|
||||
// (my_ptr is now dangling)
|
||||
|
||||
let v2 = *my_ptr; // $ Alert[rust/access-invalid-pointer]=free
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
}
|
||||
}
|
||||
|
||||
// --- std::ptr ---
|
||||
|
||||
pub fn test_ptr_invalid(mode: i32) {
|
||||
let p1: *const i64 = std::ptr::dangling(); // $ Source=dangling
|
||||
let p2: *mut i64 = std::ptr::dangling_mut(); // $ Source=dangling_mut
|
||||
let p3: *const i64 = std::ptr::null(); // $ Source=null
|
||||
|
||||
if mode == 120 {
|
||||
unsafe {
|
||||
// (a segmentation fault occurs in the code below)
|
||||
let v1 = *p1; // $ Alert[rust/access-invalid-pointer]=dangling
|
||||
let v2 = *p2; // $ Alert[rust/access-invalid-pointer]=dangling_mut
|
||||
let v3 = *p3; // $ Alert[rust/access-invalid-pointer]=null
|
||||
println!(" v1 = {v1} (!)");
|
||||
println!(" v2 = {v2} (!)");
|
||||
println!(" v3 = {v3} (!)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- drop ---
|
||||
|
||||
struct MyBuffer {
|
||||
data: Vec<u8>
|
||||
}
|
||||
|
||||
pub fn test_drop() {
|
||||
let my_buffer = MyBuffer { data: vec!(1, 2, 3) };
|
||||
let p1 = std::ptr::addr_of!(my_buffer);
|
||||
|
||||
unsafe {
|
||||
let v1 = (*p1).data[0]; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
}
|
||||
|
||||
drop(my_buffer); // explicitly destructs the `my_buffer` variable
|
||||
|
||||
unsafe {
|
||||
let v2 = (*p1).data[0]; // $ MISSING: Alert
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_ptr_drop(mode: i32) {
|
||||
let layout = std::alloc::Layout::new::<Vec<i64>>();
|
||||
unsafe {
|
||||
let p1 = std::alloc::alloc(layout) as *mut Vec<i64>;
|
||||
let p2 = p1;
|
||||
|
||||
*p1 = vec!(1, 2, 3);
|
||||
|
||||
let v1 = (*p1)[0]; // GOOD
|
||||
let v2 = (*p2)[0]; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
|
||||
std::ptr::drop_in_place(p1); // $ Source=drop_in_place
|
||||
// explicitly destructs the pointed-to `m2`
|
||||
|
||||
if mode == 1 {
|
||||
let v3 = (*p1)[0]; // $ Alert[rust/access-invalid-pointer]=drop_in_place
|
||||
println!(" v3 = {v3} (!)"); // corrupt in practice
|
||||
}
|
||||
if mode == 130 {
|
||||
let v4 = (*p2)[0]; // $ MISSING: Alert
|
||||
println!(" v4 = {v4} (!)"); // corrupt in practice
|
||||
}
|
||||
|
||||
let p3 = std::alloc::alloc(layout) as *mut Vec<i64>;
|
||||
std::ptr::drop_in_place((*p3).as_mut_ptr()); // GOOD
|
||||
}
|
||||
}
|
||||
|
||||
struct MyDropBuffer {
|
||||
ptr: *mut u8,
|
||||
}
|
||||
|
||||
impl MyDropBuffer {
|
||||
unsafe fn new() -> MyDropBuffer {
|
||||
let layout = std::alloc::Layout::from_size_align(1024, 1).unwrap();
|
||||
|
||||
MyDropBuffer {
|
||||
ptr: std::alloc::alloc(layout),
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MyDropBuffer {
|
||||
fn drop(&mut self) {
|
||||
let layout = std::alloc::Layout::from_size_align(1024, 1).unwrap();
|
||||
|
||||
unsafe {
|
||||
_ = *self.ptr;
|
||||
drop(*self.ptr); // $ MISSING: Source=drop
|
||||
_ = *self.ptr; // $ MISSING: Alert[rust/access-invalid-pointer]=drop
|
||||
std::alloc::dealloc(self.ptr, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- qhelp examples ---
|
||||
|
||||
fn do_something(s: &String) {
|
||||
println!(" s = {}", s);
|
||||
}
|
||||
|
||||
fn test_qhelp_example_good(ptr: *mut String) {
|
||||
unsafe {
|
||||
do_something(&*ptr);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
unsafe {
|
||||
std::ptr::drop_in_place(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_qhelp_example_bad(ptr: *mut String) {
|
||||
unsafe {
|
||||
std::ptr::drop_in_place(ptr); // $ Source=drop_in_place
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
unsafe {
|
||||
do_something(&*ptr); // $ Alert[rust/access-invalid-pointer]=drop_in_place
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_qhelp_examples() {
|
||||
let layout = std::alloc::Layout::new::<[String; 2]>();
|
||||
unsafe {
|
||||
let ptr = std::alloc::alloc(layout);
|
||||
let ptr_s = ptr as *mut [String; 2];
|
||||
let ptr1 = &raw mut (*ptr_s)[0];
|
||||
let ptr2 = &raw mut (*ptr_s)[1];
|
||||
|
||||
*ptr1 = String::from("123");
|
||||
*ptr2 = String::from("456");
|
||||
|
||||
test_qhelp_example_good(ptr1);
|
||||
|
||||
test_qhelp_example_bad(ptr2);
|
||||
|
||||
std::alloc::dealloc(ptr, layout);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Vec ---
|
||||
|
||||
pub fn test_vec_reserve() {
|
||||
let mut vec1 = Vec::<u16>::new();
|
||||
vec1.push(100);
|
||||
let ptr1 = &raw mut vec1[0];
|
||||
|
||||
unsafe {
|
||||
let v1 = *ptr1;
|
||||
println!(" v1 = {}", v1);
|
||||
}
|
||||
|
||||
vec1.reserve(1000); // $ MISSING: Source=reserve
|
||||
// (may invalidate the pointer)
|
||||
|
||||
unsafe {
|
||||
let v2 = *ptr1; // $ MISSING: Alert[rust/access-invalid-pointer]=reserve
|
||||
println!(" v2 = {}", v2); // corrupt in practice
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
let mut vec2 = Vec::<u16>::new();
|
||||
vec2.push(200);
|
||||
let ptr2 = &raw mut vec2[0];
|
||||
|
||||
unsafe {
|
||||
let v3 = *ptr2;
|
||||
println!(" v3 = {}", v3);
|
||||
}
|
||||
|
||||
for _i in 0..1000 {
|
||||
vec2.push(0); // $ MISSING: Source=push
|
||||
// (may invalidate the pointer)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let v4 = *ptr2; // $ MISSING: Alert[rust/access-invalid-pointer]=push
|
||||
println!(" v4 = {}", v4); // corrupt in practice
|
||||
}
|
||||
}
|
||||
443
rust/ql/test/query-tests/security/CWE-825/lifetime.rs
Normal file
443
rust/ql/test/query-tests/security/CWE-825/lifetime.rs
Normal file
@@ -0,0 +1,443 @@
|
||||
|
||||
fn use_the_stack() {
|
||||
let _buffer: [u8; 256] = [0xFF; 256];
|
||||
}
|
||||
|
||||
struct MyValue {
|
||||
value: i64
|
||||
}
|
||||
|
||||
impl Drop for MyValue {
|
||||
fn drop(&mut self) {
|
||||
println!(" drop MyValue '{}'", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
// --- local dangling ---
|
||||
|
||||
fn get_local_dangling() -> *const i64 {
|
||||
let my_local1: i64 = 1;
|
||||
|
||||
return &my_local1;
|
||||
} // (return value immediately becomes dangling)
|
||||
|
||||
fn get_local_dangling_mut() -> *mut i64 {
|
||||
let mut my_local2: i64 = 2;
|
||||
|
||||
return &mut my_local2;
|
||||
} // (return value immediately becomes dangling)
|
||||
|
||||
fn get_local_dangling_raw_const() -> *const i64 {
|
||||
let my_local3: i64 = 3;
|
||||
|
||||
return &raw const my_local3;
|
||||
} // (return value immediately becomes dangling)
|
||||
|
||||
fn get_local_dangling_raw_mut() -> *mut i64 {
|
||||
let mut my_local4: i64 = 4;
|
||||
|
||||
return &raw mut my_local4;
|
||||
} // (return value immediately becomes dangling)
|
||||
|
||||
fn get_param_dangling(param5: i64) -> *const i64 {
|
||||
return ¶m5;
|
||||
} // (return value immediately becomes dangling)
|
||||
|
||||
fn get_local_field_dangling() -> *const i64 {
|
||||
let val: MyValue;
|
||||
|
||||
val = MyValue { value: 6 };
|
||||
return &val.value;
|
||||
}
|
||||
|
||||
pub fn test_local_dangling() {
|
||||
let p1 = get_local_dangling();
|
||||
let p2 = get_local_dangling_mut();
|
||||
let p3 = get_local_dangling_raw_const();
|
||||
let p4 = get_local_dangling_raw_mut();
|
||||
let p5 = get_param_dangling(5);
|
||||
let p6 = get_local_field_dangling();
|
||||
let p7: *const i64;
|
||||
{
|
||||
let my_local7 = 7;
|
||||
p7 = &raw const my_local7;
|
||||
} // (my_local goes out of scope, thus p7 is dangling)
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
let v1 = *p1; // $ MISSING: Alert
|
||||
let v2 = *p2; // $ MISSING: Alert
|
||||
let v3 = *p3; // $ MISSING: Alert
|
||||
let v4 = *p4; // $ MISSING: Alert
|
||||
let v5 = *p5; // $ MISSING: Alert
|
||||
let v6 = *p6; // $ MISSING: Alert
|
||||
let v7 = *p7; // $ MISSING: Alert
|
||||
*p2 = 8; // $ MISSING: Alert
|
||||
*p4 = 9; // $ MISSING: Alert
|
||||
|
||||
println!(" v1 = {v1} (!)"); // corrupt in practice
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
println!(" v3 = {v3} (!)"); // corrupt in practice
|
||||
println!(" v4 = {v4} (!)"); // corrupt in practice
|
||||
println!(" v5 = {v5} (!)"); // corrupt in practice
|
||||
println!(" v6 = {v6} (!)"); // corrupt in practice
|
||||
println!(" v7 = {v7} (!)");
|
||||
}
|
||||
}
|
||||
|
||||
// --- local in scope ---
|
||||
|
||||
fn use_pointers(p1: *const i64, p2: *mut i64, mode: i32) {
|
||||
let p3: *const i64;
|
||||
let my_local1 = 1;
|
||||
p3 = &my_local1;
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
if (mode == 0) {
|
||||
// reads
|
||||
let v1 = *p1; // GOOD
|
||||
let v2 = *p2; // GOOD
|
||||
let v3 = *p3; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
println!(" v3 = {v3}");
|
||||
}
|
||||
if (mode == 200) {
|
||||
// writes
|
||||
*p2 = 2; // GOOD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_local_in_scope(mode: i32) {
|
||||
let my_local3: i64 = 3;
|
||||
let mut my_local_mut4: i64 = 4;
|
||||
|
||||
use_pointers(&my_local3, &mut my_local_mut4, mode);
|
||||
}
|
||||
|
||||
// --- static lifetime ---
|
||||
|
||||
const MY_GLOBAL_CONST: i64 = 1;
|
||||
|
||||
fn get_const() -> *const i64 {
|
||||
return &MY_GLOBAL_CONST;
|
||||
}
|
||||
|
||||
static mut MY_GLOBAL_STATIC: i64 = 2;
|
||||
|
||||
fn get_static_mut() -> *mut i64 {
|
||||
unsafe {
|
||||
return &mut MY_GLOBAL_STATIC;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_static(mode: i32) {
|
||||
let p1 = get_const();
|
||||
let p2 = get_static_mut();
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
if (mode == 0) {
|
||||
// reads
|
||||
let v1 = *p1; // GOOD
|
||||
let v2 = *p2; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
}
|
||||
if (mode == 210) {
|
||||
// writes
|
||||
*p2 = 3; // GOOD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- call contexts ---
|
||||
|
||||
fn access_ptr_1(ptr: *const i64) {
|
||||
// only called with `ptr` safe
|
||||
unsafe {
|
||||
let v1 = *ptr; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
}
|
||||
}
|
||||
|
||||
fn access_ptr_2(ptr: *const i64) {
|
||||
// only called with `ptr` dangling
|
||||
unsafe {
|
||||
let v2 = *ptr; // $ MISSING: Alert
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
}
|
||||
}
|
||||
|
||||
fn access_ptr_3(ptr: *const i64) {
|
||||
// called from contexts with `ptr` safe and dangling
|
||||
unsafe {
|
||||
let v3 = *ptr; // $ MISSING: Alert
|
||||
println!(" v3 = {v3} (!)"); // corrupt in practice (in one context)
|
||||
}
|
||||
}
|
||||
|
||||
fn access_and_get_dangling() -> *const i64 {
|
||||
let my_local1 = 1;
|
||||
let ptr = &my_local1;
|
||||
|
||||
access_ptr_1(ptr);
|
||||
access_ptr_3(ptr);
|
||||
|
||||
return ptr;
|
||||
} // (returned pointer becomes dangling)
|
||||
|
||||
pub fn test_call_contexts() {
|
||||
let ptr = access_and_get_dangling();
|
||||
|
||||
use_the_stack();
|
||||
|
||||
access_ptr_2(ptr);
|
||||
access_ptr_3(ptr);
|
||||
}
|
||||
|
||||
// --- call contexts (recursive) ---
|
||||
|
||||
fn access_ptr_rec(ptr_up: *const i64, count: i64) -> *const i64 {
|
||||
let my_local_rec = count;
|
||||
let ptr_ours = &my_local_rec;
|
||||
|
||||
if count < 5 {
|
||||
let ptr_down = access_ptr_rec(ptr_ours, count + 1);
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
let v_up = *ptr_up; // GOOD
|
||||
let v_ours = *ptr_ours; // GOOD
|
||||
let v_down = *ptr_down; // $ MISSING: Alert
|
||||
println!(" v_up = {v_up}");
|
||||
println!(" v_ours = {v_ours}");
|
||||
println!(" v_down = {v_down} (!)");
|
||||
}
|
||||
}
|
||||
|
||||
return ptr_ours;
|
||||
} // (returned pointer becomes dangling)
|
||||
|
||||
pub fn test_call_contexts_rec() {
|
||||
let my_local_rec2 = 1;
|
||||
let ptr_start = &my_local_rec2;
|
||||
|
||||
_ = access_ptr_rec(ptr_start, 2);
|
||||
}
|
||||
|
||||
// --- loops ---
|
||||
|
||||
pub fn test_loop() {
|
||||
let my_local1 = vec!(0);
|
||||
let mut prev: *const Vec<i32> = &my_local1;
|
||||
|
||||
for i in 1..5 {
|
||||
let my_local2 = vec!(i);
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
let v1 = (*prev)[0]; // $ MISSING: Alert
|
||||
println!(" v1 = {v1} (!)"); // incorrect values in practice (except first iteration)
|
||||
}
|
||||
|
||||
prev = &my_local2;
|
||||
} // (my_local2 goes out of scope, thus prev is dangling)
|
||||
|
||||
unsafe {
|
||||
let v2 = (*prev)[0]; // $ MISSING: Alert
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
}
|
||||
}
|
||||
|
||||
// --- enum ---
|
||||
|
||||
enum MyEnum {
|
||||
Value(i64),
|
||||
}
|
||||
|
||||
impl Drop for MyEnum {
|
||||
fn drop(&mut self) {
|
||||
println!(" drop MyEnum");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_enum() {
|
||||
let result: *const i64;
|
||||
|
||||
{
|
||||
let e1 = MyEnum::Value(1);
|
||||
|
||||
result = match e1 {
|
||||
MyEnum::Value(x) => { &x }
|
||||
}; // (x goes out of scope, so result is dangling, I think; seen in real world code)
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
let v1 = *result; // $ MISSING: Alert
|
||||
println!(" v1 = {v1}");
|
||||
}
|
||||
} // (e1 goes out of scope, so result is definitely dangling now)
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
let v2 = *result; // $ MISSING: Alert
|
||||
println!(" v2 = {v2}"); // dropped in practice
|
||||
}
|
||||
}
|
||||
|
||||
// --- std::ptr ---
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MyPair {
|
||||
a: i64,
|
||||
b: i64
|
||||
}
|
||||
|
||||
impl Drop for MyPair {
|
||||
fn drop(&mut self) {
|
||||
println!(" drop MyPair '{} {}'", self.a, self.b);
|
||||
self.a = -1;
|
||||
self.b = -1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_ptr_to_struct(mode: i32) {
|
||||
let p1: *mut MyPair;
|
||||
let p2: *const i64;
|
||||
let p3: *mut i64;
|
||||
|
||||
{
|
||||
let mut my_pair = MyPair { a: 1, b: 2};
|
||||
p1 = std::ptr::addr_of_mut!(my_pair);
|
||||
p2 = std::ptr::addr_of!(my_pair.a);
|
||||
p3 = std::ptr::addr_of_mut!(my_pair.b);
|
||||
|
||||
unsafe {
|
||||
let v1 = (*p1).a; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
|
||||
let v2 = (*p1).b; // GOOD
|
||||
println!(" v2 = {v2}");
|
||||
|
||||
let v3 = *p2; // GOOD
|
||||
let v4 = *p3; // GOOD
|
||||
println!(" v3 = {v3}");
|
||||
println!(" v4 = {v4}");
|
||||
|
||||
(*p1).a = 3; // GOOD
|
||||
*p3 = 4; // GOOD
|
||||
(*p1).b = 5; // GOOD
|
||||
}
|
||||
}; // my_pair goes out of scope, thus p1, p2, p3 are dangling
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
match mode {
|
||||
0 => {
|
||||
// read
|
||||
let v5 = (*p1).a; // $ MISSING: Alert
|
||||
println!(" v5 = {v5} (!)"); // dropped in practice
|
||||
},
|
||||
220 => {
|
||||
// another read
|
||||
let v6 = (*p1).b; // $ MISSING: Alert
|
||||
println!(" v6 = {v6} (!)"); // dropped in practice
|
||||
},
|
||||
221 => {
|
||||
// more reads
|
||||
let v7 = *p2; // $ MISSING: Alert
|
||||
let v8 = *p3; // $ MISSING: Alert
|
||||
println!(" v7 = {v7} (!)"); // dropped in practice
|
||||
println!(" v8 = {v8} (!)"); // dropped in practice
|
||||
},
|
||||
222 => {
|
||||
// writes
|
||||
(*p1).a = 6; // $ MISSING: Alert
|
||||
*p3 = 7; // $ MISSING: Alert
|
||||
},
|
||||
223 => {
|
||||
// another write
|
||||
(*p1).b = 8; // $ MISSING: Alert
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ptr_from_ref(val: i32) -> *const i32 {
|
||||
let my_val = val;
|
||||
let r1: &i32 = &my_val;
|
||||
let p1: *const i32 = std::ptr::from_ref(r1);
|
||||
|
||||
unsafe {
|
||||
let v1 = *p1; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
}
|
||||
|
||||
return p1;
|
||||
} // (returned pointer becomes dangling)
|
||||
|
||||
pub fn test_ptr_from_ref() {
|
||||
let p1 = get_ptr_from_ref(1);
|
||||
|
||||
use_the_stack();
|
||||
|
||||
unsafe {
|
||||
let v2 = *p1; // $ MISSING: Alert
|
||||
let v3 = *get_ptr_from_ref(2); // $ MISSING: Alert
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
println!(" v3 = {v3} (!)");
|
||||
}
|
||||
}
|
||||
|
||||
// --- std::rc (reference counting pointer) ---
|
||||
|
||||
pub fn test_rc() {
|
||||
let p1: *const i64;
|
||||
let p2: *const i64;
|
||||
|
||||
{
|
||||
let rc1: std::rc::Rc<i64> = std::rc::Rc::new(1);
|
||||
p1 = std::rc::Rc::<i64>::as_ptr(&rc1);
|
||||
|
||||
{
|
||||
let rc2: std::rc::Rc<i64> = std::rc::Rc::clone(&rc1);
|
||||
p2 = std::rc::Rc::<i64>::as_ptr(&rc2);
|
||||
|
||||
unsafe {
|
||||
let v1 = *p1; // GOOD
|
||||
let v2 = *p2; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
}
|
||||
} // rc2 goes out of scope, but the reference count is still 1 so the pointer remains valid
|
||||
|
||||
unsafe {
|
||||
let v3 = *p1; // GOOD
|
||||
let v4 = *p2; // GOOD
|
||||
println!(" v3 = {v3}");
|
||||
println!(" v4 = {v4}");
|
||||
}
|
||||
} // rc1 go out of scope, the reference count is 0, so p1, p2 are dangling
|
||||
|
||||
unsafe {
|
||||
let v5 = *p1; // $ MISSING: Alert
|
||||
let v6 = *p2; // $ MISSING: Alert
|
||||
println!(" v5 = {v5} (!)"); // corrupt in practice
|
||||
println!(" v6 = {v6} (!)"); // corrupt in practice
|
||||
}
|
||||
|
||||
// note: simialar things are likely possible with Ref, RefMut, RefCell,
|
||||
// Vec and others.
|
||||
}
|
||||
168
rust/ql/test/query-tests/security/CWE-825/main.rs
Normal file
168
rust/ql/test/query-tests/security/CWE-825/main.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
#![feature(box_as_ptr)]
|
||||
#![feature(box_into_inner)]
|
||||
|
||||
mod deallocation;
|
||||
use deallocation::*;
|
||||
mod lifetime;
|
||||
use lifetime::*;
|
||||
|
||||
fn use_the_heap() {
|
||||
let _a = Box::new(0x7FFFFFFF);
|
||||
let _b = Box::new(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
// --- boxes ---
|
||||
|
||||
pub fn test_boxes_into() {
|
||||
let b1: Box<i64> = Box::new(7); // b1 owns the memory for '50'
|
||||
let p1 = Box::as_ptr(&b1); // b1 still owns the memory
|
||||
|
||||
unsafe {
|
||||
let v1 = *p1; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
}
|
||||
|
||||
let v2 = Box::into_inner(b1); // b1 is explicitly freed here, thus p1 is dangling
|
||||
println!(" v2 = {v2}");
|
||||
|
||||
unsafe {
|
||||
let v3 = *p1; // $ MISSING: Alert
|
||||
println!(" v3 = {v3} (!)"); // corrupt in practice
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_boxes_1(mode: i32) {
|
||||
let p1: *const i64;
|
||||
let p2: *const i64;
|
||||
let p3: *mut i64;
|
||||
|
||||
{
|
||||
let b1: Box<i64> = Box::new(1);
|
||||
p1 = Box::into_raw(b1); // now owned by p1
|
||||
|
||||
let b2: Box<i64> = Box::new(2);
|
||||
p2 = Box::as_ptr(&b2); // still owned by b2
|
||||
|
||||
let mut b3: Box<i64> = Box::new(3);
|
||||
p3 = Box::as_mut_ptr(&mut b3); // still owned by b3
|
||||
|
||||
unsafe {
|
||||
let v1 = *p1; // GOOD
|
||||
let v2 = *p2; // GOOD
|
||||
let v3 = *p3; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
println!(" v2 = {v2}");
|
||||
println!(" v3 = {v3}");
|
||||
*p3 = 4;
|
||||
}
|
||||
} // (b2, b3 go out of scope, thus p2, p3 are dangling)
|
||||
|
||||
unsafe {
|
||||
if mode == 0 {
|
||||
// reads
|
||||
let v4 = *p1; // GOOD
|
||||
let v5 = *p2; // $ MISSING: Alert
|
||||
let v6 = *p3; // $ MISSING: Alert
|
||||
println!(" v4 = {v4}");
|
||||
println!(" v5 = {v5} (!)"); // corrupt in practice
|
||||
println!(" v6 = {v6} (!)"); // corrupt in practice
|
||||
}
|
||||
if mode == 10 {
|
||||
// write
|
||||
*p3 = 5; // $ MISSING: Alert
|
||||
use_the_heap(); // "malloc: Heap corruption detected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_boxes_2() {
|
||||
let b1: Box<i64> = Box::new(6); // b1 owns the memory
|
||||
let p1 = Box::into_raw(b1); // now p1 owns the memory
|
||||
|
||||
unsafe {
|
||||
let _b2 = Box::from_raw(p1); // now _b2 owns the memory
|
||||
|
||||
let v1 = *p1; // GOOD
|
||||
println!(" v1 = {v1}");
|
||||
} // (_b2 goes out of scope, thus the memory is freed and p1 is dangling)
|
||||
|
||||
unsafe {
|
||||
let v2 = *p1; // $ MISSING: Alert
|
||||
println!(" v2 = {v2} (!)"); // corrupt in practice
|
||||
}
|
||||
}
|
||||
|
||||
// --- main ---
|
||||
|
||||
fn main() {
|
||||
let mode = std::env::args().nth(1).unwrap_or("0".to_string()).parse::<i32>().unwrap_or(0);
|
||||
// mode = which test cases to explore (0 should be safe; some will crash / segfault).
|
||||
println!("mode = {mode}");
|
||||
|
||||
println!("test_boxes_into:");
|
||||
test_boxes_into();
|
||||
|
||||
println!("test_boxes_1:");
|
||||
test_boxes_1(mode);
|
||||
|
||||
println!("test_boxes_2:");
|
||||
test_boxes_2();
|
||||
|
||||
// ---
|
||||
|
||||
println!("test_alloc:");
|
||||
test_alloc(mode);
|
||||
|
||||
println!("test_alloc_array:");
|
||||
test_alloc_array(mode);
|
||||
|
||||
println!("test_libc:");
|
||||
test_libc();
|
||||
|
||||
println!("test_ptr_invalid:");
|
||||
test_ptr_invalid(mode);
|
||||
|
||||
println!("test_drop:");
|
||||
test_drop();
|
||||
|
||||
println!("test_ptr_drop:");
|
||||
test_ptr_drop(mode);
|
||||
|
||||
println!("test_qhelp_tests:");
|
||||
test_qhelp_examples();
|
||||
|
||||
println!("test_vec_reserve:");
|
||||
test_vec_reserve();
|
||||
|
||||
// ---
|
||||
|
||||
println!("test_local_dangling:");
|
||||
test_local_dangling();
|
||||
|
||||
println!("test_local_in_scope:");
|
||||
test_local_in_scope(mode);
|
||||
|
||||
println!("test_static:");
|
||||
test_static(mode);
|
||||
|
||||
println!("test_call_contexts:");
|
||||
test_call_contexts();
|
||||
|
||||
println!("test_call_contexts_rec:");
|
||||
test_call_contexts_rec();
|
||||
|
||||
println!("test_loop:");
|
||||
test_loop();
|
||||
|
||||
println!("test_enum:");
|
||||
test_enum();
|
||||
|
||||
println!("test_ptr_to_struct:");
|
||||
test_ptr_to_struct(mode);
|
||||
|
||||
println!("test_ptr_from_ref:");
|
||||
test_ptr_from_ref();
|
||||
|
||||
println!("test_rc:");
|
||||
test_rc();
|
||||
}
|
||||
3
rust/ql/test/query-tests/security/CWE-825/options.yml
Normal file
3
rust/ql/test/query-tests/security/CWE-825/options.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
qltest_cargo_check: true
|
||||
qltest_dependencies:
|
||||
- libc = { version = "0.2.11" }
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2025-03-17"
|
||||
@@ -10,6 +10,7 @@ use core::{hint, mem};
|
||||
|
||||
// summary=repo::test;crate::option::replace;Argument[0].Reference;ReturnValue;value;dfc-generated
|
||||
// summary=repo::test;crate::option::replace;Argument[1];Argument[0].Reference;value;dfc-generated
|
||||
// sink=repo::test;crate::option::replace;Argument[0];pointer-access;df-generated
|
||||
pub fn replace<T>(dest: &mut T, src: T) -> T {
|
||||
unsafe {
|
||||
let result = ptr::read(dest);
|
||||
@@ -338,6 +339,7 @@ impl<T> MyOption<T> {
|
||||
}
|
||||
|
||||
// summary=repo::test;<crate::option::MyOption>::take;Argument[self].Reference;ReturnValue;value;dfc-generated
|
||||
// sink=repo::test;<crate::option::MyOption>::take;Argument[self];pointer-access;df-generated
|
||||
pub fn take(&mut self) -> MyOption<T> {
|
||||
// FIXME(const-hack) replace `mem::replace` by `mem::take` when the latter is const ready
|
||||
replace(self, MyNone)
|
||||
@@ -345,6 +347,7 @@ impl<T> MyOption<T> {
|
||||
|
||||
// summary=repo::test;<crate::option::MyOption>::take_if;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];Argument[0].Parameter[0].Reference;value;dfc-generated
|
||||
// summary=repo::test;<crate::option::MyOption>::take_if;Argument[self].Reference;ReturnValue;value;dfc-generated
|
||||
// sink=repo::test;<crate::option::MyOption>::take_if;Argument[self];pointer-access;df-generated
|
||||
pub fn take_if<P>(&mut self, predicate: P) -> MyOption<T>
|
||||
where
|
||||
P: FnOnce(&mut T) -> bool,
|
||||
@@ -358,6 +361,7 @@ impl<T> MyOption<T> {
|
||||
|
||||
// summary=repo::test;<crate::option::MyOption>::replace;Argument[0];Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];value;dfc-generated
|
||||
// summary=repo::test;<crate::option::MyOption>::replace;Argument[self].Reference;ReturnValue;value;dfc-generated
|
||||
// sink=repo::test;<crate::option::MyOption>::replace;Argument[self];pointer-access;df-generated
|
||||
pub fn replace(&mut self, value: T) -> MyOption<T> {
|
||||
replace(self, MySome(value))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user