Skip to content

Commit 6951f58

Browse files
authored
Merge pull request #20226 from geoffw0/stdlib
Rust: Update StartswithCall to use getCanonicalPath
2 parents 72c89ec + 02b9229 commit 6951f58

File tree

3 files changed

+87
-9
lines changed

3 files changed

+87
-9
lines changed

rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private import codeql.rust.internal.PathResolution
1414
*/
1515
private class StartswithCall extends Path::SafeAccessCheck::Range, CfgNodes::MethodCallExprCfgNode {
1616
StartswithCall() {
17-
this.getAstNode().(Resolvable).getResolvedPath() = "<crate::path::Path>::starts_with"
17+
this.getMethodCallExpr().getStaticTarget().getCanonicalPath() = "<std::path::Path>::starts_with"
1818
}
1919

2020
override predicate checks(Cfg::CfgNode e, boolean branch) {

rust/ql/test/query-tests/security/CWE-022/TaintedPathSinks.ql

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import rust
22
import codeql.rust.security.TaintedPathExtensions
33
import utils.test.InlineExpectationsTest
4+
import codeql.rust.dataflow.DataFlow
5+
import codeql.rust.dataflow.internal.DataFlowImpl as DataflowImpl
6+
import codeql.rust.Concepts
47

58
module TaintedPathSinksTest implements TestSig {
6-
string getARelevantTag() { result = "path-injection-sink" }
9+
string getARelevantTag() {
10+
result =
11+
[
12+
"path-injection-sink", "path-injection-barrier", "path-injection-normalize",
13+
"path-injection-checked"
14+
]
15+
}
716

817
predicate hasActualResult(Location location, string element, string tag, string value) {
918
exists(TaintedPath::Sink sink |
@@ -13,6 +22,36 @@ module TaintedPathSinksTest implements TestSig {
1322
tag = "path-injection-sink" and
1423
value = ""
1524
)
25+
or
26+
exists(DataFlow::Node node |
27+
(
28+
node instanceof TaintedPath::Barrier or
29+
node instanceof TaintedPath::SanitizerGuard // tends to label the node *after* the check
30+
) and
31+
location = node.getLocation() and
32+
location.getFile().getBaseName() != "" and
33+
element = node.toString() and
34+
tag = "path-injection-barrier" and
35+
value = ""
36+
)
37+
or
38+
exists(DataFlow::Node node |
39+
DataflowImpl::optionalBarrier(node, "normalize-path") and
40+
location = node.getLocation() and
41+
location.getFile().getBaseName() != "" and
42+
element = node.toString() and
43+
tag = "path-injection-normalize" and
44+
value = ""
45+
)
46+
or
47+
exists(DataFlow::Node node |
48+
node instanceof Path::SafeAccessCheck and // tends to label the node *after* the check
49+
location = node.getLocation() and
50+
location.getFile().getBaseName() != "" and
51+
element = node.toString() and
52+
tag = "path-injection-checked" and
53+
value = ""
54+
)
1655
}
1756
}
1857

rust/ql/test/query-tests/security/CWE-022/src/main.rs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ fn tainted_path_handler_bad(
1313
//#[handler]
1414
fn tainted_path_handler_good(Query(file_name): Query<String>) -> Result<String> {
1515
// GOOD: ensure that the filename has no path separators or parent directory references
16-
if file_name.contains("..") || file_name.contains("/") || file_name.contains("\\") {
16+
if file_name.contains("..") || file_name.contains("/") || file_name.contains("\\") { // $ path-injection-barrier
1717
return Err(Error::from_status(StatusCode::BAD_REQUEST));
1818
}
19-
let file_path = PathBuf::from(file_name);
19+
let file_path = PathBuf::from(file_name); // $ path-injection-barrier (following the last `.contains` check)
2020
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink
2121
}
2222

@@ -29,25 +29,50 @@ fn tainted_path_handler_folder_good(Query(file_path): Query<String>) -> Result<S
2929
if !file_path.starts_with(public_path) {
3030
return Err(Error::from_status(StatusCode::BAD_REQUEST));
3131
}
32-
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink
32+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: path-injection-checked
3333
}
3434

3535
//#[handler]
3636
fn tainted_path_handler_folder_almost_good1(
37-
Query(file_path): Query<String>, // $ MISSING: Source=remote4
37+
Query(file_path): Query<String>, // $ MISSING: Source=remote2
3838
) -> Result<String> {
3939
let public_path = PathBuf::from("/var/www/public_html");
4040
let file_path = public_path.join(PathBuf::from(file_path));
4141
// BAD: the path could still contain `..` and escape the public folder
4242
if !file_path.starts_with(public_path) {
4343
return Err(Error::from_status(StatusCode::BAD_REQUEST));
4444
}
45-
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: Alert[rust/path-injection]=remote4 -- we cannot resolve the `join` call above, because it needs a `PathBuf -> Path` `Deref`
45+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: path-injection-checked Alert[rust/path-injection]=remote2 -- we cannot resolve the `join` call above, because it needs a `PathBuf -> Path` `Deref`
46+
}
47+
48+
//#[handler]
49+
fn tainted_path_handler_folder_good_simpler(Query(file_path): Query<String>) -> Result<String> {
50+
let public_path = "/var/www/public_html";
51+
let file_path = Path::new(&file_path);
52+
let file_path = file_path.canonicalize().unwrap();
53+
// GOOD: ensure that the path stays within the public folder
54+
if !file_path.starts_with(public_path) {
55+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
56+
}
57+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: path-injection-checked
58+
}
59+
60+
//#[handler]
61+
fn tainted_path_handler_folder_almost_good1_simpler(
62+
Query(file_path): Query<String>, // $ MISSING: Source=remote3
63+
) -> Result<String> {
64+
let public_path = "/var/www/public_html";
65+
let file_path = Path::new(&file_path);
66+
// BAD: the path could still contain `..` and escape the public folder
67+
if !file_path.starts_with(public_path) {
68+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
69+
}
70+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-checked path-injection-sink MISSING: Alert[rust/path-injection]=remote3
4671
}
4772

4873
//#[handler]
4974
fn tainted_path_handler_folder_almost_good2(
50-
Query(file_path): Query<String>, // $ MISSING: Source=remote5
75+
Query(file_path): Query<String>, // $ MISSING: Source=remote4
5176
) -> Result<String> {
5277
let public_path = PathBuf::from("/var/www/public_html");
5378
let file_path = public_path.join(PathBuf::from(file_path));
@@ -56,7 +81,21 @@ fn tainted_path_handler_folder_almost_good2(
5681
if file_path.starts_with(public_path) {
5782
return Err(Error::from_status(StatusCode::BAD_REQUEST));
5883
}
59-
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: Alert[rust/path-injection]=remote5 -- we cannot resolve the `join` call above, because it needs a `PathBuf -> Path` `Deref`
84+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: path-injection-checked Alert[rust/path-injection]=remote4 -- we cannot resolve the `join` call above, because it needs a `PathBuf -> Path` `Deref`
85+
}
86+
87+
//#[handler]
88+
fn tainted_path_handler_folder_almost_good3(
89+
Query(file_path): Query<String>, // $ MISSING: Source=remote5
90+
) -> Result<String> {
91+
let public_path = "/var/www/public_html";
92+
let file_path = Path::new(&file_path);
93+
// BAD: the starts_with check is ineffective before canonicalization, the path could still contain `..`
94+
if !file_path.starts_with(public_path) {
95+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
96+
}
97+
let file_path = file_path.canonicalize().unwrap(); // $ path-injection-checked
98+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: Alert[rust/path-injection]=remote5
6099
}
61100

62101
fn sinks(path1: &Path, path2: &Path) {

0 commit comments

Comments
 (0)