Skip to content

Commit 0fd69ea

Browse files
committed
Add QL test
1 parent 4b5883a commit 0fd69ea

File tree

6 files changed

+139
-0
lines changed

6 files changed

+139
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#select
2+
| src/main.rs:10:5:10:22 | ...::read_to_string | src/main.rs:6:11:6:19 | file_name | src/main.rs:10:5:10:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:6:11:6:19 | file_name | user-provided value |
3+
| src/main.rs:20:5:20:22 | ...::read_to_string | src/main.rs:14:36:14:44 | file_name | src/main.rs:20:5:20:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:14:36:14:44 | file_name | user-provided value |
4+
| src/main.rs:45:5:45:22 | ...::read_to_string | src/main.rs:37:11:37:19 | file_path | src/main.rs:45:5:45:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:37:11:37:19 | file_path | user-provided value |
5+
edges
6+
| src/main.rs:6:11:6:19 | file_name | src/main.rs:8:35:8:43 | file_name | provenance | |
7+
| src/main.rs:8:9:8:17 | file_path | src/main.rs:10:24:10:32 | file_path | provenance | |
8+
| src/main.rs:8:21:8:44 | ...::from(...) | src/main.rs:8:9:8:17 | file_path | provenance | |
9+
| src/main.rs:8:35:8:43 | file_name | src/main.rs:8:21:8:44 | ...::from(...) | provenance | MaD:3 |
10+
| src/main.rs:10:24:10:32 | file_path | src/main.rs:10:5:10:22 | ...::read_to_string | provenance | MaD:1 Sink:MaD:1 |
11+
| src/main.rs:14:36:14:44 | file_name | src/main.rs:19:35:19:43 | file_name | provenance | |
12+
| src/main.rs:19:9:19:17 | file_path | src/main.rs:20:24:20:32 | file_path | provenance | |
13+
| src/main.rs:19:21:19:44 | ...::from(...) | src/main.rs:19:9:19:17 | file_path | provenance | |
14+
| src/main.rs:19:35:19:43 | file_name | src/main.rs:19:21:19:44 | ...::from(...) | provenance | MaD:3 |
15+
| src/main.rs:20:24:20:32 | file_path | src/main.rs:20:5:20:22 | ...::read_to_string | provenance | MaD:1 Sink:MaD:1 |
16+
| src/main.rs:37:11:37:19 | file_path | src/main.rs:40:52:40:60 | file_path | provenance | |
17+
| src/main.rs:40:9:40:17 | file_path | src/main.rs:45:24:45:32 | file_path | provenance | |
18+
| src/main.rs:40:21:40:62 | public_path.join(...) | src/main.rs:40:9:40:17 | file_path | provenance | |
19+
| src/main.rs:40:38:40:61 | ...::from(...) | src/main.rs:40:21:40:62 | public_path.join(...) | provenance | MaD:2 |
20+
| src/main.rs:40:52:40:60 | file_path | src/main.rs:40:38:40:61 | ...::from(...) | provenance | MaD:3 |
21+
| src/main.rs:45:24:45:32 | file_path | src/main.rs:45:5:45:22 | ...::read_to_string | provenance | MaD:1 Sink:MaD:1 |
22+
models
23+
| 1 | Sink: lang:std; crate::fs::read_to_string; path-injection; Argument[0] |
24+
| 2 | Summary: lang:std; <crate::path::Path>::join; Argument[0]; ReturnValue; taint |
25+
| 3 | Summary: lang:std; <crate::path::PathBuf as crate::convert::From>::from; Argument[0]; ReturnValue; taint |
26+
nodes
27+
| src/main.rs:6:11:6:19 | file_name | semmle.label | file_name |
28+
| src/main.rs:8:9:8:17 | file_path | semmle.label | file_path |
29+
| src/main.rs:8:21:8:44 | ...::from(...) | semmle.label | ...::from(...) |
30+
| src/main.rs:8:35:8:43 | file_name | semmle.label | file_name |
31+
| src/main.rs:10:5:10:22 | ...::read_to_string | semmle.label | ...::read_to_string |
32+
| src/main.rs:10:24:10:32 | file_path | semmle.label | file_path |
33+
| src/main.rs:14:36:14:44 | file_name | semmle.label | file_name |
34+
| src/main.rs:19:9:19:17 | file_path | semmle.label | file_path |
35+
| src/main.rs:19:21:19:44 | ...::from(...) | semmle.label | ...::from(...) |
36+
| src/main.rs:19:35:19:43 | file_name | semmle.label | file_name |
37+
| src/main.rs:20:5:20:22 | ...::read_to_string | semmle.label | ...::read_to_string |
38+
| src/main.rs:20:24:20:32 | file_path | semmle.label | file_path |
39+
| src/main.rs:37:11:37:19 | file_path | semmle.label | file_path |
40+
| src/main.rs:40:9:40:17 | file_path | semmle.label | file_path |
41+
| src/main.rs:40:21:40:62 | public_path.join(...) | semmle.label | public_path.join(...) |
42+
| src/main.rs:40:38:40:61 | ...::from(...) | semmle.label | ...::from(...) |
43+
| src/main.rs:40:52:40:60 | file_path | semmle.label | file_path |
44+
| src/main.rs:45:5:45:22 | ...::read_to_string | semmle.label | ...::read_to_string |
45+
| src/main.rs:45:24:45:32 | file_path | semmle.label | file_path |
46+
subpaths
47+
testFailures
48+
| src/main.rs:14:36:14:44 | file_name | Unexpected result: Source |
49+
| src/main.rs:20:5:20:22 | ...::read_to_string | Unexpected result: Alert |
50+
| src/main.rs:50:38:50:56 | //... | Missing result: Source=remote5 |
51+
| src/main.rs:59:64:59:122 | //... | Missing result: Alert[rust/path-injection]=remote5 |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
query: queries/security/CWE-022/TaintedPath.ql
2+
postprocess:
3+
- utils/test/PrettyPrintModels.ql
4+
- utils/test/InlineExpectationsTestQuery.ql

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

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import rust
2+
import codeql.rust.security.TaintedPathExtensions
3+
import utils.test.InlineExpectationsTest
4+
5+
module TaintedPathSinksTest implements TestSig {
6+
string getARelevantTag() { result = "path-injection-sink" }
7+
8+
predicate hasActualResult(Location location, string element, string tag, string value) {
9+
exists(TaintedPath::Sink sink |
10+
location = sink.getLocation() and
11+
location.getFile().getBaseName() != "" and
12+
element = sink.toString() and
13+
tag = "path-injection-sink" and
14+
value = ""
15+
)
16+
}
17+
}
18+
19+
import MakeTest<TaintedPathSinksTest>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
qltest_cargo_check: true
2+
qltest_dependencies:
3+
- poem = { version = "3.1.7" }
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use poem::{error::InternalServerError, handler, http::StatusCode, web::Query, Error, Result};
2+
use std::{fs, path::PathBuf};
3+
4+
//#[handler]
5+
fn tainted_path_handler_bad(
6+
Query(file_name): Query<String>, // $ Source=remote1
7+
) -> Result<String> {
8+
let file_path = PathBuf::from(file_name);
9+
// BAD: This could read any file on the filesystem.
10+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote1
11+
}
12+
13+
//#[handler]
14+
fn tainted_path_handler_good(Query(file_name): Query<String>) -> Result<String> {
15+
// GOOD: ensure that the filename has no path separators or parent directory references
16+
if file_name.contains("..") || file_name.contains("/") || file_name.contains("\\") {
17+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
18+
}
19+
let file_path = PathBuf::from(file_name);
20+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink
21+
}
22+
23+
//#[handler]
24+
fn tainted_path_handler_folder_good(Query(file_path): Query<String>) -> Result<String> {
25+
let public_path = PathBuf::from("/var/www/public_html");
26+
let file_path = public_path.join(PathBuf::from(file_path));
27+
let file_path = file_path.canonicalize().unwrap();
28+
// GOOD: ensure that the path stays within the public folder
29+
if !file_path.starts_with(public_path) {
30+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
31+
}
32+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink
33+
}
34+
35+
//#[handler]
36+
fn tainted_path_handler_folder_almost_good1(
37+
Query(file_path): Query<String>, // $ Source=remote4
38+
) -> Result<String> {
39+
let public_path = PathBuf::from("/var/www/public_html");
40+
let file_path = public_path.join(PathBuf::from(file_path));
41+
// BAD: the path could still contain `..` and escape the public folder
42+
if !file_path.starts_with(public_path) {
43+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
44+
}
45+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote4
46+
}
47+
48+
//#[handler]
49+
fn tainted_path_handler_folder_almost_good2(
50+
Query(file_path): Query<String>, // $ Source=remote5
51+
) -> Result<String> {
52+
let public_path = PathBuf::from("/var/www/public_html");
53+
let file_path = public_path.join(PathBuf::from(file_path));
54+
let file_path = file_path.canonicalize().unwrap();
55+
// BAD: the check to ensure that the path stays within the public folder is wrong
56+
if file_path.starts_with(public_path) {
57+
return Err(Error::from_status(StatusCode::BAD_REQUEST));
58+
}
59+
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote5
60+
}
61+
62+
fn main() {}

0 commit comments

Comments
 (0)