Skip to content

Commit f8af648

Browse files
authored
Merge pull request github#18097 from geoffw0/ctor
Rust: New query for bad 'ctor' initialization
2 parents bd56a35 + 49b569c commit f8af648

File tree

8 files changed

+307
-0
lines changed

8 files changed

+307
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
7+
<p>
8+
Calling functions and methods in the Rust <code>std</code> library from a <code>#[ctor]</code> or <code>#[dtor]</code> function is not safe. This is because the <code>std</code> library only guarantees stability and portability between the beginning and the end of <code>main</code>, whereas <code>#[ctor]</code> functions are called before <code>main</code>, and <code>#[dtor]</code> functions are called after it.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Do not call any part of the <code>std</code> library from a <code>#[ctor]</code> or <code>#[dtor]</code> function. Instead either:
16+
</p>
17+
<ul>
18+
<li>Move the code to a different location, such as inside your program's <code>main</code> function.</li>
19+
<li>Rewrite the code using an alternative library.</li>
20+
</ul>
21+
22+
</recommendation>
23+
<example>
24+
25+
<p>
26+
In the following example, a <code>#[ctor]</code> function uses the <code>println!</code> macro which calls <code>std</code> library functions. This may cause unexpected behavior at runtime.
27+
</p>
28+
29+
<sample src="BadCtorInitializationBad.rs" />
30+
31+
<p>
32+
The issue can be fixed by replacing <code>println!</code> with something that does not rely on the <code>std</code> library. In the fixed code below, we used the <code>libc_println!</code> macro from the <code>libc-print</code> library:
33+
</p>
34+
35+
<sample src="BadCtorInitializationGood.rs" />
36+
37+
</example>
38+
<references>
39+
40+
<li>GitHub: <a href="https://github.com/mmastrac/rust-ctor?tab=readme-ov-file#warnings">rust-ctor - Warnings</a>.</li>
41+
<li>Rust Programming Language: <a href="https://doc.rust-lang.org/std/#use-before-and-after-main">Crate std - Use before and after main()</a>.</li>
42+
43+
</references>
44+
</qhelp>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @name Bad 'ctor' initialization
3+
* @description Calling functions in the Rust std library from a ctor or dtor function is not safe.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id rust/ctor-initialization
8+
* @tags reliability
9+
* correctness
10+
* external/cwe/cwe-696
11+
* external/cwe/cwe-665
12+
*/
13+
14+
import rust
15+
16+
/**
17+
* A `#[ctor]` or `#[dtor]` attribute.
18+
*/
19+
class CtorAttr extends Attr {
20+
string whichAttr;
21+
22+
CtorAttr() {
23+
whichAttr = this.getMeta().getPath().getPart().getNameRef().getText() and
24+
whichAttr = ["ctor", "dtor"]
25+
}
26+
27+
string getWhichAttr() { result = whichAttr }
28+
}
29+
30+
/**
31+
* A call into the Rust standard library.
32+
*/
33+
class StdCall extends Expr {
34+
StdCall() {
35+
this.(CallExpr).getFunction().(PathExpr).getPath().getResolvedCrateOrigin() = "lang:std" or
36+
this.(MethodCallExpr).getResolvedCrateOrigin() = "lang:std"
37+
}
38+
}
39+
40+
class PathElement = AstNode;
41+
42+
query predicate edges(PathElement pred, PathElement succ) {
43+
// starting edge
44+
exists(CtorAttr ctor, Function f, StdCall call |
45+
f.getAnAttr() = ctor and
46+
call.getEnclosingCallable() = f and
47+
pred = ctor and // source
48+
succ = call // sink
49+
)
50+
// or
51+
// transitive edge
52+
// TODO
53+
}
54+
55+
from CtorAttr ctor, StdCall call
56+
where edges*(ctor, call)
57+
select call, ctor, call,
58+
"Call to " + call.toString() + " in a function with the " + ctor.getWhichAttr() + " attribute."
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
#[ctor::ctor]
3+
fn bad_example() {
4+
println!("Hello, world!"); // BAD: the println! macro calls std library functions
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
#[ctor::ctor]
3+
fn good_example() {
4+
libc_print::libc_println!("Hello, world!"); // GOOD: libc-print does not use the std library
5+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#select
2+
| test.rs:31:9:31:25 | ...::stdout(...) | test.rs:29:1:29:13 | Attr | test.rs:31:9:31:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the ctor attribute. |
3+
| test.rs:36:9:36:25 | ...::stdout(...) | test.rs:34:1:34:13 | Attr | test.rs:36:9:36:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the dtor attribute. |
4+
| test.rs:43:9:43:25 | ...::stdout(...) | test.rs:40:1:40:13 | Attr | test.rs:43:9:43:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the dtor attribute. |
5+
| test.rs:53:9:53:16 | stdout(...) | test.rs:51:1:51:7 | Attr | test.rs:53:9:53:16 | stdout(...) | Call to stdout(...) in a function with the ctor attribute. |
6+
| test.rs:58:9:58:16 | stderr(...) | test.rs:56:1:56:7 | Attr | test.rs:58:9:58:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
7+
| test.rs:63:14:63:28 | ...::_print(...) | test.rs:61:1:61:7 | Attr | test.rs:63:14:63:28 | ...::_print(...) | Call to ...::_print(...) in a function with the ctor attribute. |
8+
| test.rs:69:9:69:24 | ...::stdin(...) | test.rs:66:1:66:7 | Attr | test.rs:69:9:69:24 | ...::stdin(...) | Call to ...::stdin(...) in a function with the ctor attribute. |
9+
| test.rs:90:5:90:35 | ...::sleep(...) | test.rs:88:1:88:7 | Attr | test.rs:90:5:90:35 | ...::sleep(...) | Call to ...::sleep(...) in a function with the ctor attribute. |
10+
| test.rs:97:5:97:23 | ...::exit(...) | test.rs:95:1:95:7 | Attr | test.rs:97:5:97:23 | ...::exit(...) | Call to ...::exit(...) in a function with the ctor attribute. |
11+
| test.rs:166:5:166:15 | ...::stdout(...) | test.rs:164:1:164:7 | Attr | test.rs:166:5:166:15 | ...::stdout(...) | Call to ...::stdout(...) in a function with the ctor attribute. |
12+
edges
13+
| test.rs:29:1:29:13 | Attr | test.rs:31:9:31:25 | ...::stdout(...) |
14+
| test.rs:34:1:34:13 | Attr | test.rs:36:9:36:25 | ...::stdout(...) |
15+
| test.rs:40:1:40:13 | Attr | test.rs:43:9:43:25 | ...::stdout(...) |
16+
| test.rs:51:1:51:7 | Attr | test.rs:53:9:53:16 | stdout(...) |
17+
| test.rs:56:1:56:7 | Attr | test.rs:58:9:58:16 | stderr(...) |
18+
| test.rs:61:1:61:7 | Attr | test.rs:63:14:63:28 | ...::_print(...) |
19+
| test.rs:66:1:66:7 | Attr | test.rs:69:9:69:24 | ...::stdin(...) |
20+
| test.rs:88:1:88:7 | Attr | test.rs:90:5:90:35 | ...::sleep(...) |
21+
| test.rs:95:1:95:7 | Attr | test.rs:97:5:97:23 | ...::exit(...) |
22+
| test.rs:164:1:164:7 | Attr | test.rs:166:5:166:15 | ...::stdout(...) |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: queries/security/CWE-696/BadCtorInitialization.ql
2+
postprocess: utils/InlineExpectationsTestQuery.ql
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
qltest_cargo_check: true
2+
qltest_dependencies:
3+
- ctor = { version = "0.2.9" }
4+
- libc-print = { version = "0.1.23" }
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
2+
// --- attribute variants ---
3+
4+
use std::io::Write;
5+
6+
fn harmless1_1() {
7+
// ...
8+
}
9+
10+
#[ctor::ctor]
11+
fn harmless1_2() {
12+
// ...
13+
}
14+
15+
#[ctor::dtor]
16+
fn harmless1_3() {
17+
// ...
18+
}
19+
20+
fn harmless1_4() {
21+
_ = std::io::stdout().write(b"Hello, world!");
22+
}
23+
24+
#[rustfmt::skip]
25+
fn harmless1_5() {
26+
_ = std::io::stdout().write(b"Hello, world!");
27+
}
28+
29+
#[ctor::ctor] // $ Source=source1_1
30+
fn bad1_1() {
31+
_ = std::io::stdout().write(b"Hello, world!"); // $ Alert[rust/ctor-initialization]=source1_1
32+
}
33+
34+
#[ctor::dtor] // $ Source=source1_2
35+
fn bad1_2() {
36+
_ = std::io::stdout().write(b"Hello, world!"); // $ Alert[rust/ctor-initialization]=source1_2
37+
}
38+
39+
#[rustfmt::skip]
40+
#[ctor::dtor] // $ Source=source1_3
41+
#[rustfmt::skip]
42+
fn bad1_3() {
43+
_ = std::io::stdout().write(b"Hello, world!"); // $ Alert[rust/ctor-initialization]=source1_3
44+
}
45+
46+
// --- code variants ---
47+
48+
use ctor::ctor;
49+
use std::io::*;
50+
51+
#[ctor] // $ Source=source2_1
52+
fn bad2_1() {
53+
_ = stdout().write(b"Hello, world!"); // $ Alert[rust/ctor-initialization]=source2_1
54+
}
55+
56+
#[ctor] // $ Source=source2_2
57+
fn bad2_2() {
58+
_ = stderr().write_all(b"Hello, world!"); // $ Alert[rust/ctor-initialization]=source2_2
59+
}
60+
61+
#[ctor] // $ Source=source2_3
62+
fn bad2_3() {
63+
println!("Hello, world!"); // $ Alert[rust/ctor-initialization]=source2_3
64+
}
65+
66+
#[ctor] // $ Source=source2_4
67+
fn bad2_4() {
68+
let mut buff = String::new();
69+
_ = std::io::stdin().read_line(&mut buff); // $ Alert[rust/ctor-initialization]=source2_4
70+
}
71+
72+
use std::fs;
73+
74+
#[ctor] // $ MISSING: Source=source2_5
75+
fn bad2_5() {
76+
let _buff = fs::File::create("hello.txt").unwrap(); // $ MISSING: Alert[rust/ctor-initialization]=source2_5
77+
}
78+
79+
#[ctor] // $ MISSING: Source=source2_6
80+
fn bad2_6() {
81+
let _t = std::time::Instant::now(); // $ MISSING: Alert[rust/ctor-initialization]=source2_6
82+
}
83+
84+
use std::time::Duration;
85+
86+
const DURATION2_7: Duration = Duration::new(1, 0);
87+
88+
#[ctor] // $ Source=source2_7
89+
fn bad2_7() {
90+
std::thread::sleep(DURATION2_7); // $ Alert[rust/ctor-initialization]=source2_7
91+
}
92+
93+
use std::process;
94+
95+
#[ctor] // $ Source=source2_8
96+
fn bad2_8() {
97+
process::exit(1234); // $ Alert[rust/ctor-initialization]=source2_8
98+
}
99+
100+
#[ctor::ctor]
101+
fn harmless2_9() {
102+
libc_print::libc_println!("Hello, world!"); // does not use the std library
103+
}
104+
105+
#[ctor::ctor]
106+
fn harmless2_10() {
107+
core::assert!(true); // core library should be OK in this context
108+
}
109+
110+
extern crate alloc;
111+
use alloc::alloc::{alloc, dealloc, Layout};
112+
113+
#[ctor::ctor]
114+
unsafe fn harmless2_11() {
115+
let layout = Layout::new::<u64>();
116+
let ptr = alloc(layout); // alloc library should be OK in this context
117+
118+
if !ptr.is_null() {
119+
dealloc(ptr, layout);
120+
}
121+
}
122+
123+
// --- transitive cases ---
124+
125+
fn call_target3_1() {
126+
_ = stderr().write_all(b"Hello, world!"); // $ MISSING: Alert=source3_1 Alert=source3_3 Alert=source3_4
127+
}
128+
129+
#[ctor] // $ MISSING: Source=source3_1
130+
fn bad3_1() {
131+
call_target3_1();
132+
}
133+
134+
fn call_target3_2() {
135+
for _x in 0..10 {
136+
// ...
137+
}
138+
}
139+
140+
#[ctor] // $ MISSING: Source=source3_2
141+
fn harmless3_2() {
142+
call_target3_2();
143+
}
144+
145+
#[ctor]
146+
fn bad3_3() {
147+
call_target3_1();
148+
call_target3_2();
149+
}
150+
151+
#[ctor] // $ MISSING: Source=source3_4
152+
fn bad3_4() {
153+
bad3_3();
154+
}
155+
156+
// --- macros ---
157+
158+
macro_rules! macro4_1 {
159+
() => {
160+
_ = std::io::stdout().write(b"Hello, world!");
161+
};
162+
}
163+
164+
#[ctor] // $ Source=source4_1
165+
fn bad4_1() {
166+
macro4_1!(); // $ Alert[rust/ctor-initialization]=source4_1
167+
}

0 commit comments

Comments
 (0)