Add QL test

This commit is contained in:
Arthur Baars
2025-03-10 14:11:58 +01:00
parent 4b5883ab79
commit 0fd69eaa43
6 changed files with 139 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
#select
| 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 |
| 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 |
| 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 |
edges
| src/main.rs:6:11:6:19 | file_name | src/main.rs:8:35:8:43 | file_name | provenance | |
| src/main.rs:8:9:8:17 | file_path | src/main.rs:10:24:10:32 | file_path | provenance | |
| src/main.rs:8:21:8:44 | ...::from(...) | src/main.rs:8:9:8:17 | file_path | provenance | |
| src/main.rs:8:35:8:43 | file_name | src/main.rs:8:21:8:44 | ...::from(...) | provenance | MaD:3 |
| 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 |
| src/main.rs:14:36:14:44 | file_name | src/main.rs:19:35:19:43 | file_name | provenance | |
| src/main.rs:19:9:19:17 | file_path | src/main.rs:20:24:20:32 | file_path | provenance | |
| src/main.rs:19:21:19:44 | ...::from(...) | src/main.rs:19:9:19:17 | file_path | provenance | |
| src/main.rs:19:35:19:43 | file_name | src/main.rs:19:21:19:44 | ...::from(...) | provenance | MaD:3 |
| 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 |
| src/main.rs:37:11:37:19 | file_path | src/main.rs:40:52:40:60 | file_path | provenance | |
| src/main.rs:40:9:40:17 | file_path | src/main.rs:45:24:45:32 | file_path | provenance | |
| src/main.rs:40:21:40:62 | public_path.join(...) | src/main.rs:40:9:40:17 | file_path | provenance | |
| src/main.rs:40:38:40:61 | ...::from(...) | src/main.rs:40:21:40:62 | public_path.join(...) | provenance | MaD:2 |
| src/main.rs:40:52:40:60 | file_path | src/main.rs:40:38:40:61 | ...::from(...) | provenance | MaD:3 |
| 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 |
models
| 1 | Sink: lang:std; crate::fs::read_to_string; path-injection; Argument[0] |
| 2 | Summary: lang:std; <crate::path::Path>::join; Argument[0]; ReturnValue; taint |
| 3 | Summary: lang:std; <crate::path::PathBuf as crate::convert::From>::from; Argument[0]; ReturnValue; taint |
nodes
| src/main.rs:6:11:6:19 | file_name | semmle.label | file_name |
| src/main.rs:8:9:8:17 | file_path | semmle.label | file_path |
| src/main.rs:8:21:8:44 | ...::from(...) | semmle.label | ...::from(...) |
| src/main.rs:8:35:8:43 | file_name | semmle.label | file_name |
| src/main.rs:10:5:10:22 | ...::read_to_string | semmle.label | ...::read_to_string |
| src/main.rs:10:24:10:32 | file_path | semmle.label | file_path |
| src/main.rs:14:36:14:44 | file_name | semmle.label | file_name |
| src/main.rs:19:9:19:17 | file_path | semmle.label | file_path |
| src/main.rs:19:21:19:44 | ...::from(...) | semmle.label | ...::from(...) |
| src/main.rs:19:35:19:43 | file_name | semmle.label | file_name |
| src/main.rs:20:5:20:22 | ...::read_to_string | semmle.label | ...::read_to_string |
| src/main.rs:20:24:20:32 | file_path | semmle.label | file_path |
| src/main.rs:37:11:37:19 | file_path | semmle.label | file_path |
| src/main.rs:40:9:40:17 | file_path | semmle.label | file_path |
| src/main.rs:40:21:40:62 | public_path.join(...) | semmle.label | public_path.join(...) |
| src/main.rs:40:38:40:61 | ...::from(...) | semmle.label | ...::from(...) |
| src/main.rs:40:52:40:60 | file_path | semmle.label | file_path |
| src/main.rs:45:5:45:22 | ...::read_to_string | semmle.label | ...::read_to_string |
| src/main.rs:45:24:45:32 | file_path | semmle.label | file_path |
subpaths
testFailures
| src/main.rs:14:36:14:44 | file_name | Unexpected result: Source |
| src/main.rs:20:5:20:22 | ...::read_to_string | Unexpected result: Alert |
| src/main.rs:50:38:50:56 | //... | Missing result: Source=remote5 |
| src/main.rs:59:64:59:122 | //... | Missing result: Alert[rust/path-injection]=remote5 |

View File

@@ -0,0 +1,4 @@
query: queries/security/CWE-022/TaintedPath.ql
postprocess:
- utils/test/PrettyPrintModels.ql
- utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,19 @@
import rust
import codeql.rust.security.TaintedPathExtensions
import utils.test.InlineExpectationsTest
module TaintedPathSinksTest implements TestSig {
string getARelevantTag() { result = "path-injection-sink" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(TaintedPath::Sink sink |
location = sink.getLocation() and
location.getFile().getBaseName() != "" and
element = sink.toString() and
tag = "path-injection-sink" and
value = ""
)
}
}
import MakeTest<TaintedPathSinksTest>

View File

@@ -0,0 +1,3 @@
qltest_cargo_check: true
qltest_dependencies:
- poem = { version = "3.1.7" }

View File

@@ -0,0 +1,62 @@
use poem::{error::InternalServerError, handler, http::StatusCode, web::Query, Error, Result};
use std::{fs, path::PathBuf};
//#[handler]
fn tainted_path_handler_bad(
Query(file_name): Query<String>, // $ Source=remote1
) -> Result<String> {
let file_path = PathBuf::from(file_name);
// BAD: This could read any file on the filesystem.
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote1
}
//#[handler]
fn tainted_path_handler_good(Query(file_name): Query<String>) -> Result<String> {
// GOOD: ensure that the filename has no path separators or parent directory references
if file_name.contains("..") || file_name.contains("/") || file_name.contains("\\") {
return Err(Error::from_status(StatusCode::BAD_REQUEST));
}
let file_path = PathBuf::from(file_name);
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink
}
//#[handler]
fn tainted_path_handler_folder_good(Query(file_path): Query<String>) -> Result<String> {
let public_path = PathBuf::from("/var/www/public_html");
let file_path = public_path.join(PathBuf::from(file_path));
let file_path = file_path.canonicalize().unwrap();
// GOOD: ensure that the path stays within the public folder
if !file_path.starts_with(public_path) {
return Err(Error::from_status(StatusCode::BAD_REQUEST));
}
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink
}
//#[handler]
fn tainted_path_handler_folder_almost_good1(
Query(file_path): Query<String>, // $ Source=remote4
) -> Result<String> {
let public_path = PathBuf::from("/var/www/public_html");
let file_path = public_path.join(PathBuf::from(file_path));
// BAD: the path could still contain `..` and escape the public folder
if !file_path.starts_with(public_path) {
return Err(Error::from_status(StatusCode::BAD_REQUEST));
}
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote4
}
//#[handler]
fn tainted_path_handler_folder_almost_good2(
Query(file_path): Query<String>, // $ Source=remote5
) -> Result<String> {
let public_path = PathBuf::from("/var/www/public_html");
let file_path = public_path.join(PathBuf::from(file_path));
let file_path = file_path.canonicalize().unwrap();
// BAD: the check to ensure that the path stays within the public folder is wrong
if file_path.starts_with(public_path) {
return Err(Error::from_status(StatusCode::BAD_REQUEST));
}
fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote5
}
fn main() {}