From 5da296d77fed96ab72a022307711905355a81f93 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:15:25 +0100 Subject: [PATCH 01/18] Rust: Add tests for std::fs::OpenOptions and similar. --- .../dataflow/sources/TaintSources.expected | 28 +- .../library-tests/dataflow/sources/test.rs | 53 ++ .../query-tests/security/CWE-022/Cargo.lock | 515 +++++++++++++++++- .../security/CWE-022/TaintedPath.expected | 26 +- .../query-tests/security/CWE-022/options.yml | 2 + .../query-tests/security/CWE-022/src/main.rs | 4 + 6 files changed, 589 insertions(+), 39 deletions(-) diff --git a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected index 66d909f2e1ba..d8787604ff4d 100644 --- a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected +++ b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected @@ -67,20 +67,20 @@ | test.rs:457:31:457:39 | file_name | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:463:22:463:41 | ...::read_link | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:473:20:473:38 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:507:21:507:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:508:21:508:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:516:21:516:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:528:20:528:40 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:575:21:575:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:576:21:576:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:584:21:584:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:601:26:601:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:620:26:620:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:672:28:672:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:754:22:754:49 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:780:22:780:50 | ...::new | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:807:16:807:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | -| test.rs:807:16:807:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | +| test.rs:530:21:530:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:531:21:531:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:539:21:539:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:551:20:551:40 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:607:21:607:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:608:21:608:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:616:21:616:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:648:26:648:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:667:26:667:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:719:28:719:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:801:22:801:49 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:827:22:827:50 | ...::new | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:854:16:854:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | +| test.rs:854:16:854:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | | test_futures_io.rs:19:15:19:32 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | | web_frameworks.rs:11:31:11:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). | | web_frameworks.rs:11:31:11:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). | diff --git a/rust/ql/test/library-tests/dataflow/sources/test.rs b/rust/ql/test/library-tests/dataflow/sources/test.rs index baa2062a910a..4ace4c56b064 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test.rs @@ -500,6 +500,29 @@ fn test_io_file() -> std::io::Result<()> { sink(byte); // $ hasTaintFlow="file.txt" } + // --- OpenOptions --- + + { + let mut f1 = std::fs::OpenOptions::new().open("f1.txt").unwrap(); // $ MISSING: Alert[rust/summary/taint-sources] + let mut buffer = [0u8; 1024]; + let _bytes = f1.read(&mut buffer)?; + sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" + } + + { + let mut f2 = std::fs::OpenOptions::new().create_new(true).open("f2.txt").unwrap(); // $ MISSING: Alert[rust/summary/taint-sources] + let mut buffer = [0u8; 1024]; + let _bytes = f2.read(&mut buffer)?; + sink(&buffer); // $ MISSING: hasTaintFlow="f2.txt" + } + + { + let mut f3 = std::fs::OpenOptions::new().read(true).write(true).truncate(true).create(true).open("f3.txt").unwrap(); // $ MISSING: Alert[rust/summary/taint-sources] + let mut buffer = [0u8; 1024]; + let _bytes = f3.read(&mut buffer)?; + sink(&buffer); // $ MISSING: hasTaintFlow="f3.txt" + } + // --- misc operations --- { @@ -568,6 +591,15 @@ async fn test_tokio_file() -> std::io::Result<()> { sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_buf` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` } + // --- OpenOptions --- + + { + let mut f1 = tokio::fs::OpenOptions::new().open("f1.txt").await?; // $ MISSING: Alert[rust/summary/taint-sources] + let mut buffer = [0u8; 1024]; + let _bytes = f1.read(&mut buffer).await?; + sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" + } + // --- misc operations --- { @@ -590,6 +622,21 @@ async fn test_tokio_file() -> std::io::Result<()> { Ok(()) } +use async_std::io::ReadExt; + +async fn test_async_std_file() -> std::io::Result<()> { + // --- OpenOptions --- + + { + let mut f1 = async_std::fs::OpenOptions::new().open("f1.txt").await?; // $ MISSING: Alert[rust/summary/taint-sources] + let mut buffer = [0u8; 1024]; + let _bytes = f1.read(&mut buffer).await?; + sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" + } + + Ok(()) +} + use std::net::ToSocketAddrs; async fn test_std_tcpstream(case: i64) -> std::io::Result<()> { @@ -863,6 +910,12 @@ async fn main() -> Result<(), Box> { Err(e) => println!("error: {}", e), } + println!("test_async_std_file..."); + match futures::executor::block_on(test_async_std_file()) { + Ok(_) => println!("complete"), + Err(e) => println!("error: {}", e), + } + println!("test_std_tcpstream..."); match futures::executor::block_on(test_std_tcpstream(case)) { Ok(_) => println!("complete"), diff --git a/rust/ql/test/query-tests/security/CWE-022/Cargo.lock b/rust/ql/test/query-tests/security/CWE-022/Cargo.lock index 29fb68236577..f6b8cfc2c4b4 100644 --- a/rust/ql/test/query-tests/security/CWE-022/Cargo.lock +++ b/rust/ql/test/query-tests/security/CWE-022/Cargo.lock @@ -26,6 +26,119 @@ dependencies = [ "memchr", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -50,7 +163,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -74,6 +187,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" @@ -92,6 +224,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -101,6 +242,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -127,6 +274,49 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fnv" version = "1.0.7" @@ -157,6 +347,25 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -211,6 +420,18 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.10" @@ -260,6 +481,12 @@ dependencies = [ "http", ] +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "http" version = "1.3.1" @@ -357,12 +584,37 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "libc" version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lock_api" version = "0.4.13" @@ -373,6 +625,15 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "value-bag", +] + [[package]] name = "memchr" version = "2.7.5" @@ -432,6 +693,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -452,7 +719,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -473,6 +740,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "poem" version = "3.1.11" @@ -518,6 +796,20 @@ dependencies = [ "syn", ] +[[package]] +name = "polling" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.60.2", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -598,6 +890,25 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -665,6 +976,15 @@ dependencies = [ "digest", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.10" @@ -711,7 +1031,9 @@ dependencies = [ name = "test" version = "0.0.1" dependencies = [ + "async-std", "poem", + "tokio", ] [[package]] @@ -744,7 +1066,9 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -843,6 +1167,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + [[package]] name = "version_check" version = "0.9.5" @@ -855,19 +1185,106 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wildmatch" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -876,7 +1293,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -885,14 +1311,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -901,48 +1344,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.11" diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index 60847b71b798..8b757e5a4bf2 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -1,20 +1,20 @@ #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:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | 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:2 | -| src/main.rs:8:35:8:43 | file_name | src/main.rs:8:21:8:44 | ...::from(...) | provenance | MaD:2 | -| 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:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | +| src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | +| src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:2 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:2 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:1 Sink:MaD:1 | models | 1 | Sink: std::fs::read_to_string; Argument[0]; path-injection | | 2 | Summary: ::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:7:11:7:19 | file_name | semmle.label | file_name | +| src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | +| src/main.rs:9:21:9:44 | ...::from(...) | semmle.label | ...::from(...) | +| src/main.rs:9:35:9:43 | file_name | semmle.label | file_name | +| src/main.rs:11:5:11:22 | ...::read_to_string | semmle.label | ...::read_to_string | +| src/main.rs:11:24:11:32 | file_path | semmle.label | file_path | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-022/options.yml b/rust/ql/test/query-tests/security/CWE-022/options.yml index e0b9bbfb5cf1..18392052c7c5 100644 --- a/rust/ql/test/query-tests/security/CWE-022/options.yml +++ b/rust/ql/test/query-tests/security/CWE-022/options.yml @@ -1,3 +1,5 @@ qltest_use_nightly: true qltest_dependencies: - poem = { version = "3.1.7" } + - tokio = { version = "1.43.0", features = ["full"] } + - async-std = { version = "1.13.1" } diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 6f8c73654c5e..233a6996746c 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -1,6 +1,7 @@ #![feature(file_buffered)] use poem::{error::InternalServerError, handler, http::StatusCode, web::Query, Error, Result}; use std::{fs, path::Path, path::PathBuf}; + //#[handler] fn tainted_path_handler_bad( Query(file_name): Query, // $ Source=remote1 @@ -122,6 +123,9 @@ fn sinks(path1: &Path, path2: &Path) { let _ = std::fs::File::create_new(path1); // $ path-injection-sink let _ = std::fs::File::open(path1); // $ path-injection-sink let _ = std::fs::File::open_buffered(path1); // $ path-injection-sink + let _ = std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink } fn main() {} From bc226e21170b6ef315fdf571d1df471a0f578562 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:39:59 +0100 Subject: [PATCH 02/18] Rust: Add more general test cases for async_std::fs and tokio::fs. --- .../dataflow/sources/TaintSources.expected | 14 +++++++------- .../ql/test/library-tests/dataflow/sources/test.rs | 10 ++++++++++ .../test/query-tests/security/CWE-022/src/main.rs | 8 ++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected index d8787604ff4d..942d810caab1 100644 --- a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected +++ b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected @@ -74,13 +74,13 @@ | test.rs:607:21:607:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:608:21:608:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:616:21:616:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:648:26:648:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:667:26:667:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:719:28:719:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:801:22:801:49 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:827:22:827:50 | ...::new | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:854:16:854:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | -| test.rs:854:16:854:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | +| test.rs:658:26:658:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:677:26:677:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:729:28:729:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:811:22:811:49 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:837:22:837:50 | ...::new | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:864:16:864:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | +| test.rs:864:16:864:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | | test_futures_io.rs:19:15:19:32 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | | web_frameworks.rs:11:31:11:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). | | web_frameworks.rs:11:31:11:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). | diff --git a/rust/ql/test/library-tests/dataflow/sources/test.rs b/rust/ql/test/library-tests/dataflow/sources/test.rs index 4ace4c56b064..d0ef30d334ee 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test.rs @@ -625,6 +625,16 @@ async fn test_tokio_file() -> std::io::Result<()> { use async_std::io::ReadExt; async fn test_async_std_file() -> std::io::Result<()> { + // --- file --- + + let mut file = async_std::fs::File::open("file.txt").await?; // $ MISSING: Alert[rust/summary/taint-sources] + + { + let mut buffer = [0u8; 100]; + let _bytes = file.read(&mut buffer).await?; + sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" + } + // --- OpenOptions --- { diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 233a6996746c..15e0791bf2d0 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -124,7 +124,15 @@ fn sinks(path1: &Path, path2: &Path) { let _ = std::fs::File::open(path1); // $ path-injection-sink let _ = std::fs::File::open_buffered(path1); // $ path-injection-sink let _ = std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + + let _ = tokio::fs::read(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::read_to_string(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::remove_file(path1); // $ MISSING: path-injection-sink let _ = tokio::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + + let _ = async_std::fs::read(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::read_to_string(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::remove_file(path1); // $ MISSING: path-injection-sink let _ = async_std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink } From 801be8fbbd12656a3486a1d54ae35fdb8c79c147 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:33:49 +0100 Subject: [PATCH 03/18] Rust: Add more tests for std::fs::DirBuilder and similar. --- rust/ql/test/query-tests/security/CWE-022/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 15e0791bf2d0..bdaa9dcfa56a 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -117,22 +117,27 @@ fn sinks(path1: &Path, path2: &Path) { let _ = std::fs::soft_link(path1, path2); // $ path-injection-sink let _ = std::fs::symlink_metadata(path1); // $ path-injection-sink let _ = std::fs::write(path1, "contents"); // $ path-injection-sink - let _ = std::fs::DirBuilder::new().create(path1); // $ path-injection-sink let _ = std::fs::File::create(path1); // $ path-injection-sink let _ = std::fs::File::create_buffered(path1); // $ path-injection-sink let _ = std::fs::File::create_new(path1); // $ path-injection-sink let _ = std::fs::File::open(path1); // $ path-injection-sink let _ = std::fs::File::open_buffered(path1); // $ path-injection-sink + let _ = std::fs::DirBuilder::new().create(path1); // $ path-injection-sink + let _ = std::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink let _ = std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink let _ = tokio::fs::read(path1); // $ MISSING: path-injection-sink let _ = tokio::fs::read_to_string(path1); // $ MISSING: path-injection-sink let _ = tokio::fs::remove_file(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::DirBuilder::new().create(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::DirBuilder::new().recursive(true).create(path1); // $ MISSING: path-injection-sink let _ = tokio::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink let _ = async_std::fs::read(path1); // $ MISSING: path-injection-sink let _ = async_std::fs::read_to_string(path1); // $ MISSING: path-injection-sink let _ = async_std::fs::remove_file(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::DirBuilder::new().create(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::DirBuilder::new().recursive(true).create(path1); // $ MISSING: path-injection-sink let _ = async_std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink } From 8da44828a642467bde9bbcfb80997fedf3d2f0a7 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:18:54 +0100 Subject: [PATCH 04/18] Rust: Add tests for std::fs::canonicalize and similar. --- .../security/CWE-022/TaintedPath.expected | 31 ++++++++++++++++--- .../query-tests/security/CWE-022/src/main.rs | 20 ++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index 8b757e5a4bf2..0f8832188e61 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -1,15 +1,28 @@ #select | src/main.rs:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | user-provided value | +| src/main.rs:104:13:104:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:104:13:104:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:2 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:2 | -| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:1 Sink:MaD:1 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:7 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:7 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | +| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:3 | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:5 | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:6 | +| src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | +| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:4 | +| src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:1 Sink:MaD:1 | models -| 1 | Sink: std::fs::read_to_string; Argument[0]; path-injection | -| 2 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 1 | Sink: ::open; Argument[0]; path-injection | +| 2 | Sink: std::fs::read_to_string; Argument[0]; path-injection | +| 3 | Source: std::env::args; ReturnValue.Element; commandargs | +| 4 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | +| 5 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 6 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 7 | Summary: ::from; Argument[0]; ReturnValue; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -17,4 +30,12 @@ nodes | src/main.rs:9:35:9:43 | file_name | semmle.label | file_name | | src/main.rs:11:5:11:22 | ...::read_to_string | semmle.label | ...::read_to_string | | src/main.rs:11:24:11:32 | file_path | semmle.label | file_path | +| src/main.rs:103:9:103:13 | path1 | semmle.label | path1 | +| src/main.rs:103:17:103:30 | ...::args | semmle.label | ...::args | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| src/main.rs:103:17:103:48 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:104:13:104:31 | ...::open | semmle.label | ...::open | +| src/main.rs:104:33:104:37 | path1 | semmle.label | path1 | +| src/main.rs:104:33:104:45 | path1.clone() | semmle.label | path1.clone() | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index bdaa9dcfa56a..15d38660517a 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -99,6 +99,26 @@ fn tainted_path_handler_folder_almost_good3( fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: Alert[rust/path-injection]=remote5 } +async fn more_simple_cases() { + let path1 = std::env::args().nth(1).unwrap(); // $ Source=arg1 + let _ = std::fs::File::open(path1.clone()); // $ path-injection-sink Alert[rust/path-injection]=arg1 + + let path2 = std::fs::canonicalize(path1.clone()).unwrap(); + let _ = std::fs::File::open(path2); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 + + let path3 = tokio::fs::canonicalize(path1.clone()).await.unwrap(); + let _ = tokio::fs::File::open(path3); // $ MISSING: path-injection-sink Alert[rust/path-injection]=arg1 + + let path4 = async_std::fs::canonicalize(path1.clone()).await.unwrap(); + let _ = async_std::fs::File::open(path4); // $ MISSING: path-injection-sink Alert[rust/path-injection]=arg1 + + let path5 = std::path::Path::new(&path1); + let _ = std::fs::File::open(path5); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 + + let path6 = path5.canonicalize().unwrap(); + let _ = std::fs::File::open(path6); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 +} + fn sinks(path1: &Path, path2: &Path) { let _ = std::fs::copy(path1, path2); // $ path-injection-sink let _ = std::fs::create_dir(path1); // $ path-injection-sink From 49b4adcc99f151ef1f7d5b2cfc50d04ada3e173c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:04:52 +0100 Subject: [PATCH 05/18] Rust: Add more tests for DirEntry, PathBuf, OsString. --- .../dataflow/sources/TaintSources.expected | 54 ++++++++++--------- .../library-tests/dataflow/sources/test.rs | 32 ++++++++++- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected index 942d810caab1..818f35345d44 100644 --- a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected +++ b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected @@ -55,32 +55,34 @@ | test.rs:413:31:413:38 | ...::read | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:418:22:418:39 | ...::read_to_string | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:418:22:418:39 | ...::read_to_string | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:424:22:424:25 | path | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:425:27:425:35 | file_name | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:431:22:431:34 | ...::read_link | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:440:31:440:45 | ...::read | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:445:31:445:45 | ...::read | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:450:22:450:46 | ...::read_to_string | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:456:26:456:29 | path | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:456:26:456:29 | path | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:457:31:457:39 | file_name | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:457:31:457:39 | file_name | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:463:22:463:41 | ...::read_link | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:473:20:473:38 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:530:21:530:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:531:21:531:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:539:21:539:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:551:20:551:40 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:607:21:607:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:608:21:608:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:616:21:616:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | -| test.rs:658:26:658:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:677:26:677:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:729:28:729:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:811:22:811:49 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:837:22:837:50 | ...::new | Flow source 'RemoteSource' of type remote (DEFAULT). | -| test.rs:864:16:864:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | -| test.rs:864:16:864:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | +| test.rs:425:22:425:25 | path | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:439:27:439:35 | file_name | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:450:22:450:25 | path | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:451:27:451:35 | file_name | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:461:22:461:34 | ...::read_link | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:470:31:470:45 | ...::read | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:475:31:475:45 | ...::read | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:480:22:480:46 | ...::read_to_string | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:486:26:486:29 | path | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:486:26:486:29 | path | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:487:31:487:39 | file_name | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:487:31:487:39 | file_name | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:493:22:493:41 | ...::read_link | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:503:20:503:38 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:560:21:560:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:561:21:561:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:569:21:569:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:581:20:581:40 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:637:21:637:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:638:21:638:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:646:21:646:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:688:26:688:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:707:26:707:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:759:28:759:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:841:22:841:49 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:867:22:867:50 | ...::new | Flow source 'RemoteSource' of type remote (DEFAULT). | +| test.rs:894:16:894:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | +| test.rs:894:16:894:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). | | test_futures_io.rs:19:15:19:32 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | | web_frameworks.rs:11:31:11:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). | | web_frameworks.rs:11:31:11:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). | diff --git a/rust/ql/test/library-tests/dataflow/sources/test.rs b/rust/ql/test/library-tests/dataflow/sources/test.rs index d0ef30d334ee..91d68c7c28ad 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test.rs @@ -421,11 +421,41 @@ fn test_fs() -> Result<(), Box> { for entry in fs::read_dir("directory")? { let e = entry?; + let path = e.path(); // $ Alert[rust/summary/taint-sources] - let file_name = e.file_name(); // $ Alert[rust/summary/taint-sources] + sink(path.clone()); // $ hasTaintFlow + sink(path.clone().as_path()); // $ hasTaintFlow + sink(path.clone().into_os_string()); // $ MISSING: hasTaintFlow + sink(std::path::PathBuf::from(path.clone().into_boxed_path())); // $ MISSING: hasTaintFlow + sink(path.clone().as_os_str()); // $ MISSING: hasTaintFlow + sink(path.clone().as_mut_os_str()); // $ MISSING: hasTaintFlow + sink(path.to_str()); // $ MISSING: hasTaintFlow + sink(path.to_path_buf()); // $ MISSING: hasTaintFlow + sink(path.file_name().unwrap()); // $ MISSING: hasTaintFlow + sink(path.extension().unwrap()); // $ MISSING: hasTaintFlow + sink(path.canonicalize().unwrap()); // $ MISSING: hasTaintFlow sink(path); // $ hasTaintFlow + + let file_name = e.file_name(); // $ Alert[rust/summary/taint-sources] + sink(file_name.clone()); // $ hasTaintFlow + sink(file_name.clone().into_string().unwrap()); // $ MISSING: hasTaintFlow + sink(file_name.to_str().unwrap()); // $ MISSING: hasTaintFlow + sink(file_name.to_string_lossy().to_mut()); // $ MISSING: hasTaintFlow + sink(file_name.clone().as_encoded_bytes()); // $ MISSING: hasTaintFlow sink(file_name); // $ hasTaintFlow } + for entry in std::path::Path::new("directory").read_dir()? { + let e = entry?; + + let path = e.path(); // $ Alert[rust/summary/taint-sources] + let file_name = e.file_name(); // $ Alert[rust/summary/taint-sources] + } + for entry in std::path::PathBuf::from("directory").read_dir()? { + let e = entry?; + + let path = e.path(); // $ MISSING: Alert[rust/summary/taint-sources] + let file_name = e.file_name(); // $ MISSING: Alert[rust/summary/taint-sources] + } { let target = fs::read_link("symlink.txt")?; // $ Alert[rust/summary/taint-sources] From 1d2ac33bb6f3ff533bbe9fe05b2b4a91f982a002 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:41:08 +0100 Subject: [PATCH 06/18] Rust: Model async-std::fs. --- .../rust/frameworks/asyncstd/fs.model.yml | 42 ++++++++++++++++ .../dataflow/sources/TaintSources.expected | 1 + .../library-tests/dataflow/sources/test.rs | 2 +- .../security/CWE-022/TaintedPath.expected | 50 +++++++++++++------ .../query-tests/security/CWE-022/src/main.rs | 12 ++--- 5 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml diff --git a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml new file mode 100644 index 000000000000..cbf80ae8b889 --- /dev/null +++ b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml @@ -0,0 +1,42 @@ +extensions: + - addsTo: + pack: codeql/rust-all + extensible: sourceModel + data: + - ["async_std::fs::read::read", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - ["async_std::fs::read_to_string::read_to_string", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - ["async_std::fs::read_link::read_link", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - ["::path", "ReturnValue", "file", "manual"] + - ["::file_name", "ReturnValue", "file", "manual"] + - ["::open", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - addsTo: + pack: codeql/rust-all + extensible: sinkModel + data: + - ["async_std::fs::copy::copy", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::copy::copy", "Argument[1]", "path-injection", "manual"] + - ["async_std::fs::create_dir::create_dir", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::create_dir_all::create_dir_all", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::hard_link::hard_link", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::hard_link::hard_link", "Argument[1]", "path-injection", "manual"] + - ["async_std::fs::metadata::metadata", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::read::read", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::read_dir::read_dir", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::read_link::read_link", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::read_to_string::read_to_string", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::remove_dir::remove_dir", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::remove_dir_all::remove_dir_all", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::remove_file::remove_file", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::rename::rename", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::rename::rename", "Argument[1]", "path-injection", "manual"] + - ["async_std::fs::set_permissions::set_permissions", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::symlink_metadata::symlink_metadata", "Argument[0]", "path-injection", "manual"] + - ["async_std::fs::write::write", "Argument[0]", "path-injection", "manual"] + - ["::create", "Argument[0]", "path-injection", "manual"] + - ["::create", "Argument[0]", "path-injection", "manual"] + - ["::open", "Argument[0]", "path-injection", "manual"] + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: + - ["async_std::fs::canonicalize::canonicalize", "Argument[0]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] diff --git a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected index 818f35345d44..2ca8819ce163 100644 --- a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected +++ b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected @@ -76,6 +76,7 @@ | test.rs:637:21:637:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:638:21:638:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:646:21:646:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:660:20:660:44 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:688:26:688:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | | test.rs:707:26:707:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | | test.rs:759:28:759:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | diff --git a/rust/ql/test/library-tests/dataflow/sources/test.rs b/rust/ql/test/library-tests/dataflow/sources/test.rs index 91d68c7c28ad..e1b3c0f79540 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test.rs @@ -657,7 +657,7 @@ use async_std::io::ReadExt; async fn test_async_std_file() -> std::io::Result<()> { // --- file --- - let mut file = async_std::fs::File::open("file.txt").await?; // $ MISSING: Alert[rust/summary/taint-sources] + let mut file = async_std::fs::File::open("file.txt").await?; // $ Alert[rust/summary/taint-sources] { let mut buffer = [0u8; 100]; diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index 0f8832188e61..cbeb5679e064 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -1,28 +1,40 @@ #select | src/main.rs:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | user-provided value | | src/main.rs:104:13:104:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:104:13:104:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:7 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:7 | -| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:9 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:9 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:3 Sink:MaD:3 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | -| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:3 | -| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:5 | -| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:6 | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:112:45:112:49 | path1 | provenance | | +| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:4 | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:6 | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:7 | | src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | -| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:4 | -| src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:1 Sink:MaD:1 | +| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:5 | +| src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | +| src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | +| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:8 | +| src/main.rs:112:17:112:73 | ... .unwrap() | src/main.rs:112:9:112:13 | path4 | provenance | | +| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:5 | +| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:10 | +| src/main.rs:113:39:113:43 | path4 | src/main.rs:113:13:113:37 | ...::open | provenance | MaD:1 Sink:MaD:1 | models -| 1 | Sink: ::open; Argument[0]; path-injection | -| 2 | Sink: std::fs::read_to_string; Argument[0]; path-injection | -| 3 | Source: std::env::args; ReturnValue.Element; commandargs | -| 4 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | -| 5 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 6 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 7 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 1 | Sink: ::open; Argument[0]; path-injection | +| 2 | Sink: ::open; Argument[0]; path-injection | +| 3 | Sink: std::fs::read_to_string; Argument[0]; path-injection | +| 4 | Source: std::env::args; ReturnValue.Element; commandargs | +| 5 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | +| 6 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 7 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 8 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 9 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 10 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -38,4 +50,12 @@ nodes | src/main.rs:104:13:104:31 | ...::open | semmle.label | ...::open | | src/main.rs:104:33:104:37 | path1 | semmle.label | path1 | | src/main.rs:104:33:104:45 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:112:9:112:13 | path4 | semmle.label | path4 | +| src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | semmle.label | ...::canonicalize(...) [future, Ok] | +| src/main.rs:112:17:112:64 | await ... [Ok] | semmle.label | await ... [Ok] | +| src/main.rs:112:17:112:73 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:112:45:112:49 | path1 | semmle.label | path1 | +| src/main.rs:112:45:112:57 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:113:13:113:37 | ...::open | semmle.label | ...::open | +| src/main.rs:113:39:113:43 | path4 | semmle.label | path4 | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 15d38660517a..7aaf46e3deee 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -110,7 +110,7 @@ async fn more_simple_cases() { let _ = tokio::fs::File::open(path3); // $ MISSING: path-injection-sink Alert[rust/path-injection]=arg1 let path4 = async_std::fs::canonicalize(path1.clone()).await.unwrap(); - let _ = async_std::fs::File::open(path4); // $ MISSING: path-injection-sink Alert[rust/path-injection]=arg1 + let _ = async_std::fs::File::open(path4); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path5 = std::path::Path::new(&path1); let _ = std::fs::File::open(path5); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 @@ -153,11 +153,11 @@ fn sinks(path1: &Path, path2: &Path) { let _ = tokio::fs::DirBuilder::new().recursive(true).create(path1); // $ MISSING: path-injection-sink let _ = tokio::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink - let _ = async_std::fs::read(path1); // $ MISSING: path-injection-sink - let _ = async_std::fs::read_to_string(path1); // $ MISSING: path-injection-sink - let _ = async_std::fs::remove_file(path1); // $ MISSING: path-injection-sink - let _ = async_std::fs::DirBuilder::new().create(path1); // $ MISSING: path-injection-sink - let _ = async_std::fs::DirBuilder::new().recursive(true).create(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::read(path1); // $ path-injection-sink + let _ = async_std::fs::read_to_string(path1); // $ path-injection-sink + let _ = async_std::fs::remove_file(path1); // $ path-injection-sink + let _ = async_std::fs::DirBuilder::new().create(path1); // $ path-injection-sink + let _ = async_std::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink let _ = async_std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink } From 29e7b6ad2c89b7b1a96b5cc1af13b3b05c8c27d6 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 09:55:17 +0100 Subject: [PATCH 07/18] Rust: Fill a gap in the std::fs model. --- .../codeql/rust/frameworks/stdlib/fs.model.yml | 1 + .../security/CWE-022/TaintedPath.expected | 16 ++++++++++++++++ .../query-tests/security/CWE-022/src/main.rs | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml index 346b4dbc722d..bd2f6f6c3e02 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml @@ -45,6 +45,7 @@ extensions: pack: codeql/rust-all extensible: summaryModel data: + - ["std::fs::canonicalize", "Argument[0]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - ["::from", "Argument[0]", "ReturnValue", "taint", "manual"] - ["::join", "Argument[self]", "ReturnValue", "taint", "manual"] - ["::join", "Argument[0]", "ReturnValue", "taint", "manual"] diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index cbeb5679e064..b76f4a36bcc5 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -1,6 +1,7 @@ #select | src/main.rs:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | user-provided value | | src/main.rs:104:13:104:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:104:13:104:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:107:13:107:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:107:13:107:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | @@ -10,6 +11,7 @@ edges | src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:9 | | src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:3 Sink:MaD:3 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:106:39:106:43 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:112:45:112:49 | path1 | provenance | | | src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:4 | | src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:6 | @@ -17,6 +19,12 @@ edges | src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | | src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:5 | | src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:106:9:106:13 | path2 | src/main.rs:107:33:107:37 | path2 | provenance | | +| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:8 | +| src/main.rs:106:17:106:61 | ... .unwrap() | src/main.rs:106:9:106:13 | path2 | provenance | | +| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:5 | +| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:11 | +| src/main.rs:107:33:107:37 | path2 | src/main.rs:107:13:107:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | | src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:8 | @@ -35,6 +43,7 @@ models | 8 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | | 9 | Summary: ::from; Argument[0]; ReturnValue; taint | | 10 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 11 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -50,6 +59,13 @@ nodes | src/main.rs:104:13:104:31 | ...::open | semmle.label | ...::open | | src/main.rs:104:33:104:37 | path1 | semmle.label | path1 | | src/main.rs:104:33:104:45 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:106:9:106:13 | path2 | semmle.label | path2 | +| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | semmle.label | ...::canonicalize(...) [Ok] | +| src/main.rs:106:17:106:61 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:106:39:106:43 | path1 | semmle.label | path1 | +| src/main.rs:106:39:106:51 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:107:13:107:31 | ...::open | semmle.label | ...::open | +| src/main.rs:107:33:107:37 | path2 | semmle.label | path2 | | src/main.rs:112:9:112:13 | path4 | semmle.label | path4 | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | semmle.label | ...::canonicalize(...) [future, Ok] | | src/main.rs:112:17:112:64 | await ... [Ok] | semmle.label | await ... [Ok] | diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 7aaf46e3deee..ce44ba819c98 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -104,7 +104,7 @@ async fn more_simple_cases() { let _ = std::fs::File::open(path1.clone()); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path2 = std::fs::canonicalize(path1.clone()).unwrap(); - let _ = std::fs::File::open(path2); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 + let _ = std::fs::File::open(path2); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path3 = tokio::fs::canonicalize(path1.clone()).await.unwrap(); let _ = tokio::fs::File::open(path3); // $ MISSING: path-injection-sink Alert[rust/path-injection]=arg1 From 16e0de0cfb2fd5ef4fe512ca6324a970e96db408 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:00:46 +0100 Subject: [PATCH 08/18] Rust: Fill gaps in the tokio models. --- .../codeql/rust/frameworks/tokio/fs.model.yml | 39 ++++++++++++ .../security/CWE-022/TaintedPath.expected | 63 ++++++++++++------- .../query-tests/security/CWE-022/src/main.rs | 12 ++-- 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml index 8fe76763dd5c..834c4b7c94ff 100644 --- a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml @@ -9,3 +9,42 @@ extensions: - ["::path", "ReturnValue", "file", "manual"] - ["::file_name", "ReturnValue", "file", "manual"] - ["::open", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - addsTo: + pack: codeql/rust-all + extensible: sinkModel + data: + - ["tokio::fs::copy::copy", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::copy::copy", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::create_dir::create_dir", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::create_dir_all::create_dir_all", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::hard_link::hard_link", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::hard_link::hard_link", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::metadata::metadata", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::read::read", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::read_dir::read_dir", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::read_link::read_link", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::read_to_string::read_to_string", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::remove_dir::remove_dir", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::remove_dir_all::remove_dir_all", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::remove_file::remove_file", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::rename::rename", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::rename::rename", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::set_permissions::set_permissions", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::symlink::symlink", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::symlink::symlink", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::symlink_dir::symlink_dir", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::symlink_dir::symlink_dir", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::symlink_file::symlink_file", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::symlink_file::symlink_file", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::symlink_metadata::symlink_metadata", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::try_exists::try_exists", "Argument[0]", "path-injection", "manual"] + - ["tokio::fs::write::write", "Argument[0]", "path-injection", "manual"] + - ["::create", "Argument[0]", "path-injection", "manual"] + - ["::create", "Argument[0]", "path-injection", "manual"] + - ["::create_new", "Argument[0]", "path-injection", "manual"] + - ["::open", "Argument[0]", "path-injection", "manual"] + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: + - ["tokio::fs::canonicalize::canonicalize", "Argument[0]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index b76f4a36bcc5..f86bea577295 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -2,48 +2,59 @@ | src/main.rs:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | user-provided value | | src/main.rs:104:13:104:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:104:13:104:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:107:13:107:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:107:13:107:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:110:13:110:33 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:110:13:110:33 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:9 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:9 | -| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:3 Sink:MaD:3 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:10 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:10 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:4 Sink:MaD:4 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:106:39:106:43 | path1 | provenance | | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:109:41:109:45 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:112:45:112:49 | path1 | provenance | | -| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:4 | -| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:6 | -| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:7 | +| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:5 | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:7 | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:8 | | src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | -| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:5 | +| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:6 | | src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:106:9:106:13 | path2 | src/main.rs:107:33:107:37 | path2 | provenance | | -| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:8 | +| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:9 | | src/main.rs:106:17:106:61 | ... .unwrap() | src/main.rs:106:9:106:13 | path2 | provenance | | -| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:5 | -| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:11 | +| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:6 | +| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:12 | | src/main.rs:107:33:107:37 | path2 | src/main.rs:107:13:107:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:109:9:109:13 | path3 | src/main.rs:110:35:110:39 | path3 | provenance | | +| src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | src/main.rs:109:17:109:60 | await ... [Ok] | provenance | | +| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:109:17:109:69 | ... .unwrap() | src/main.rs:109:9:109:13 | path3 | provenance | | +| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:6 | +| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:13 | +| src/main.rs:110:35:110:39 | path3 | src/main.rs:110:13:110:33 | ...::open | provenance | MaD:3 Sink:MaD:3 | | src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | -| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:8 | +| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:9 | | src/main.rs:112:17:112:73 | ... .unwrap() | src/main.rs:112:9:112:13 | path4 | provenance | | -| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:5 | -| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:10 | +| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:6 | +| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:11 | | src/main.rs:113:39:113:43 | path4 | src/main.rs:113:13:113:37 | ...::open | provenance | MaD:1 Sink:MaD:1 | models | 1 | Sink: ::open; Argument[0]; path-injection | | 2 | Sink: ::open; Argument[0]; path-injection | -| 3 | Sink: std::fs::read_to_string; Argument[0]; path-injection | -| 4 | Source: std::env::args; ReturnValue.Element; commandargs | -| 5 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | -| 6 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 7 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 8 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | -| 9 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 10 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | -| 11 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | +| 3 | Sink: ::open; Argument[0]; path-injection | +| 4 | Sink: std::fs::read_to_string; Argument[0]; path-injection | +| 5 | Source: std::env::args; ReturnValue.Element; commandargs | +| 6 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | +| 7 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 8 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 9 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 10 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 11 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 12 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | +| 13 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -66,6 +77,14 @@ nodes | src/main.rs:106:39:106:51 | path1.clone() | semmle.label | path1.clone() | | src/main.rs:107:13:107:31 | ...::open | semmle.label | ...::open | | src/main.rs:107:33:107:37 | path2 | semmle.label | path2 | +| src/main.rs:109:9:109:13 | path3 | semmle.label | path3 | +| src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | semmle.label | ...::canonicalize(...) [future, Ok] | +| src/main.rs:109:17:109:60 | await ... [Ok] | semmle.label | await ... [Ok] | +| src/main.rs:109:17:109:69 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:109:41:109:45 | path1 | semmle.label | path1 | +| src/main.rs:109:41:109:53 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:110:13:110:33 | ...::open | semmle.label | ...::open | +| src/main.rs:110:35:110:39 | path3 | semmle.label | path3 | | src/main.rs:112:9:112:13 | path4 | semmle.label | path4 | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | semmle.label | ...::canonicalize(...) [future, Ok] | | src/main.rs:112:17:112:64 | await ... [Ok] | semmle.label | await ... [Ok] | diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index ce44ba819c98..c6bc8c333a3a 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -107,7 +107,7 @@ async fn more_simple_cases() { let _ = std::fs::File::open(path2); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path3 = tokio::fs::canonicalize(path1.clone()).await.unwrap(); - let _ = tokio::fs::File::open(path3); // $ MISSING: path-injection-sink Alert[rust/path-injection]=arg1 + let _ = tokio::fs::File::open(path3); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path4 = async_std::fs::canonicalize(path1.clone()).await.unwrap(); let _ = async_std::fs::File::open(path4); // $ path-injection-sink Alert[rust/path-injection]=arg1 @@ -146,11 +146,11 @@ fn sinks(path1: &Path, path2: &Path) { let _ = std::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink let _ = std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink - let _ = tokio::fs::read(path1); // $ MISSING: path-injection-sink - let _ = tokio::fs::read_to_string(path1); // $ MISSING: path-injection-sink - let _ = tokio::fs::remove_file(path1); // $ MISSING: path-injection-sink - let _ = tokio::fs::DirBuilder::new().create(path1); // $ MISSING: path-injection-sink - let _ = tokio::fs::DirBuilder::new().recursive(true).create(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::read(path1); // $ path-injection-sink + let _ = tokio::fs::read_to_string(path1); // $ path-injection-sink + let _ = tokio::fs::remove_file(path1); // $ path-injection-sink + let _ = tokio::fs::DirBuilder::new().create(path1); // $ path-injection-sink + let _ = tokio::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink let _ = tokio::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink let _ = async_std::fs::read(path1); // $ path-injection-sink From fcce862cea1f46b5b34815888a998d9384449880 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:39:33 +0100 Subject: [PATCH 09/18] Rust: Add an explicit test case for sinks with two relevant args. --- .../security/CWE-022/TaintedPath.expected | 68 ++++++++++++------- .../query-tests/security/CWE-022/src/main.rs | 4 ++ 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index f86bea577295..8cbc9832412e 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -4,57 +4,67 @@ | src/main.rs:107:13:107:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:107:13:107:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:110:13:110:33 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:110:13:110:33 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:122:13:122:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:122:13:122:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:123:13:123:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:123:13:123:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:10 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:10 | -| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:4 Sink:MaD:4 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:12 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:12 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:106:39:106:43 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:109:41:109:45 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:112:45:112:49 | path1 | provenance | | -| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:5 | -| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:7 | -| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:8 | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:122:27:122:31 | path1 | provenance | | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:123:37:123:41 | path1 | provenance | | +| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:7 | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:9 | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:10 | | src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | -| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:6 | +| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:8 | | src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:106:9:106:13 | path2 | src/main.rs:107:33:107:37 | path2 | provenance | | -| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:106:17:106:61 | ... .unwrap() | src/main.rs:106:9:106:13 | path2 | provenance | | -| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:6 | -| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:12 | +| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:8 | +| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:14 | | src/main.rs:107:33:107:37 | path2 | src/main.rs:107:13:107:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:109:9:109:13 | path3 | src/main.rs:110:35:110:39 | path3 | provenance | | | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | src/main.rs:109:17:109:60 | await ... [Ok] | provenance | | -| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:109:17:109:69 | ... .unwrap() | src/main.rs:109:9:109:13 | path3 | provenance | | -| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:6 | -| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:13 | +| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:8 | +| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:15 | | src/main.rs:110:35:110:39 | path3 | src/main.rs:110:13:110:33 | ...::open | provenance | MaD:3 Sink:MaD:3 | | src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | -| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:112:17:112:73 | ... .unwrap() | src/main.rs:112:9:112:13 | path4 | provenance | | -| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:6 | -| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:11 | +| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:8 | +| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:13 | | src/main.rs:113:39:113:43 | path4 | src/main.rs:113:13:113:37 | ...::open | provenance | MaD:1 Sink:MaD:1 | +| src/main.rs:122:27:122:31 | path1 | src/main.rs:122:27:122:39 | path1.clone() | provenance | MaD:8 | +| src/main.rs:122:27:122:39 | path1.clone() | src/main.rs:122:13:122:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | +| src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:8 | +| src/main.rs:123:37:123:49 | path1.clone() | src/main.rs:123:13:123:25 | ...::copy | provenance | MaD:5 Sink:MaD:5 | models | 1 | Sink: ::open; Argument[0]; path-injection | | 2 | Sink: ::open; Argument[0]; path-injection | | 3 | Sink: ::open; Argument[0]; path-injection | -| 4 | Sink: std::fs::read_to_string; Argument[0]; path-injection | -| 5 | Source: std::env::args; ReturnValue.Element; commandargs | -| 6 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | -| 7 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 8 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 9 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | -| 10 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 11 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | -| 12 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | -| 13 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 4 | Sink: std::fs::copy; Argument[0]; path-injection | +| 5 | Sink: std::fs::copy; Argument[1]; path-injection | +| 6 | Sink: std::fs::read_to_string; Argument[0]; path-injection | +| 7 | Source: std::env::args; ReturnValue.Element; commandargs | +| 8 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | +| 9 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 10 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 11 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 12 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 13 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 14 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | +| 15 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -93,4 +103,10 @@ nodes | src/main.rs:112:45:112:57 | path1.clone() | semmle.label | path1.clone() | | src/main.rs:113:13:113:37 | ...::open | semmle.label | ...::open | | src/main.rs:113:39:113:43 | path4 | semmle.label | path4 | +| src/main.rs:122:13:122:25 | ...::copy | semmle.label | ...::copy | +| src/main.rs:122:27:122:31 | path1 | semmle.label | path1 | +| src/main.rs:122:27:122:39 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:123:13:123:25 | ...::copy | semmle.label | ...::copy | +| src/main.rs:123:37:123:41 | path1 | semmle.label | path1 | +| src/main.rs:123:37:123:49 | path1.clone() | semmle.label | path1.clone() | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index c6bc8c333a3a..f8ba7f20b332 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -117,6 +117,10 @@ async fn more_simple_cases() { let path6 = path5.canonicalize().unwrap(); let _ = std::fs::File::open(path6); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 + + let harmless = ""; + let _ = std::fs::copy(path1.clone(), harmless); // $ path-injection-sink Alert[rust/path-injection]=arg1 + let _ = std::fs::copy(harmless, path1.clone()); // $ path-injection-sink Alert[rust/path-injection]=arg1 } fn sinks(path1: &Path, path2: &Path) { From 9fbbe02da02500cd2e2193a6486584fa7532b4a8 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:22:17 +0100 Subject: [PATCH 10/18] Rust: Compact these models a little. --- .../rust/frameworks/asyncstd/fs.model.yml | 9 +-- .../rust/frameworks/stdlib/fs.model.yml | 12 ++-- .../codeql/rust/frameworks/tokio/fs.model.yml | 18 ++---- .../security/CWE-022/TaintedPath.expected | 61 +++++++++---------- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml index cbf80ae8b889..c8bd1983648d 100644 --- a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml @@ -13,12 +13,10 @@ extensions: pack: codeql/rust-all extensible: sinkModel data: - - ["async_std::fs::copy::copy", "Argument[0]", "path-injection", "manual"] - - ["async_std::fs::copy::copy", "Argument[1]", "path-injection", "manual"] + - ["async_std::fs::copy::copy", "Argument[0,1]", "path-injection", "manual"] - ["async_std::fs::create_dir::create_dir", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::create_dir_all::create_dir_all", "Argument[0]", "path-injection", "manual"] - - ["async_std::fs::hard_link::hard_link", "Argument[0]", "path-injection", "manual"] - - ["async_std::fs::hard_link::hard_link", "Argument[1]", "path-injection", "manual"] + - ["async_std::fs::hard_link::hard_link", "Argument[0,1]", "path-injection", "manual"] - ["async_std::fs::metadata::metadata", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::read::read", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::read_dir::read_dir", "Argument[0]", "path-injection", "manual"] @@ -27,8 +25,7 @@ extensions: - ["async_std::fs::remove_dir::remove_dir", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::remove_dir_all::remove_dir_all", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::remove_file::remove_file", "Argument[0]", "path-injection", "manual"] - - ["async_std::fs::rename::rename", "Argument[0]", "path-injection", "manual"] - - ["async_std::fs::rename::rename", "Argument[1]", "path-injection", "manual"] + - ["async_std::fs::rename::rename", "Argument[0,1]", "path-injection", "manual"] - ["async_std::fs::set_permissions::set_permissions", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::symlink_metadata::symlink_metadata", "Argument[0]", "path-injection", "manual"] - ["async_std::fs::write::write", "Argument[0]", "path-injection", "manual"] diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml index bd2f6f6c3e02..ea7761d1ce5c 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml @@ -14,12 +14,10 @@ extensions: pack: codeql/rust-all extensible: sinkModel data: - - ["std::fs::copy", "Argument[0]", "path-injection", "manual"] - - ["std::fs::copy", "Argument[1]", "path-injection", "manual"] + - ["std::fs::copy", "Argument[0,1]", "path-injection", "manual"] - ["std::fs::create_dir", "Argument[0]", "path-injection", "manual"] - ["std::fs::create_dir_all", "Argument[0]", "path-injection", "manual"] - - ["std::fs::hard_link", "Argument[0]", "path-injection", "manual"] - - ["std::fs::hard_link", "Argument[1]", "path-injection", "manual"] + - ["std::fs::hard_link", "Argument[0,1]", "path-injection", "manual"] - ["std::fs::metadata", "Argument[0]", "path-injection", "manual"] - ["std::fs::read", "Argument[0]", "path-injection", "manual"] - ["std::fs::read_dir", "Argument[0]", "path-injection", "manual"] @@ -28,11 +26,9 @@ extensions: - ["std::fs::remove_dir", "Argument[0]", "path-injection", "manual"] - ["std::fs::remove_dir_all", "Argument[0]", "path-injection", "manual"] - ["std::fs::remove_file", "Argument[0]", "path-injection", "manual"] - - ["std::fs::rename", "Argument[0]", "path-injection", "manual"] - - ["std::fs::rename", "Argument[1]", "path-injection", "manual"] + - ["std::fs::rename", "Argument[0,1]", "path-injection", "manual"] - ["std::fs::set_permissions", "Argument[0]", "path-injection", "manual"] - - ["std::fs::soft_link", "Argument[0]", "path-injection", "manual"] - - ["std::fs::soft_link", "Argument[1]", "path-injection", "manual"] + - ["std::fs::soft_link", "Argument[0,1]", "path-injection", "manual"] - ["std::fs::symlink_metadata", "Argument[0]", "path-injection", "manual"] - ["std::fs::write", "Argument[0]", "path-injection", "manual"] - ["::create", "Argument[0]", "path-injection", "manual"] diff --git a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml index 834c4b7c94ff..52f36294827c 100644 --- a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml @@ -13,12 +13,10 @@ extensions: pack: codeql/rust-all extensible: sinkModel data: - - ["tokio::fs::copy::copy", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::copy::copy", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::copy::copy", "Argument[0,1]", "path-injection", "manual"] - ["tokio::fs::create_dir::create_dir", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::create_dir_all::create_dir_all", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::hard_link::hard_link", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::hard_link::hard_link", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::hard_link::hard_link", "Argument[0,1]", "path-injection", "manual"] - ["tokio::fs::metadata::metadata", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::read::read", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::read_dir::read_dir", "Argument[0]", "path-injection", "manual"] @@ -27,15 +25,11 @@ extensions: - ["tokio::fs::remove_dir::remove_dir", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::remove_dir_all::remove_dir_all", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::remove_file::remove_file", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::rename::rename", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::rename::rename", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::rename::rename", "Argument[0,1]", "path-injection", "manual"] - ["tokio::fs::set_permissions::set_permissions", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::symlink::symlink", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::symlink::symlink", "Argument[1]", "path-injection", "manual"] - - ["tokio::fs::symlink_dir::symlink_dir", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::symlink_dir::symlink_dir", "Argument[1]", "path-injection", "manual"] - - ["tokio::fs::symlink_file::symlink_file", "Argument[0]", "path-injection", "manual"] - - ["tokio::fs::symlink_file::symlink_file", "Argument[1]", "path-injection", "manual"] + - ["tokio::fs::symlink::symlink", "Argument[0,1]", "path-injection", "manual"] + - ["tokio::fs::symlink_dir::symlink_dir", "Argument[0,1]", "path-injection", "manual"] + - ["tokio::fs::symlink_file::symlink_file", "Argument[0,1]", "path-injection", "manual"] - ["tokio::fs::symlink_metadata::symlink_metadata", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::try_exists::try_exists", "Argument[0]", "path-injection", "manual"] - ["tokio::fs::write::write", "Argument[0]", "path-injection", "manual"] diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index 8cbc9832412e..532d5d316e86 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -10,61 +10,60 @@ edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:12 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:12 | -| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:11 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:11 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:5 Sink:MaD:5 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:106:39:106:43 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:109:41:109:45 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:112:45:112:49 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:122:27:122:31 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:123:37:123:41 | path1 | provenance | | -| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:7 | -| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:9 | -| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:10 | +| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:6 | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:8 | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:9 | | src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | -| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:8 | +| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:7 | | src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:106:9:106:13 | path2 | src/main.rs:107:33:107:37 | path2 | provenance | | -| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:11 | +| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:10 | | src/main.rs:106:17:106:61 | ... .unwrap() | src/main.rs:106:9:106:13 | path2 | provenance | | -| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:8 | -| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:14 | +| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:7 | +| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:13 | | src/main.rs:107:33:107:37 | path2 | src/main.rs:107:13:107:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:109:9:109:13 | path3 | src/main.rs:110:35:110:39 | path3 | provenance | | | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | src/main.rs:109:17:109:60 | await ... [Ok] | provenance | | -| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:11 | +| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:10 | | src/main.rs:109:17:109:69 | ... .unwrap() | src/main.rs:109:9:109:13 | path3 | provenance | | -| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:8 | -| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:15 | +| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:7 | +| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:14 | | src/main.rs:110:35:110:39 | path3 | src/main.rs:110:13:110:33 | ...::open | provenance | MaD:3 Sink:MaD:3 | | src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | -| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:11 | +| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:10 | | src/main.rs:112:17:112:73 | ... .unwrap() | src/main.rs:112:9:112:13 | path4 | provenance | | -| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:8 | -| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:13 | +| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:7 | +| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:12 | | src/main.rs:113:39:113:43 | path4 | src/main.rs:113:13:113:37 | ...::open | provenance | MaD:1 Sink:MaD:1 | -| src/main.rs:122:27:122:31 | path1 | src/main.rs:122:27:122:39 | path1.clone() | provenance | MaD:8 | +| src/main.rs:122:27:122:31 | path1 | src/main.rs:122:27:122:39 | path1.clone() | provenance | MaD:7 | | src/main.rs:122:27:122:39 | path1.clone() | src/main.rs:122:13:122:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | -| src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:8 | -| src/main.rs:123:37:123:49 | path1.clone() | src/main.rs:123:13:123:25 | ...::copy | provenance | MaD:5 Sink:MaD:5 | +| src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:7 | +| src/main.rs:123:37:123:49 | path1.clone() | src/main.rs:123:13:123:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | models | 1 | Sink: ::open; Argument[0]; path-injection | | 2 | Sink: ::open; Argument[0]; path-injection | | 3 | Sink: ::open; Argument[0]; path-injection | -| 4 | Sink: std::fs::copy; Argument[0]; path-injection | -| 5 | Sink: std::fs::copy; Argument[1]; path-injection | -| 6 | Sink: std::fs::read_to_string; Argument[0]; path-injection | -| 7 | Source: std::env::args; ReturnValue.Element; commandargs | -| 8 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | -| 9 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 10 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 11 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | -| 12 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 13 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | -| 14 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | -| 15 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 4 | Sink: std::fs::copy; Argument[0,1]; path-injection | +| 5 | Sink: std::fs::read_to_string; Argument[0]; path-injection | +| 6 | Source: std::env::args; ReturnValue.Element; commandargs | +| 7 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | +| 8 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 9 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 10 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 11 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 12 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 13 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | +| 14 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | From 8b04bc0cebd94793a7e49b8628eb208db41b7c00 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:12:14 +0100 Subject: [PATCH 11/18] Rust: Model std::fs::OpenOptions and similar. --- .../codeql/rust/frameworks/asyncstd/fs.model.yml | 2 ++ .../codeql/rust/frameworks/stdlib/fs.model.yml | 2 ++ .../codeql/rust/frameworks/tokio/fs.model.yml | 2 ++ .../dataflow/sources/TaintSources.expected | 5 +++++ .../test/library-tests/dataflow/sources/test.rs | 16 ++++++++-------- .../query-tests/security/CWE-022/src/main.rs | 6 +++--- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml index c8bd1983648d..f30fc54ecd81 100644 --- a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml @@ -9,6 +9,7 @@ extensions: - ["::path", "ReturnValue", "file", "manual"] - ["::file_name", "ReturnValue", "file", "manual"] - ["::open", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - ["::open", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] - addsTo: pack: codeql/rust-all extensible: sinkModel @@ -32,6 +33,7 @@ extensions: - ["::create", "Argument[0]", "path-injection", "manual"] - ["::create", "Argument[0]", "path-injection", "manual"] - ["::open", "Argument[0]", "path-injection", "manual"] + - ["::open", "Argument[0]", "path-injection", "manual"] - addsTo: pack: codeql/rust-all extensible: summaryModel diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml index ea7761d1ce5c..851553c8d726 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml @@ -10,6 +10,7 @@ extensions: - ["::file_name", "ReturnValue", "file", "manual"] - ["::open", "ReturnValue.Field[core::result::Result::Ok(0)]", "file", "manual"] - ["::open_buffered", "ReturnValue.Field[core::result::Result::Ok(0)]", "file", "manual"] + - ["::open", "ReturnValue.Field[core::result::Result::Ok(0)]", "file", "manual"] - addsTo: pack: codeql/rust-all extensible: sinkModel @@ -37,6 +38,7 @@ extensions: - ["::create_new", "Argument[0]", "path-injection", "manual"] - ["::open", "Argument[0]", "path-injection", "manual"] - ["::open_buffered", "Argument[0]", "path-injection", "manual"] + - ["::open", "Argument[0]", "path-injection", "manual"] - addsTo: pack: codeql/rust-all extensible: summaryModel diff --git a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml index 52f36294827c..3e051d15dd7c 100644 --- a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml @@ -9,6 +9,7 @@ extensions: - ["::path", "ReturnValue", "file", "manual"] - ["::file_name", "ReturnValue", "file", "manual"] - ["::open", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] + - ["::open", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "file", "manual"] - addsTo: pack: codeql/rust-all extensible: sinkModel @@ -37,6 +38,7 @@ extensions: - ["::create", "Argument[0]", "path-injection", "manual"] - ["::create_new", "Argument[0]", "path-injection", "manual"] - ["::open", "Argument[0]", "path-injection", "manual"] + - ["::open", "Argument[0]", "path-injection", "manual"] - addsTo: pack: codeql/rust-all extensible: summaryModel diff --git a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected index 2ca8819ce163..59f1e9b4e0c4 100644 --- a/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected +++ b/rust/ql/test/library-tests/dataflow/sources/TaintSources.expected @@ -69,14 +69,19 @@ | test.rs:487:31:487:39 | file_name | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:493:22:493:41 | ...::read_link | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:503:20:503:38 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:536:50:536:53 | open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:543:67:543:70 | open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:550:101:550:104 | open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:560:21:560:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:561:21:561:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:569:21:569:39 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:581:20:581:40 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:627:52:627:55 | open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:637:21:637:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:638:21:638:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:646:21:646:41 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:660:20:660:44 | ...::open | Flow source 'FileSource' of type file (DEFAULT). | +| test.rs:671:56:671:59 | open | Flow source 'FileSource' of type file (DEFAULT). | | test.rs:688:26:688:53 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | | test.rs:707:26:707:61 | ...::connect_timeout | Flow source 'RemoteSource' of type remote (DEFAULT). | | test.rs:759:28:759:57 | ...::connect | Flow source 'RemoteSource' of type remote (DEFAULT). | diff --git a/rust/ql/test/library-tests/dataflow/sources/test.rs b/rust/ql/test/library-tests/dataflow/sources/test.rs index e1b3c0f79540..64d74d9527d4 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test.rs @@ -533,24 +533,24 @@ fn test_io_file() -> std::io::Result<()> { // --- OpenOptions --- { - let mut f1 = std::fs::OpenOptions::new().open("f1.txt").unwrap(); // $ MISSING: Alert[rust/summary/taint-sources] + let mut f1 = std::fs::OpenOptions::new().open("f1.txt").unwrap(); // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 1024]; let _bytes = f1.read(&mut buffer)?; - sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" + sink(&buffer); // $ hasTaintFlow="f1.txt" } { - let mut f2 = std::fs::OpenOptions::new().create_new(true).open("f2.txt").unwrap(); // $ MISSING: Alert[rust/summary/taint-sources] + let mut f2 = std::fs::OpenOptions::new().create_new(true).open("f2.txt").unwrap(); // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 1024]; let _bytes = f2.read(&mut buffer)?; - sink(&buffer); // $ MISSING: hasTaintFlow="f2.txt" + sink(&buffer); // $ hasTaintFlow="f2.txt" } { - let mut f3 = std::fs::OpenOptions::new().read(true).write(true).truncate(true).create(true).open("f3.txt").unwrap(); // $ MISSING: Alert[rust/summary/taint-sources] + let mut f3 = std::fs::OpenOptions::new().read(true).write(true).truncate(true).create(true).open("f3.txt").unwrap(); // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 1024]; let _bytes = f3.read(&mut buffer)?; - sink(&buffer); // $ MISSING: hasTaintFlow="f3.txt" + sink(&buffer); // $ hasTaintFlow="f3.txt" } // --- misc operations --- @@ -624,7 +624,7 @@ async fn test_tokio_file() -> std::io::Result<()> { // --- OpenOptions --- { - let mut f1 = tokio::fs::OpenOptions::new().open("f1.txt").await?; // $ MISSING: Alert[rust/summary/taint-sources] + let mut f1 = tokio::fs::OpenOptions::new().open("f1.txt").await?; // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 1024]; let _bytes = f1.read(&mut buffer).await?; sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" @@ -668,7 +668,7 @@ async fn test_async_std_file() -> std::io::Result<()> { // --- OpenOptions --- { - let mut f1 = async_std::fs::OpenOptions::new().open("f1.txt").await?; // $ MISSING: Alert[rust/summary/taint-sources] + let mut f1 = async_std::fs::OpenOptions::new().open("f1.txt").await?; // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 1024]; let _bytes = f1.read(&mut buffer).await?; sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index f8ba7f20b332..552e6c371658 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -148,21 +148,21 @@ fn sinks(path1: &Path, path2: &Path) { let _ = std::fs::File::open_buffered(path1); // $ path-injection-sink let _ = std::fs::DirBuilder::new().create(path1); // $ path-injection-sink let _ = std::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink - let _ = std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + let _ = std::fs::OpenOptions::new().open(path1); // $ path-injection-sink let _ = tokio::fs::read(path1); // $ path-injection-sink let _ = tokio::fs::read_to_string(path1); // $ path-injection-sink let _ = tokio::fs::remove_file(path1); // $ path-injection-sink let _ = tokio::fs::DirBuilder::new().create(path1); // $ path-injection-sink let _ = tokio::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink - let _ = tokio::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + let _ = tokio::fs::OpenOptions::new().open(path1); // $ path-injection-sink let _ = async_std::fs::read(path1); // $ path-injection-sink let _ = async_std::fs::read_to_string(path1); // $ path-injection-sink let _ = async_std::fs::remove_file(path1); // $ path-injection-sink let _ = async_std::fs::DirBuilder::new().create(path1); // $ path-injection-sink let _ = async_std::fs::DirBuilder::new().recursive(true).create(path1); // $ path-injection-sink - let _ = async_std::fs::OpenOptions::new().open(path1); // $ MISSING: path-injection-sink + let _ = async_std::fs::OpenOptions::new().open(path1); // $ path-injection-sink } fn main() {} From d1a5c9b297a317dd9d107269a8fc05cd7a73cf4c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:27:28 +0100 Subject: [PATCH 12/18] Rust: Add a test case resembling code seen in the wild. --- .../security/CWE-022/TaintedPath.expected | 20 ++++++++++++++++++ .../query-tests/security/CWE-022/src/main.rs | 21 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index 532d5d316e86..d319db7cbde0 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -6,6 +6,7 @@ | src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:122:13:122:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:122:13:122:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:123:13:123:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:123:13:123:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:174:25:174:34 | ...::open | src/main.rs:185:17:185:30 | ...::args | src/main.rs:174:25:174:34 | ...::open | This path depends on a $@. | src/main.rs:185:17:185:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | @@ -49,6 +50,15 @@ edges | src/main.rs:122:27:122:39 | path1.clone() | src/main.rs:122:13:122:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | | src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:7 | | src/main.rs:123:37:123:49 | path1.clone() | src/main.rs:123:13:123:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | +| src/main.rs:170:16:170:29 | ...: ... [&ref] | src/main.rs:174:36:174:43 | path_str [&ref] | provenance | | +| src/main.rs:174:36:174:43 | path_str [&ref] | src/main.rs:174:25:174:34 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:185:9:185:13 | path1 | src/main.rs:186:18:186:22 | path1 | provenance | | +| src/main.rs:185:17:185:30 | ...::args | src/main.rs:185:17:185:32 | ...::args(...) [element] | provenance | Src:MaD:6 | +| src/main.rs:185:17:185:32 | ...::args(...) [element] | src/main.rs:185:17:185:39 | ... .nth(...) [Some] | provenance | MaD:8 | +| src/main.rs:185:17:185:39 | ... .nth(...) [Some] | src/main.rs:185:17:185:48 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:185:17:185:48 | ... .unwrap() | src/main.rs:185:9:185:13 | path1 | provenance | | +| src/main.rs:186:17:186:22 | &path1 [&ref] | src/main.rs:170:16:170:29 | ...: ... [&ref] | provenance | | +| src/main.rs:186:18:186:22 | path1 | src/main.rs:186:17:186:22 | &path1 [&ref] | provenance | | models | 1 | Sink: ::open; Argument[0]; path-injection | | 2 | Sink: ::open; Argument[0]; path-injection | @@ -108,4 +118,14 @@ nodes | src/main.rs:123:13:123:25 | ...::copy | semmle.label | ...::copy | | src/main.rs:123:37:123:41 | path1 | semmle.label | path1 | | src/main.rs:123:37:123:49 | path1.clone() | semmle.label | path1.clone() | +| src/main.rs:170:16:170:29 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| src/main.rs:174:25:174:34 | ...::open | semmle.label | ...::open | +| src/main.rs:174:36:174:43 | path_str [&ref] | semmle.label | path_str [&ref] | +| src/main.rs:185:9:185:13 | path1 | semmle.label | path1 | +| src/main.rs:185:17:185:30 | ...::args | semmle.label | ...::args | +| src/main.rs:185:17:185:32 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | +| src/main.rs:185:17:185:39 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] | +| src/main.rs:185:17:185:48 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:186:17:186:22 | &path1 [&ref] | semmle.label | &path1 [&ref] | +| src/main.rs:186:18:186:22 | path1 | semmle.label | path1 | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 552e6c371658..061b716476d8 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -165,4 +165,23 @@ fn sinks(path1: &Path, path2: &Path) { let _ = async_std::fs::OpenOptions::new().open(path1); // $ path-injection-sink } -fn main() {} +use std::fs::File; + +fn my_function(path_str: &str) -> Result<(), std::io::Error> { + // somewhat realistic example + let path = Path::new(path_str); + if path.exists() { // $ path-injection-sink + let mut file1 = File::open(path_str)?; // $ path-injection-sink Alert[rust/path-injection]=arg2 + // ... + + let mut file2 = File::open(path)?; // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg2 + // ... + } + + Ok(()) +} + +fn main() { + let path1 = std::env::args().nth(1).unwrap(); // $ Source=arg2 + my_function(&path1); +} From 2f2a975350b40040d8b11c56b5d96f21d75955d0 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:46:40 +0100 Subject: [PATCH 13/18] Rust: Model path::new. --- .../rust/frameworks/stdlib/fs.model.yml | 1 + .../security/CWE-022/TaintedPath.expected | 117 ++++++++++++------ .../query-tests/security/CWE-022/src/main.rs | 10 +- 3 files changed, 87 insertions(+), 41 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml index 851553c8d726..5e899f81a17e 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml @@ -45,6 +45,7 @@ extensions: data: - ["std::fs::canonicalize", "Argument[0]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - ["::from", "Argument[0]", "ReturnValue", "taint", "manual"] + - ["::new", "Argument[0].Reference", "ReturnValue.Reference", "taint", "manual"] - ["::join", "Argument[self]", "ReturnValue", "taint", "manual"] - ["::join", "Argument[0]", "ReturnValue", "taint", "manual"] - ["::canonicalize", "Argument[self].OptionalStep[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index d319db7cbde0..b37bf0c3d291 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -1,79 +1,104 @@ #select | src/main.rs:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | user-provided value | +| src/main.rs:71:5:71:22 | ...::read_to_string | src/main.rs:63:11:63:19 | file_path | src/main.rs:71:5:71:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:63:11:63:19 | file_path | user-provided value | | src/main.rs:104:13:104:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:104:13:104:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:107:13:107:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:107:13:107:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:110:13:110:33 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:110:13:110:33 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:116:13:116:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:116:13:116:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:122:13:122:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:122:13:122:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:123:13:123:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:123:13:123:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:173:13:173:18 | exists | src/main.rs:185:17:185:30 | ...::args | src/main.rs:173:13:173:18 | exists | This path depends on a $@. | src/main.rs:185:17:185:30 | ...::args | user-provided value | | src/main.rs:174:25:174:34 | ...::open | src/main.rs:185:17:185:30 | ...::args | src/main.rs:174:25:174:34 | ...::open | This path depends on a $@. | src/main.rs:185:17:185:30 | ...::args | user-provided value | +| src/main.rs:177:25:177:34 | ...::open | src/main.rs:185:17:185:30 | ...::args | src/main.rs:177:25:177:34 | ...::open | This path depends on a $@. | src/main.rs:185:17:185:30 | ...::args | user-provided value | edges | src/main.rs:7:11:7:19 | file_name | src/main.rs:9:35:9:43 | file_name | provenance | | | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:11 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:11 | -| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:5 Sink:MaD:5 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:13 | +| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:13 | +| src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | +| src/main.rs:63:11:63:19 | file_path | src/main.rs:66:32:66:40 | file_path | provenance | | +| src/main.rs:66:9:66:17 | file_path [&ref] | src/main.rs:71:24:71:32 | file_path [&ref] | provenance | | +| src/main.rs:66:21:66:41 | ...::new(...) [&ref] | src/main.rs:66:9:66:17 | file_path [&ref] | provenance | | +| src/main.rs:66:31:66:40 | &file_path [&ref] | src/main.rs:66:21:66:41 | ...::new(...) [&ref] | provenance | MaD:12 | +| src/main.rs:66:32:66:40 | file_path | src/main.rs:66:31:66:40 | &file_path [&ref] | provenance | | +| src/main.rs:71:24:71:32 | file_path [&ref] | src/main.rs:71:5:71:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:106:39:106:43 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:109:41:109:45 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:112:45:112:49 | path1 | provenance | | +| src/main.rs:103:9:103:13 | path1 | src/main.rs:115:39:115:43 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:122:27:122:31 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:123:37:123:41 | path1 | provenance | | -| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:6 | -| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:8 | -| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:103:17:103:30 | ...::args | src/main.rs:103:17:103:32 | ...::args(...) [element] | provenance | Src:MaD:7 | +| src/main.rs:103:17:103:32 | ...::args(...) [element] | src/main.rs:103:17:103:39 | ... .nth(...) [Some] | provenance | MaD:9 | +| src/main.rs:103:17:103:39 | ... .nth(...) [Some] | src/main.rs:103:17:103:48 | ... .unwrap() | provenance | MaD:10 | | src/main.rs:103:17:103:48 | ... .unwrap() | src/main.rs:103:9:103:13 | path1 | provenance | | -| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:7 | +| src/main.rs:104:33:104:37 | path1 | src/main.rs:104:33:104:45 | path1.clone() | provenance | MaD:8 | | src/main.rs:104:33:104:45 | path1.clone() | src/main.rs:104:13:104:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:106:9:106:13 | path2 | src/main.rs:107:33:107:37 | path2 | provenance | | -| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:10 | +| src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:106:17:106:61 | ... .unwrap() | src/main.rs:106:9:106:13 | path2 | provenance | | -| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:7 | -| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:13 | +| src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:8 | +| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:15 | | src/main.rs:107:33:107:37 | path2 | src/main.rs:107:13:107:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:109:9:109:13 | path3 | src/main.rs:110:35:110:39 | path3 | provenance | | | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | src/main.rs:109:17:109:60 | await ... [Ok] | provenance | | -| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:10 | +| src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:109:17:109:69 | ... .unwrap() | src/main.rs:109:9:109:13 | path3 | provenance | | -| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:7 | -| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:14 | -| src/main.rs:110:35:110:39 | path3 | src/main.rs:110:13:110:33 | ...::open | provenance | MaD:3 Sink:MaD:3 | +| src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:8 | +| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:16 | +| src/main.rs:110:35:110:39 | path3 | src/main.rs:110:13:110:33 | ...::open | provenance | MaD:4 Sink:MaD:4 | | src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | -| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:10 | +| src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:112:17:112:73 | ... .unwrap() | src/main.rs:112:9:112:13 | path4 | provenance | | -| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:7 | -| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:12 | +| src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:8 | +| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:14 | | src/main.rs:113:39:113:43 | path4 | src/main.rs:113:13:113:37 | ...::open | provenance | MaD:1 Sink:MaD:1 | -| src/main.rs:122:27:122:31 | path1 | src/main.rs:122:27:122:39 | path1.clone() | provenance | MaD:7 | -| src/main.rs:122:27:122:39 | path1.clone() | src/main.rs:122:13:122:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | -| src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:7 | -| src/main.rs:123:37:123:49 | path1.clone() | src/main.rs:123:13:123:25 | ...::copy | provenance | MaD:4 Sink:MaD:4 | +| src/main.rs:115:9:115:13 | path5 [&ref] | src/main.rs:116:33:116:37 | path5 [&ref] | provenance | | +| src/main.rs:115:17:115:44 | ...::new(...) [&ref] | src/main.rs:115:9:115:13 | path5 [&ref] | provenance | | +| src/main.rs:115:38:115:43 | &path1 [&ref] | src/main.rs:115:17:115:44 | ...::new(...) [&ref] | provenance | MaD:12 | +| src/main.rs:115:39:115:43 | path1 | src/main.rs:115:38:115:43 | &path1 [&ref] | provenance | | +| src/main.rs:116:33:116:37 | path5 [&ref] | src/main.rs:116:13:116:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:122:27:122:31 | path1 | src/main.rs:122:27:122:39 | path1.clone() | provenance | MaD:8 | +| src/main.rs:122:27:122:39 | path1.clone() | src/main.rs:122:13:122:25 | ...::copy | provenance | MaD:5 Sink:MaD:5 | +| src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:8 | +| src/main.rs:123:37:123:49 | path1.clone() | src/main.rs:123:13:123:25 | ...::copy | provenance | MaD:5 Sink:MaD:5 | +| src/main.rs:170:16:170:29 | ...: ... [&ref] | src/main.rs:172:26:172:33 | path_str [&ref] | provenance | | | src/main.rs:170:16:170:29 | ...: ... [&ref] | src/main.rs:174:36:174:43 | path_str [&ref] | provenance | | +| src/main.rs:172:9:172:12 | path [&ref] | src/main.rs:173:8:173:11 | path [&ref] | provenance | | +| src/main.rs:172:16:172:34 | ...::new(...) [&ref] | src/main.rs:172:9:172:12 | path [&ref] | provenance | | +| src/main.rs:172:26:172:33 | path_str [&ref] | src/main.rs:172:16:172:34 | ...::new(...) [&ref] | provenance | MaD:12 | +| src/main.rs:173:8:173:11 | path [&ref] | src/main.rs:173:13:173:18 | exists | provenance | MaD:3 Sink:MaD:3 | +| src/main.rs:173:8:173:11 | path [&ref] | src/main.rs:177:36:177:39 | path [&ref] | provenance | | | src/main.rs:174:36:174:43 | path_str [&ref] | src/main.rs:174:25:174:34 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:177:36:177:39 | path [&ref] | src/main.rs:177:25:177:34 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:185:9:185:13 | path1 | src/main.rs:186:18:186:22 | path1 | provenance | | -| src/main.rs:185:17:185:30 | ...::args | src/main.rs:185:17:185:32 | ...::args(...) [element] | provenance | Src:MaD:6 | -| src/main.rs:185:17:185:32 | ...::args(...) [element] | src/main.rs:185:17:185:39 | ... .nth(...) [Some] | provenance | MaD:8 | -| src/main.rs:185:17:185:39 | ... .nth(...) [Some] | src/main.rs:185:17:185:48 | ... .unwrap() | provenance | MaD:9 | +| src/main.rs:185:17:185:30 | ...::args | src/main.rs:185:17:185:32 | ...::args(...) [element] | provenance | Src:MaD:7 | +| src/main.rs:185:17:185:32 | ...::args(...) [element] | src/main.rs:185:17:185:39 | ... .nth(...) [Some] | provenance | MaD:9 | +| src/main.rs:185:17:185:39 | ... .nth(...) [Some] | src/main.rs:185:17:185:48 | ... .unwrap() | provenance | MaD:10 | | src/main.rs:185:17:185:48 | ... .unwrap() | src/main.rs:185:9:185:13 | path1 | provenance | | | src/main.rs:186:17:186:22 | &path1 [&ref] | src/main.rs:170:16:170:29 | ...: ... [&ref] | provenance | | | src/main.rs:186:18:186:22 | path1 | src/main.rs:186:17:186:22 | &path1 [&ref] | provenance | | models | 1 | Sink: ::open; Argument[0]; path-injection | | 2 | Sink: ::open; Argument[0]; path-injection | -| 3 | Sink: ::open; Argument[0]; path-injection | -| 4 | Sink: std::fs::copy; Argument[0,1]; path-injection | -| 5 | Sink: std::fs::read_to_string; Argument[0]; path-injection | -| 6 | Source: std::env::args; ReturnValue.Element; commandargs | -| 7 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | -| 8 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 9 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 10 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | -| 11 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 12 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | -| 13 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | -| 14 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 3 | Sink: ::exists; Argument[self]; path-injection | +| 4 | Sink: ::open; Argument[0]; path-injection | +| 5 | Sink: std::fs::copy; Argument[0,1]; path-injection | +| 6 | Sink: std::fs::read_to_string; Argument[0]; path-injection | +| 7 | Source: std::env::args; ReturnValue.Element; commandargs | +| 8 | Summary: <_ as core::clone::Clone>::clone; Argument[self].Reference; ReturnValue; value | +| 9 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 10 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 11 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 12 | Summary: ::new; Argument[0].Reference; ReturnValue.Reference; taint | +| 13 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 14 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 15 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | +| 16 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -81,6 +106,13 @@ nodes | src/main.rs:9:35:9:43 | file_name | semmle.label | file_name | | src/main.rs:11:5:11:22 | ...::read_to_string | semmle.label | ...::read_to_string | | src/main.rs:11:24:11:32 | file_path | semmle.label | file_path | +| src/main.rs:63:11:63:19 | file_path | semmle.label | file_path | +| src/main.rs:66:9:66:17 | file_path [&ref] | semmle.label | file_path [&ref] | +| src/main.rs:66:21:66:41 | ...::new(...) [&ref] | semmle.label | ...::new(...) [&ref] | +| src/main.rs:66:31:66:40 | &file_path [&ref] | semmle.label | &file_path [&ref] | +| src/main.rs:66:32:66:40 | file_path | semmle.label | file_path | +| src/main.rs:71:5:71:22 | ...::read_to_string | semmle.label | ...::read_to_string | +| src/main.rs:71:24:71:32 | file_path [&ref] | semmle.label | file_path [&ref] | | src/main.rs:103:9:103:13 | path1 | semmle.label | path1 | | src/main.rs:103:17:103:30 | ...::args | semmle.label | ...::args | | src/main.rs:103:17:103:32 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | @@ -112,6 +144,12 @@ nodes | src/main.rs:112:45:112:57 | path1.clone() | semmle.label | path1.clone() | | src/main.rs:113:13:113:37 | ...::open | semmle.label | ...::open | | src/main.rs:113:39:113:43 | path4 | semmle.label | path4 | +| src/main.rs:115:9:115:13 | path5 [&ref] | semmle.label | path5 [&ref] | +| src/main.rs:115:17:115:44 | ...::new(...) [&ref] | semmle.label | ...::new(...) [&ref] | +| src/main.rs:115:38:115:43 | &path1 [&ref] | semmle.label | &path1 [&ref] | +| src/main.rs:115:39:115:43 | path1 | semmle.label | path1 | +| src/main.rs:116:13:116:31 | ...::open | semmle.label | ...::open | +| src/main.rs:116:33:116:37 | path5 [&ref] | semmle.label | path5 [&ref] | | src/main.rs:122:13:122:25 | ...::copy | semmle.label | ...::copy | | src/main.rs:122:27:122:31 | path1 | semmle.label | path1 | | src/main.rs:122:27:122:39 | path1.clone() | semmle.label | path1.clone() | @@ -119,8 +157,15 @@ nodes | src/main.rs:123:37:123:41 | path1 | semmle.label | path1 | | src/main.rs:123:37:123:49 | path1.clone() | semmle.label | path1.clone() | | src/main.rs:170:16:170:29 | ...: ... [&ref] | semmle.label | ...: ... [&ref] | +| src/main.rs:172:9:172:12 | path [&ref] | semmle.label | path [&ref] | +| src/main.rs:172:16:172:34 | ...::new(...) [&ref] | semmle.label | ...::new(...) [&ref] | +| src/main.rs:172:26:172:33 | path_str [&ref] | semmle.label | path_str [&ref] | +| src/main.rs:173:8:173:11 | path [&ref] | semmle.label | path [&ref] | +| src/main.rs:173:13:173:18 | exists | semmle.label | exists | | src/main.rs:174:25:174:34 | ...::open | semmle.label | ...::open | | src/main.rs:174:36:174:43 | path_str [&ref] | semmle.label | path_str [&ref] | +| src/main.rs:177:25:177:34 | ...::open | semmle.label | ...::open | +| src/main.rs:177:36:177:39 | path [&ref] | semmle.label | path [&ref] | | src/main.rs:185:9:185:13 | path1 | semmle.label | path1 | | src/main.rs:185:17:185:30 | ...::args | semmle.label | ...::args | | src/main.rs:185:17:185:32 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 061b716476d8..1dce8d590bac 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -60,7 +60,7 @@ fn tainted_path_handler_folder_good_simpler(Query(file_path): Query) -> //#[handler] fn tainted_path_handler_folder_almost_good1_simpler( - Query(file_path): Query, // $ MISSING: Source=remote3 + Query(file_path): Query, // $ Source=remote3 ) -> Result { let public_path = "/var/www/public_html"; let file_path = Path::new(&file_path); @@ -68,7 +68,7 @@ fn tainted_path_handler_folder_almost_good1_simpler( 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-checked path-injection-sink MISSING: Alert[rust/path-injection]=remote3 + fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-checked path-injection-sink Alert[rust/path-injection]=remote3 } //#[handler] @@ -113,7 +113,7 @@ async fn more_simple_cases() { let _ = async_std::fs::File::open(path4); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path5 = std::path::Path::new(&path1); - let _ = std::fs::File::open(path5); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 + let _ = std::fs::File::open(path5); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path6 = path5.canonicalize().unwrap(); let _ = std::fs::File::open(path6); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 @@ -170,11 +170,11 @@ use std::fs::File; fn my_function(path_str: &str) -> Result<(), std::io::Error> { // somewhat realistic example let path = Path::new(path_str); - if path.exists() { // $ path-injection-sink + if path.exists() { // $ path-injection-sink Alert[rust/path-injection]=arg2 let mut file1 = File::open(path_str)?; // $ path-injection-sink Alert[rust/path-injection]=arg2 // ... - let mut file2 = File::open(path)?; // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg2 + let mut file2 = File::open(path)?; // $ path-injection-sink Alert[rust/path-injection]=arg2 // ... } From 40b9754071833bb87cf1bb46c3d75403e30d1ffa Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:06:01 +0100 Subject: [PATCH 14/18] Rust: Change note. --- rust/ql/lib/change-notes/2025-08-22-fs.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/lib/change-notes/2025-08-22-fs.md diff --git a/rust/ql/lib/change-notes/2025-08-22-fs.md b/rust/ql/lib/change-notes/2025-08-22-fs.md new file mode 100644 index 000000000000..4de91616bb35 --- /dev/null +++ b/rust/ql/lib/change-notes/2025-08-22-fs.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improved modelling of the `std::fs`, `async_std::fs` and `tokio::fs` libraries. This may cause more alerts to be found by Rust injection queries, particularly `rust/path-injection`. From 7279b0318bddfff2300dd1225fa87b53a3f42731 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:50:32 +0100 Subject: [PATCH 15/18] Rust: Model a few more Path and PathBuf methods. --- .../rust/frameworks/stdlib/fs.model.yml | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml index 5e899f81a17e..3acf1b12d8df 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml @@ -44,9 +44,27 @@ extensions: extensible: summaryModel data: - ["std::fs::canonicalize", "Argument[0]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - - ["::from", "Argument[0]", "ReturnValue", "taint", "manual"] - - ["::new", "Argument[0].Reference", "ReturnValue.Reference", "taint", "manual"] + - ["::from", "Argument[0]", "ReturnValue", "value", "manual"] + - ["::as_path", "Argument[Self]", "ReturnValue.Reference", "value", "manual"] + - ["::as_mut_os_string", "Argument[Self].Reference", "ReturnValue.Reference", "value", "manual"] + - ["::into_os_string", "Argument[Self]", "ReturnValue", "value", "manual"] + - ["::into_boxed_path", "Argument[Self]", "ReturnValue.Reference", "value", "manual"] + - ["::new", "Argument[0].Reference", "ReturnValue.Reference", "value", "manual"] - ["::join", "Argument[self]", "ReturnValue", "taint", "manual"] - ["::join", "Argument[0]", "ReturnValue", "taint", "manual"] + - ["::as_os_string", "Argument[Self].Reference", "ReturnValue.Reference", "value", "manual"] + - ["::as_mut_os_string", "Argument[Self].Reference", "ReturnValue.Reference", "value", "manual"] - ["::canonicalize", "Argument[self].OptionalStep[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - ["::canonicalize", "Argument[self].OptionalBarrier[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["::extension", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] + - ["::file_name", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] + - ["::file_prefix", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] + - ["::file_stem", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] + - ["::into_path_buf", "Argument[Self].Reference", "ReturnValue", "value", "manual"] + - ["::parent", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] + - ["::to_path_buf", "Argument[Self].Reference", "ReturnValue", "value", "manual"] + - ["::to_str", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "value", "manual"] + - ["::with_added_extension", "Argument[Self].Reference", "ReturnValue", "taint", "manual"] + - ["::with_extension", "Argument[Self].Reference", "ReturnValue", "taint", "manual"] + - ["::with_file_name", "Argument[Self].Reference", "ReturnValue", "taint", "manual"] + - ["::with_file_name", "Argument[0]", "ReturnValue", "taint", "manual"] From 701aec1c8e1ffbbd8f7838e058eb7bf66cd2f0f7 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 22 Aug 2025 13:22:46 +0100 Subject: [PATCH 16/18] Rust: Fix the canonicalize models. --- .../rust/frameworks/asyncstd/fs.model.yml | 3 +- .../rust/frameworks/stdlib/fs.model.yml | 7 +- .../codeql/rust/frameworks/tokio/fs.model.yml | 3 +- .../security/CWE-022/TaintedPath.expected | 65 ++++++++++++++++--- .../query-tests/security/CWE-022/src/main.rs | 10 +-- 5 files changed, 69 insertions(+), 19 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml index f30fc54ecd81..b8c4cf4e10a8 100644 --- a/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/asyncstd/fs.model.yml @@ -38,4 +38,5 @@ extensions: pack: codeql/rust-all extensible: summaryModel data: - - ["async_std::fs::canonicalize::canonicalize", "Argument[0]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["async_std::fs::canonicalize::canonicalize", "Argument[0].OptionalStep[normalize-path]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["async_std::fs::canonicalize::canonicalize", "Argument[0].OptionalBarrier[normalize-path]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml index 3acf1b12d8df..848bb22a5baf 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml @@ -43,7 +43,8 @@ extensions: pack: codeql/rust-all extensible: summaryModel data: - - ["std::fs::canonicalize", "Argument[0]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["std::fs::canonicalize", "Argument[0].OptionalStep[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["std::fs::canonicalize", "Argument[0].OptionalBarrier[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - ["::from", "Argument[0]", "ReturnValue", "value", "manual"] - ["::as_path", "Argument[Self]", "ReturnValue.Reference", "value", "manual"] - ["::as_mut_os_string", "Argument[Self].Reference", "ReturnValue.Reference", "value", "manual"] @@ -54,8 +55,8 @@ extensions: - ["::join", "Argument[0]", "ReturnValue", "taint", "manual"] - ["::as_os_string", "Argument[Self].Reference", "ReturnValue.Reference", "value", "manual"] - ["::as_mut_os_string", "Argument[Self].Reference", "ReturnValue.Reference", "value", "manual"] - - ["::canonicalize", "Argument[self].OptionalStep[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - - ["::canonicalize", "Argument[self].OptionalBarrier[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["::canonicalize", "Argument[self].Reference.OptionalStep[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["::canonicalize", "Argument[self].Reference.OptionalBarrier[normalize-path]", "ReturnValue.Field[core::result::Result::Ok(0)]", "taint", "manual"] - ["::extension", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] - ["::file_name", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] - ["::file_prefix", "Argument[Self].Reference", "ReturnValue.Field[core::option::Option::Some(0)].Reference", "taint", "manual"] diff --git a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml index 3e051d15dd7c..f2b41988fc09 100644 --- a/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/tokio/fs.model.yml @@ -43,4 +43,5 @@ extensions: pack: codeql/rust-all extensible: summaryModel data: - - ["tokio::fs::canonicalize::canonicalize", "Argument[0]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["tokio::fs::canonicalize::canonicalize", "Argument[0].OptionalStep[normalize-path]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] + - ["tokio::fs::canonicalize::canonicalize", "Argument[0].OptionalBarrier[normalize-path]", "ReturnValue.Future.Field[core::result::Result::Ok(0)]", "taint", "manual"] diff --git a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected index b37bf0c3d291..79a98e594e3f 100644 --- a/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected +++ b/rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected @@ -1,11 +1,14 @@ #select | src/main.rs:11:5:11:22 | ...::read_to_string | src/main.rs:7:11:7:19 | file_name | src/main.rs:11:5:11:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:7:11:7:19 | file_name | user-provided value | +| src/main.rs:58:5:58:22 | ...::read_to_string | src/main.rs:50:51:50:59 | file_path | src/main.rs:58:5:58:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:50:51:50:59 | file_path | user-provided value | | src/main.rs:71:5:71:22 | ...::read_to_string | src/main.rs:63:11:63:19 | file_path | src/main.rs:71:5:71:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:63:11:63:19 | file_path | user-provided value | +| src/main.rs:99:5:99:22 | ...::read_to_string | src/main.rs:90:11:90:19 | file_path | src/main.rs:99:5:99:22 | ...::read_to_string | This path depends on a $@. | src/main.rs:90:11:90:19 | file_path | user-provided value | | src/main.rs:104:13:104:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:104:13:104:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:107:13:107:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:107:13:107:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:110:13:110:33 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:110:13:110:33 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:113:13:113:37 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:113:13:113:37 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:116:13:116:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:116:13:116:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | +| src/main.rs:119:13:119:31 | ...::open | src/main.rs:103:17:103:30 | ...::args | src/main.rs:119:13:119:31 | ...::open | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:122:13:122:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:122:13:122:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:123:13:123:25 | ...::copy | src/main.rs:103:17:103:30 | ...::args | src/main.rs:123:13:123:25 | ...::copy | This path depends on a $@. | src/main.rs:103:17:103:30 | ...::args | user-provided value | | src/main.rs:173:13:173:18 | exists | src/main.rs:185:17:185:30 | ...::args | src/main.rs:173:13:173:18 | exists | This path depends on a $@. | src/main.rs:185:17:185:30 | ...::args | user-provided value | @@ -16,14 +19,31 @@ edges | src/main.rs:9:9:9:17 | file_path | src/main.rs:11:24:11:32 | file_path | provenance | | | src/main.rs:9:21:9:44 | ...::from(...) | src/main.rs:9:9:9:17 | file_path | provenance | | | src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:13 | -| src/main.rs:9:35:9:43 | file_name | src/main.rs:9:21:9:44 | ...::from(...) | provenance | MaD:13 | | src/main.rs:11:24:11:32 | file_path | src/main.rs:11:5:11:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | +| src/main.rs:50:51:50:59 | file_path | src/main.rs:52:32:52:40 | file_path | provenance | | +| src/main.rs:52:9:52:17 | file_path [&ref] | src/main.rs:53:21:53:44 | file_path.canonicalize() [Ok] | provenance | Config | +| src/main.rs:52:21:52:41 | ...::new(...) [&ref] | src/main.rs:52:9:52:17 | file_path [&ref] | provenance | | +| src/main.rs:52:31:52:40 | &file_path [&ref] | src/main.rs:52:21:52:41 | ...::new(...) [&ref] | provenance | MaD:12 | +| src/main.rs:52:32:52:40 | file_path | src/main.rs:52:31:52:40 | &file_path [&ref] | provenance | | +| src/main.rs:53:9:53:17 | file_path | src/main.rs:58:24:58:32 | file_path | provenance | | +| src/main.rs:53:21:53:44 | file_path.canonicalize() [Ok] | src/main.rs:53:21:53:53 | ... .unwrap() | provenance | MaD:11 | +| src/main.rs:53:21:53:53 | ... .unwrap() | src/main.rs:53:9:53:17 | file_path | provenance | | +| src/main.rs:58:24:58:32 | file_path | src/main.rs:58:5:58:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | | src/main.rs:63:11:63:19 | file_path | src/main.rs:66:32:66:40 | file_path | provenance | | | src/main.rs:66:9:66:17 | file_path [&ref] | src/main.rs:71:24:71:32 | file_path [&ref] | provenance | | | src/main.rs:66:21:66:41 | ...::new(...) [&ref] | src/main.rs:66:9:66:17 | file_path [&ref] | provenance | | | src/main.rs:66:31:66:40 | &file_path [&ref] | src/main.rs:66:21:66:41 | ...::new(...) [&ref] | provenance | MaD:12 | | src/main.rs:66:32:66:40 | file_path | src/main.rs:66:31:66:40 | &file_path [&ref] | provenance | | | src/main.rs:71:24:71:32 | file_path [&ref] | src/main.rs:71:5:71:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | +| src/main.rs:90:11:90:19 | file_path | src/main.rs:93:32:93:40 | file_path | provenance | | +| src/main.rs:93:9:93:17 | file_path [&ref] | src/main.rs:98:21:98:44 | file_path.canonicalize() [Ok] | provenance | Config | +| src/main.rs:93:21:93:41 | ...::new(...) [&ref] | src/main.rs:93:9:93:17 | file_path [&ref] | provenance | | +| src/main.rs:93:31:93:40 | &file_path [&ref] | src/main.rs:93:21:93:41 | ...::new(...) [&ref] | provenance | MaD:12 | +| src/main.rs:93:32:93:40 | file_path | src/main.rs:93:31:93:40 | &file_path [&ref] | provenance | | +| src/main.rs:98:9:98:17 | file_path | src/main.rs:99:24:99:32 | file_path | provenance | | +| src/main.rs:98:21:98:44 | file_path.canonicalize() [Ok] | src/main.rs:98:21:98:53 | ... .unwrap() | provenance | MaD:11 | +| src/main.rs:98:21:98:53 | ... .unwrap() | src/main.rs:98:9:98:17 | file_path | provenance | | +| src/main.rs:99:24:99:32 | file_path | src/main.rs:99:5:99:22 | ...::read_to_string | provenance | MaD:6 Sink:MaD:6 | | src/main.rs:103:9:103:13 | path1 | src/main.rs:104:33:104:37 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:106:39:106:43 | path1 | provenance | | | src/main.rs:103:9:103:13 | path1 | src/main.rs:109:41:109:45 | path1 | provenance | | @@ -41,27 +61,32 @@ edges | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | src/main.rs:106:17:106:61 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:106:17:106:61 | ... .unwrap() | src/main.rs:106:9:106:13 | path2 | provenance | | | src/main.rs:106:39:106:43 | path1 | src/main.rs:106:39:106:51 | path1.clone() | provenance | MaD:8 | -| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | MaD:15 | +| src/main.rs:106:39:106:51 | path1.clone() | src/main.rs:106:17:106:52 | ...::canonicalize(...) [Ok] | provenance | Config | | src/main.rs:107:33:107:37 | path2 | src/main.rs:107:13:107:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:109:9:109:13 | path3 | src/main.rs:110:35:110:39 | path3 | provenance | | | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | src/main.rs:109:17:109:60 | await ... [Ok] | provenance | | | src/main.rs:109:17:109:60 | await ... [Ok] | src/main.rs:109:17:109:69 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:109:17:109:69 | ... .unwrap() | src/main.rs:109:9:109:13 | path3 | provenance | | | src/main.rs:109:41:109:45 | path1 | src/main.rs:109:41:109:53 | path1.clone() | provenance | MaD:8 | -| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | MaD:16 | +| src/main.rs:109:41:109:53 | path1.clone() | src/main.rs:109:17:109:54 | ...::canonicalize(...) [future, Ok] | provenance | Config | | src/main.rs:110:35:110:39 | path3 | src/main.rs:110:13:110:33 | ...::open | provenance | MaD:4 Sink:MaD:4 | | src/main.rs:112:9:112:13 | path4 | src/main.rs:113:39:113:43 | path4 | provenance | | | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | src/main.rs:112:17:112:64 | await ... [Ok] | provenance | | | src/main.rs:112:17:112:64 | await ... [Ok] | src/main.rs:112:17:112:73 | ... .unwrap() | provenance | MaD:11 | | src/main.rs:112:17:112:73 | ... .unwrap() | src/main.rs:112:9:112:13 | path4 | provenance | | | src/main.rs:112:45:112:49 | path1 | src/main.rs:112:45:112:57 | path1.clone() | provenance | MaD:8 | -| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | MaD:14 | +| src/main.rs:112:45:112:57 | path1.clone() | src/main.rs:112:17:112:58 | ...::canonicalize(...) [future, Ok] | provenance | Config | | src/main.rs:113:39:113:43 | path4 | src/main.rs:113:13:113:37 | ...::open | provenance | MaD:1 Sink:MaD:1 | | src/main.rs:115:9:115:13 | path5 [&ref] | src/main.rs:116:33:116:37 | path5 [&ref] | provenance | | | src/main.rs:115:17:115:44 | ...::new(...) [&ref] | src/main.rs:115:9:115:13 | path5 [&ref] | provenance | | | src/main.rs:115:38:115:43 | &path1 [&ref] | src/main.rs:115:17:115:44 | ...::new(...) [&ref] | provenance | MaD:12 | | src/main.rs:115:39:115:43 | path1 | src/main.rs:115:38:115:43 | &path1 [&ref] | provenance | | | src/main.rs:116:33:116:37 | path5 [&ref] | src/main.rs:116:13:116:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | +| src/main.rs:116:33:116:37 | path5 [&ref] | src/main.rs:118:17:118:36 | path5.canonicalize() [Ok] | provenance | Config | +| src/main.rs:118:9:118:13 | path6 | src/main.rs:119:33:119:37 | path6 | provenance | | +| src/main.rs:118:17:118:36 | path5.canonicalize() [Ok] | src/main.rs:118:17:118:45 | ... .unwrap() | provenance | MaD:11 | +| src/main.rs:118:17:118:45 | ... .unwrap() | src/main.rs:118:9:118:13 | path6 | provenance | | +| src/main.rs:119:33:119:37 | path6 | src/main.rs:119:13:119:31 | ...::open | provenance | MaD:2 Sink:MaD:2 | | src/main.rs:122:27:122:31 | path1 | src/main.rs:122:27:122:39 | path1.clone() | provenance | MaD:8 | | src/main.rs:122:27:122:39 | path1.clone() | src/main.rs:122:13:122:25 | ...::copy | provenance | MaD:5 Sink:MaD:5 | | src/main.rs:123:37:123:41 | path1 | src/main.rs:123:37:123:49 | path1.clone() | provenance | MaD:8 | @@ -94,11 +119,8 @@ models | 9 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value | | 10 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | | 11 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | -| 12 | Summary: ::new; Argument[0].Reference; ReturnValue.Reference; taint | -| 13 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 14 | Summary: async_std::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | -| 15 | Summary: std::fs::canonicalize; Argument[0]; ReturnValue.Field[core::result::Result::Ok(0)]; taint | -| 16 | Summary: tokio::fs::canonicalize::canonicalize; Argument[0]; ReturnValue.Future.Field[core::result::Result::Ok(0)]; taint | +| 12 | Summary: ::new; Argument[0].Reference; ReturnValue.Reference; value | +| 13 | Summary: ::from; Argument[0]; ReturnValue; value | nodes | src/main.rs:7:11:7:19 | file_name | semmle.label | file_name | | src/main.rs:9:9:9:17 | file_path | semmle.label | file_path | @@ -106,6 +128,16 @@ nodes | src/main.rs:9:35:9:43 | file_name | semmle.label | file_name | | src/main.rs:11:5:11:22 | ...::read_to_string | semmle.label | ...::read_to_string | | src/main.rs:11:24:11:32 | file_path | semmle.label | file_path | +| src/main.rs:50:51:50:59 | file_path | semmle.label | file_path | +| src/main.rs:52:9:52:17 | file_path [&ref] | semmle.label | file_path [&ref] | +| src/main.rs:52:21:52:41 | ...::new(...) [&ref] | semmle.label | ...::new(...) [&ref] | +| src/main.rs:52:31:52:40 | &file_path [&ref] | semmle.label | &file_path [&ref] | +| src/main.rs:52:32:52:40 | file_path | semmle.label | file_path | +| src/main.rs:53:9:53:17 | file_path | semmle.label | file_path | +| src/main.rs:53:21:53:44 | file_path.canonicalize() [Ok] | semmle.label | file_path.canonicalize() [Ok] | +| src/main.rs:53:21:53:53 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:58:5:58:22 | ...::read_to_string | semmle.label | ...::read_to_string | +| src/main.rs:58:24:58:32 | file_path | semmle.label | file_path | | src/main.rs:63:11:63:19 | file_path | semmle.label | file_path | | src/main.rs:66:9:66:17 | file_path [&ref] | semmle.label | file_path [&ref] | | src/main.rs:66:21:66:41 | ...::new(...) [&ref] | semmle.label | ...::new(...) [&ref] | @@ -113,6 +145,16 @@ nodes | src/main.rs:66:32:66:40 | file_path | semmle.label | file_path | | src/main.rs:71:5:71:22 | ...::read_to_string | semmle.label | ...::read_to_string | | src/main.rs:71:24:71:32 | file_path [&ref] | semmle.label | file_path [&ref] | +| src/main.rs:90:11:90:19 | file_path | semmle.label | file_path | +| src/main.rs:93:9:93:17 | file_path [&ref] | semmle.label | file_path [&ref] | +| src/main.rs:93:21:93:41 | ...::new(...) [&ref] | semmle.label | ...::new(...) [&ref] | +| src/main.rs:93:31:93:40 | &file_path [&ref] | semmle.label | &file_path [&ref] | +| src/main.rs:93:32:93:40 | file_path | semmle.label | file_path | +| src/main.rs:98:9:98:17 | file_path | semmle.label | file_path | +| src/main.rs:98:21:98:44 | file_path.canonicalize() [Ok] | semmle.label | file_path.canonicalize() [Ok] | +| src/main.rs:98:21:98:53 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:99:5:99:22 | ...::read_to_string | semmle.label | ...::read_to_string | +| src/main.rs:99:24:99:32 | file_path | semmle.label | file_path | | src/main.rs:103:9:103:13 | path1 | semmle.label | path1 | | src/main.rs:103:17:103:30 | ...::args | semmle.label | ...::args | | src/main.rs:103:17:103:32 | ...::args(...) [element] | semmle.label | ...::args(...) [element] | @@ -150,6 +192,11 @@ nodes | src/main.rs:115:39:115:43 | path1 | semmle.label | path1 | | src/main.rs:116:13:116:31 | ...::open | semmle.label | ...::open | | src/main.rs:116:33:116:37 | path5 [&ref] | semmle.label | path5 [&ref] | +| src/main.rs:118:9:118:13 | path6 | semmle.label | path6 | +| src/main.rs:118:17:118:36 | path5.canonicalize() [Ok] | semmle.label | path5.canonicalize() [Ok] | +| src/main.rs:118:17:118:45 | ... .unwrap() | semmle.label | ... .unwrap() | +| src/main.rs:119:13:119:31 | ...::open | semmle.label | ...::open | +| src/main.rs:119:33:119:37 | path6 | semmle.label | path6 | | src/main.rs:122:13:122:25 | ...::copy | semmle.label | ...::copy | | src/main.rs:122:27:122:31 | path1 | semmle.label | path1 | | src/main.rs:122:27:122:39 | path1.clone() | semmle.label | path1.clone() | diff --git a/rust/ql/test/query-tests/security/CWE-022/src/main.rs b/rust/ql/test/query-tests/security/CWE-022/src/main.rs index 1dce8d590bac..7acf036bb6bf 100644 --- a/rust/ql/test/query-tests/security/CWE-022/src/main.rs +++ b/rust/ql/test/query-tests/security/CWE-022/src/main.rs @@ -47,7 +47,7 @@ fn tainted_path_handler_folder_almost_good1( } //#[handler] -fn tainted_path_handler_folder_good_simpler(Query(file_path): Query) -> Result { +fn tainted_path_handler_folder_good_simpler(Query(file_path): Query) -> Result { // $ Source=remote6 let public_path = "/var/www/public_html"; let file_path = Path::new(&file_path); let file_path = file_path.canonicalize().unwrap(); @@ -55,7 +55,7 @@ fn tainted_path_handler_folder_good_simpler(Query(file_path): Query) -> 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 MISSING: path-injection-checked + fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: path-injection-checked SPURIOUS: Alert[rust/path-injection]=remote6 } //#[handler] @@ -87,7 +87,7 @@ fn tainted_path_handler_folder_almost_good2( //#[handler] fn tainted_path_handler_folder_almost_good3( - Query(file_path): Query, // $ MISSING: Source=remote5 + Query(file_path): Query, // $ Source=remote5 ) -> Result { let public_path = "/var/www/public_html"; let file_path = Path::new(&file_path); @@ -96,7 +96,7 @@ fn tainted_path_handler_folder_almost_good3( return Err(Error::from_status(StatusCode::BAD_REQUEST)); } let file_path = file_path.canonicalize().unwrap(); // $ path-injection-checked - fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink MISSING: Alert[rust/path-injection]=remote5 + fs::read_to_string(file_path).map_err(InternalServerError) // $ path-injection-sink Alert[rust/path-injection]=remote5 } async fn more_simple_cases() { @@ -116,7 +116,7 @@ async fn more_simple_cases() { let _ = std::fs::File::open(path5); // $ path-injection-sink Alert[rust/path-injection]=arg1 let path6 = path5.canonicalize().unwrap(); - let _ = std::fs::File::open(path6); // $ path-injection-sink MISSING: Alert[rust/path-injection]=arg1 + let _ = std::fs::File::open(path6); // $ path-injection-sink Alert[rust/path-injection]=arg1 let harmless = ""; let _ = std::fs::copy(path1.clone(), harmless); // $ path-injection-sink Alert[rust/path-injection]=arg1 From 08cb03808646eb59b13083c5c5b335ace4719491 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:17:50 +0100 Subject: [PATCH 17/18] Rust: Accept changes to other tests. --- .../dataflow/local/DataFlowStep.expected | 3 +- .../PathResolutionConsistency.expected | 125 +++++++++--------- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected index 3a830d7d36a6..49698874e22e 100644 --- a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected +++ b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected @@ -1,5 +1,6 @@ localStep -| file://:0:0:0:0 | [summary param] self in fn canonicalize | file://:0:0:0:0 | [summary] read: Argument[self].OptionalBarrier[normalize-path] in fn canonicalize | +| file://:0:0:0:0 | [summary param] 0 in fn canonicalize | file://:0:0:0:0 | [summary] read: Argument[0].OptionalBarrier[normalize-path] in fn canonicalize | +| file://:0:0:0:0 | [summary] read: Argument[self].Reference in fn canonicalize | file://:0:0:0:0 | [summary] read: Argument[self].Reference.OptionalBarrier[normalize-path] in fn canonicalize | | main.rs:4:11:4:11 | [SSA] i | main.rs:5:12:5:12 | i | | main.rs:4:11:4:11 | i | main.rs:4:11:4:11 | [SSA] i | | main.rs:4:11:4:11 | i | main.rs:4:11:4:11 | i | diff --git a/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected index 19b896ddee29..760dc497b3c2 100644 --- a/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected +++ b/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected @@ -11,68 +11,69 @@ multipleCallTargets | test.rs:179:30:179:68 | ...::_print(...) | | test.rs:188:26:188:105 | ...::_print(...) | | test.rs:229:22:229:72 | ... .read_to_string(...) | -| test.rs:483:22:483:50 | file.read_to_end(...) | -| test.rs:489:22:489:53 | file.read_to_string(...) | -| test.rs:610:18:610:38 | ...::_print(...) | -| test.rs:615:18:615:45 | ...::_print(...) | -| test.rs:619:25:619:49 | address.to_socket_addrs() | -| test.rs:633:38:633:42 | ...::_print(...) | -| test.rs:637:38:637:54 | ...::_print(...) | -| test.rs:642:38:642:51 | ...::_print(...) | -| test.rs:652:34:652:52 | ...::_print(...) | -| test.rs:671:14:671:43 | ...::_print(...) | -| test.rs:686:18:686:42 | ...::_print(...) | -| test.rs:690:18:690:42 | ...::_print(...) | -| test.rs:695:18:695:45 | ...::_print(...) | -| test.rs:702:30:702:34 | ...::_print(...) | -| test.rs:706:30:706:52 | ...::_print(...) | -| test.rs:715:30:715:43 | ...::_print(...) | -| test.rs:725:30:725:34 | ...::_print(...) | -| test.rs:729:30:729:52 | ...::_print(...) | -| test.rs:738:30:738:43 | ...::_print(...) | -| test.rs:753:14:753:43 | ...::_print(...) | -| test.rs:767:14:767:34 | ...::_print(...) | -| test.rs:807:50:807:66 | ...::from(...) | -| test.rs:807:50:807:66 | ...::from(...) | -| test.rs:809:14:809:31 | ...::_print(...) | -| test.rs:812:14:812:31 | ...::_print(...) | -| test.rs:815:14:815:31 | ...::_print(...) | -| test.rs:818:14:818:30 | ...::_print(...) | -| test.rs:820:27:820:36 | ...::_print(...) | -| test.rs:821:28:821:41 | ...::_print(...) | -| test.rs:824:14:824:33 | ...::_print(...) | -| test.rs:826:27:826:36 | ...::_print(...) | -| test.rs:827:28:827:41 | ...::_print(...) | -| test.rs:830:14:830:31 | ...::_print(...) | -| test.rs:832:27:832:36 | ...::_print(...) | -| test.rs:833:28:833:41 | ...::_print(...) | -| test.rs:836:14:836:34 | ...::_print(...) | -| test.rs:838:27:838:36 | ...::_print(...) | -| test.rs:839:28:839:41 | ...::_print(...) | -| test.rs:842:14:842:25 | ...::_print(...) | -| test.rs:844:27:844:36 | ...::_print(...) | -| test.rs:845:28:845:41 | ...::_print(...) | -| test.rs:848:14:848:31 | ...::_print(...) | -| test.rs:850:27:850:36 | ...::_print(...) | -| test.rs:851:28:851:41 | ...::_print(...) | -| test.rs:854:14:854:30 | ...::_print(...) | -| test.rs:856:27:856:36 | ...::_print(...) | -| test.rs:857:28:857:41 | ...::_print(...) | -| test.rs:860:14:860:33 | ...::_print(...) | -| test.rs:862:27:862:36 | ...::_print(...) | -| test.rs:863:28:863:41 | ...::_print(...) | -| test.rs:866:14:866:36 | ...::_print(...) | -| test.rs:868:27:868:36 | ...::_print(...) | -| test.rs:869:28:869:41 | ...::_print(...) | -| test.rs:872:14:872:38 | ...::_print(...) | -| test.rs:874:27:874:36 | ...::_print(...) | -| test.rs:875:28:875:41 | ...::_print(...) | -| test.rs:878:14:878:45 | ...::_print(...) | -| test.rs:880:27:880:36 | ...::_print(...) | -| test.rs:881:28:881:41 | ...::_print(...) | -| test.rs:884:14:884:29 | ...::_print(...) | -| test.rs:886:27:886:36 | ...::_print(...) | -| test.rs:887:28:887:41 | ...::_print(...) | +| test.rs:697:18:697:38 | ...::_print(...) | +| test.rs:702:18:702:45 | ...::_print(...) | +| test.rs:706:25:706:49 | address.to_socket_addrs() | +| test.rs:720:38:720:42 | ...::_print(...) | +| test.rs:724:38:724:54 | ...::_print(...) | +| test.rs:729:38:729:51 | ...::_print(...) | +| test.rs:739:34:739:52 | ...::_print(...) | +| test.rs:758:14:758:43 | ...::_print(...) | +| test.rs:773:18:773:42 | ...::_print(...) | +| test.rs:777:18:777:42 | ...::_print(...) | +| test.rs:782:18:782:45 | ...::_print(...) | +| test.rs:789:30:789:34 | ...::_print(...) | +| test.rs:793:30:793:52 | ...::_print(...) | +| test.rs:802:30:802:43 | ...::_print(...) | +| test.rs:812:30:812:34 | ...::_print(...) | +| test.rs:816:30:816:52 | ...::_print(...) | +| test.rs:825:30:825:43 | ...::_print(...) | +| test.rs:840:14:840:43 | ...::_print(...) | +| test.rs:854:14:854:34 | ...::_print(...) | +| test.rs:894:50:894:66 | ...::from(...) | +| test.rs:894:50:894:66 | ...::from(...) | +| test.rs:896:14:896:31 | ...::_print(...) | +| test.rs:899:14:899:31 | ...::_print(...) | +| test.rs:902:14:902:31 | ...::_print(...) | +| test.rs:905:14:905:30 | ...::_print(...) | +| test.rs:907:27:907:36 | ...::_print(...) | +| test.rs:908:28:908:41 | ...::_print(...) | +| test.rs:911:14:911:33 | ...::_print(...) | +| test.rs:913:27:913:36 | ...::_print(...) | +| test.rs:914:28:914:41 | ...::_print(...) | +| test.rs:917:14:917:31 | ...::_print(...) | +| test.rs:919:27:919:36 | ...::_print(...) | +| test.rs:920:28:920:41 | ...::_print(...) | +| test.rs:923:14:923:34 | ...::_print(...) | +| test.rs:925:27:925:36 | ...::_print(...) | +| test.rs:926:28:926:41 | ...::_print(...) | +| test.rs:929:14:929:25 | ...::_print(...) | +| test.rs:931:27:931:36 | ...::_print(...) | +| test.rs:932:28:932:41 | ...::_print(...) | +| test.rs:935:14:935:31 | ...::_print(...) | +| test.rs:937:27:937:36 | ...::_print(...) | +| test.rs:938:28:938:41 | ...::_print(...) | +| test.rs:941:14:941:30 | ...::_print(...) | +| test.rs:943:27:943:36 | ...::_print(...) | +| test.rs:944:28:944:41 | ...::_print(...) | +| test.rs:947:14:947:33 | ...::_print(...) | +| test.rs:949:27:949:36 | ...::_print(...) | +| test.rs:950:28:950:41 | ...::_print(...) | +| test.rs:953:14:953:37 | ...::_print(...) | +| test.rs:955:27:955:36 | ...::_print(...) | +| test.rs:956:28:956:41 | ...::_print(...) | +| test.rs:959:14:959:36 | ...::_print(...) | +| test.rs:961:27:961:36 | ...::_print(...) | +| test.rs:962:28:962:41 | ...::_print(...) | +| test.rs:965:14:965:38 | ...::_print(...) | +| test.rs:967:27:967:36 | ...::_print(...) | +| test.rs:968:28:968:41 | ...::_print(...) | +| test.rs:971:14:971:45 | ...::_print(...) | +| test.rs:973:27:973:36 | ...::_print(...) | +| test.rs:974:28:974:41 | ...::_print(...) | +| test.rs:977:14:977:29 | ...::_print(...) | +| test.rs:979:27:979:36 | ...::_print(...) | +| test.rs:980:28:980:41 | ...::_print(...) | | test_futures_io.rs:35:26:35:63 | pinned.poll_read(...) | | test_futures_io.rs:62:22:62:50 | pinned.poll_fill_buf(...) | | test_futures_io.rs:69:23:69:67 | ... .poll_fill_buf(...) | From 4a4f782d861fa1cdd809c82c8daa3f9e0bd1a7ca Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:54:10 +0100 Subject: [PATCH 18/18] Rust: Accept another consistency test change. --- .../sources/CONSISTENCY/PathResolutionConsistency.expected | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected index 760dc497b3c2..ca419a552fa1 100644 --- a/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected +++ b/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected @@ -11,6 +11,8 @@ multipleCallTargets | test.rs:179:30:179:68 | ...::_print(...) | | test.rs:188:26:188:105 | ...::_print(...) | | test.rs:229:22:229:72 | ... .read_to_string(...) | +| test.rs:513:22:513:50 | file.read_to_end(...) | +| test.rs:519:22:519:53 | file.read_to_string(...) | | test.rs:697:18:697:38 | ...::_print(...) | | test.rs:702:18:702:45 | ...::_print(...) | | test.rs:706:25:706:49 | address.to_socket_addrs() |