From 0c72a2b9823cf56883d1abb8d2bcc6190487a7da Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:52:23 +0100 Subject: [PATCH 1/7] Add test cases involving string appends. --- .../HardcodedCryptographicValue.expected | 82 +++++++++++++------ .../security/CWE-798/test_heuristic.rs | 8 ++ 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected index f416e2b7b387..3aaa5608e616 100644 --- a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected +++ b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected @@ -18,37 +18,40 @@ | test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:22:69:32 | ... + ... | This hard-coded value is used as $@. | test_heuristic.rs:69:22:69:32 | ... + ... | a salt | | test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:22:70:62 | ... ^ ... | This hard-coded value is used as $@. | test_heuristic.rs:70:22:70:62 | ... ^ ... | a salt | | test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:22:70:62 | ... ^ ... | This hard-coded value is used as $@. | test_heuristic.rs:70:22:70:62 | ... ^ ... | a salt | +| test_heuristic.rs:72:20:72:24 | "foo" | test_heuristic.rs:72:20:72:24 | "foo" | test_heuristic.rs:74:28:74:32 | &key1 | This hard-coded value is used as $@. | test_heuristic.rs:74:28:74:32 | &key1 | a password | +| test_heuristic.rs:73:13:73:17 | "bar" | test_heuristic.rs:73:13:73:17 | "bar" | test_heuristic.rs:74:28:74:32 | &key1 | This hard-coded value is used as $@. | test_heuristic.rs:74:28:74:32 | &key1 | a password | +| test_heuristic.rs:76:20:76:24 | "foo" | test_heuristic.rs:76:20:76:24 | "foo" | test_heuristic.rs:78:28:78:32 | &key2 | This hard-coded value is used as $@. | test_heuristic.rs:78:28:78:32 | &key2 | a password | edges | test_cipher.rs:18:9:18:14 | const1 [&ref] | test_cipher.rs:19:73:19:78 | const1 [&ref] | provenance | | | test_cipher.rs:18:28:18:36 | &... [&ref] | test_cipher.rs:18:9:18:14 | const1 [&ref] | provenance | | | test_cipher.rs:18:29:18:36 | [0u8; 16] | test_cipher.rs:18:28:18:36 | &... [&ref] | provenance | | | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref] | test_cipher.rs:19:30:19:47 | ...::new | provenance | MaD:3 Sink:MaD:3 | -| test_cipher.rs:19:73:19:78 | const1 [&ref] | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref] | provenance | MaD:17 | +| test_cipher.rs:19:73:19:78 | const1 [&ref] | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref] | provenance | MaD:20 | | test_cipher.rs:25:9:25:14 | const4 [&ref] | test_cipher.rs:26:66:26:71 | const4 [&ref] | provenance | | | test_cipher.rs:25:28:25:36 | &... [&ref] | test_cipher.rs:25:9:25:14 | const4 [&ref] | provenance | | | test_cipher.rs:25:29:25:36 | [0u8; 16] | test_cipher.rs:25:28:25:36 | &... [&ref] | provenance | | | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref] | test_cipher.rs:26:30:26:40 | ...::new | provenance | MaD:4 Sink:MaD:4 | -| test_cipher.rs:26:66:26:71 | const4 [&ref] | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref] | provenance | MaD:17 | +| test_cipher.rs:26:66:26:71 | const4 [&ref] | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref] | provenance | MaD:20 | | test_cipher.rs:29:9:29:14 | const5 [&ref] | test_cipher.rs:30:95:30:100 | const5 [&ref] | provenance | | | test_cipher.rs:29:28:29:36 | &... [&ref] | test_cipher.rs:29:9:29:14 | const5 [&ref] | provenance | | | test_cipher.rs:29:29:29:36 | [0u8; 16] | test_cipher.rs:29:28:29:36 | &... [&ref] | provenance | | | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref] | test_cipher.rs:30:30:30:40 | ...::new | provenance | MaD:5 Sink:MaD:5 | -| test_cipher.rs:30:95:30:100 | const5 [&ref] | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref] | provenance | MaD:17 | +| test_cipher.rs:30:95:30:100 | const5 [&ref] | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref] | provenance | MaD:20 | | test_cipher.rs:37:9:37:14 | const7 | test_cipher.rs:38:74:38:79 | const7 | provenance | | | test_cipher.rs:37:27:37:74 | [...] | test_cipher.rs:37:9:37:14 | const7 | provenance | | | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | test_cipher.rs:38:30:38:47 | ...::new | provenance | MaD:3 Sink:MaD:3 | -| test_cipher.rs:38:73:38:79 | &const7 [&ref] | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | provenance | MaD:17 | +| test_cipher.rs:38:73:38:79 | &const7 [&ref] | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | provenance | MaD:20 | | test_cipher.rs:38:74:38:79 | const7 | test_cipher.rs:38:73:38:79 | &const7 [&ref] | provenance | | | test_cipher.rs:41:9:41:14 | const8 [&ref] | test_cipher.rs:42:73:42:78 | const8 [&ref] | provenance | | | test_cipher.rs:41:28:41:76 | &... [&ref] | test_cipher.rs:41:9:41:14 | const8 [&ref] | provenance | | | test_cipher.rs:41:29:41:76 | [...] | test_cipher.rs:41:28:41:76 | &... [&ref] | provenance | | | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | test_cipher.rs:42:30:42:47 | ...::new | provenance | MaD:3 Sink:MaD:3 | -| test_cipher.rs:42:73:42:78 | const8 [&ref] | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | provenance | MaD:17 | +| test_cipher.rs:42:73:42:78 | const8 [&ref] | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | provenance | MaD:20 | | test_cipher.rs:50:9:50:15 | const10 [element] | test_cipher.rs:51:75:51:81 | const10 [element] | provenance | | | test_cipher.rs:50:37:50:52 | ...::zeroed | test_cipher.rs:50:37:50:54 | ...::zeroed(...) [element] | provenance | Src:MaD:7 | | test_cipher.rs:50:37:50:54 | ...::zeroed(...) [element] | test_cipher.rs:50:9:50:15 | const10 [element] | provenance | | | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | test_cipher.rs:51:31:51:48 | ...::new | provenance | MaD:3 Sink:MaD:3 Sink:MaD:3 | -| test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | provenance | MaD:17 | +| test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | provenance | MaD:20 | | test_cipher.rs:51:75:51:81 | const10 [element] | test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | provenance | | | test_cipher.rs:73:9:73:14 | const2 [&ref] | test_cipher.rs:74:46:74:51 | const2 [&ref] | provenance | | | test_cipher.rs:73:18:73:26 | &... [&ref] | test_cipher.rs:73:9:73:14 | const2 [&ref] | provenance | | @@ -64,14 +67,14 @@ edges | test_cookie.rs:22:27:22:32 | array2 | test_cookie.rs:22:26:22:32 | &array2 [&ref] | provenance | | | test_cookie.rs:38:9:38:14 | array2 | test_cookie.rs:42:34:42:39 | array2 | provenance | | | test_cookie.rs:38:18:38:37 | ...::from(...) | test_cookie.rs:38:9:38:14 | array2 | provenance | | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:12 | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:13 | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:14 | | test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:15 | | test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:16 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:17 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:18 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:19 | | test_cookie.rs:42:34:42:39 | array2 | test_cookie.rs:42:14:42:32 | ...::from | provenance | MaD:2 Sink:MaD:2 | | test_cookie.rs:49:9:49:14 | array3 [element] | test_cookie.rs:53:34:53:39 | array3 [element] | provenance | | -| test_cookie.rs:49:23:49:25 | 0u8 | test_cookie.rs:49:23:49:29 | ...::from_elem(...) [element] | provenance | MaD:18 | +| test_cookie.rs:49:23:49:25 | 0u8 | test_cookie.rs:49:23:49:29 | ...::from_elem(...) [element] | provenance | MaD:21 | | test_cookie.rs:49:23:49:29 | ...::from_elem(...) [element] | test_cookie.rs:49:9:49:14 | array3 [element] | provenance | | | test_cookie.rs:53:34:53:39 | array3 [element] | test_cookie.rs:53:14:53:32 | ...::from | provenance | MaD:2 Sink:MaD:2 | | test_heuristic.rs:44:9:44:16 | const_iv [&ref] | test_heuristic.rs:45:41:45:48 | const_iv | provenance | | @@ -79,11 +82,22 @@ edges | test_heuristic.rs:44:31:44:38 | [0u8; 16] | test_heuristic.rs:44:30:44:38 | &... [&ref] | provenance | | | test_heuristic.rs:64:20:64:27 | [0u8; 16] | test_heuristic.rs:64:19:64:27 | &... | provenance | | | test_heuristic.rs:65:31:65:38 | [0u8; 16] | test_heuristic.rs:65:30:65:38 | &... | provenance | | -| test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:22:69:32 | ... + ... | provenance | MaD:8 | -| test_heuristic.rs:70:23:70:35 | ... << ... | test_heuristic.rs:70:22:70:62 | ... ^ ... | provenance | MaD:10 | -| test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:23:70:35 | ... << ... | provenance | MaD:11 | -| test_heuristic.rs:70:41:70:61 | ... & ... | test_heuristic.rs:70:22:70:62 | ... ^ ... | provenance | MaD:10 | -| test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:41:70:61 | ... & ... | provenance | MaD:9 | +| test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:22:69:32 | ... + ... | provenance | MaD:9 | +| test_heuristic.rs:70:23:70:35 | ... << ... | test_heuristic.rs:70:22:70:62 | ... ^ ... | provenance | MaD:13 | +| test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:23:70:35 | ... << ... | provenance | MaD:14 | +| test_heuristic.rs:70:41:70:61 | ... & ... | test_heuristic.rs:70:22:70:62 | ... ^ ... | provenance | MaD:13 | +| test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:41:70:61 | ... & ... | provenance | MaD:12 | +| test_heuristic.rs:72:9:72:16 | mut key1 | test_heuristic.rs:73:5:73:8 | key1 | provenance | | +| test_heuristic.rs:72:20:72:24 | "foo" | test_heuristic.rs:72:20:72:36 | "foo".to_string() | provenance | MaD:8 | +| test_heuristic.rs:72:20:72:36 | "foo".to_string() | test_heuristic.rs:72:9:72:16 | mut key1 | provenance | | +| test_heuristic.rs:73:5:73:8 | key1 | test_heuristic.rs:74:29:74:32 | key1 | provenance | MaD:11 | +| test_heuristic.rs:73:13:73:17 | "bar" | test_heuristic.rs:74:29:74:32 | key1 | provenance | MaD:10 | +| test_heuristic.rs:74:29:74:32 | key1 | test_heuristic.rs:74:28:74:32 | &key1 | provenance | | +| test_heuristic.rs:76:9:76:16 | mut key2 | test_heuristic.rs:77:5:77:8 | key2 | provenance | | +| test_heuristic.rs:76:20:76:24 | "foo" | test_heuristic.rs:76:20:76:36 | "foo".to_string() | provenance | MaD:8 | +| test_heuristic.rs:76:20:76:36 | "foo".to_string() | test_heuristic.rs:76:9:76:16 | mut key2 | provenance | | +| test_heuristic.rs:77:5:77:8 | key2 | test_heuristic.rs:78:29:78:32 | key2 | provenance | MaD:11 | +| test_heuristic.rs:78:29:78:32 | key2 | test_heuristic.rs:78:28:78:32 | &key2 | provenance | | models | 1 | Sink: <_ as crypto_common::KeyInit>::new_from_slice; Argument[0]; credentials-key | | 2 | Sink: ::from; Argument[0]; credentials-key | @@ -92,17 +106,20 @@ models | 5 | Sink: ::new; Argument[1]; credentials-iv | | 6 | Sink: ::from; Argument[0].Reference; credentials-key | | 7 | Source: core::mem::zeroed; ReturnValue.Element; constant-source | -| 8 | Summary: <_ as core::ops::arith::Add>::add; Argument[self,0]; ReturnValue; taint | -| 9 | Summary: <_ as core::ops::bit::BitAnd>::bitand; Argument[self,0]; ReturnValue; taint | -| 10 | Summary: <_ as core::ops::bit::BitXor>::bitxor; Argument[self,0]; ReturnValue; taint | -| 11 | Summary: <_ as core::ops::bit::Shl>::shl; Argument[self,0]; ReturnValue; taint | -| 12 | Summary: ::from; Argument[0].Field[alloc::borrow::Cow::Owned(0)]; ReturnValue; value | -| 13 | Summary: ::from; Argument[0].Field[alloc::bstr::ByteString(0)]; ReturnValue; value | -| 14 | Summary: ::from; Argument[0].Field[alloc::collections::binary_heap::BinaryHeap::data]; ReturnValue; value | -| 15 | Summary: ::from; Argument[0].Field[alloc::string::String::vec]; ReturnValue; value | -| 16 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 17 | Summary: ::from_slice; Argument[0].Reference; ReturnValue.Reference; value | -| 18 | Summary: alloc::vec::from_elem; Argument[0]; ReturnValue.Element; value | +| 8 | Summary: <_ as alloc::string::ToString>::to_string; Argument[self].Reference; ReturnValue; taint | +| 9 | Summary: <_ as core::ops::arith::Add>::add; Argument[self,0]; ReturnValue; taint | +| 10 | Summary: <_ as core::ops::arith::AddAssign>::add_assign; Argument[0]; Argument[self].Reference; taint | +| 11 | Summary: <_ as core::ops::arith::AddAssign>::add_assign; Argument[self].Reference; Argument[self].Reference; taint | +| 12 | Summary: <_ as core::ops::bit::BitAnd>::bitand; Argument[self,0]; ReturnValue; taint | +| 13 | Summary: <_ as core::ops::bit::BitXor>::bitxor; Argument[self,0]; ReturnValue; taint | +| 14 | Summary: <_ as core::ops::bit::Shl>::shl; Argument[self,0]; ReturnValue; taint | +| 15 | Summary: ::from; Argument[0].Field[alloc::borrow::Cow::Owned(0)]; ReturnValue; value | +| 16 | Summary: ::from; Argument[0].Field[alloc::bstr::ByteString(0)]; ReturnValue; value | +| 17 | Summary: ::from; Argument[0].Field[alloc::collections::binary_heap::BinaryHeap::data]; ReturnValue; value | +| 18 | Summary: ::from; Argument[0].Field[alloc::string::String::vec]; ReturnValue; value | +| 19 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 20 | Summary: ::from_slice; Argument[0].Reference; ReturnValue.Reference; value | +| 21 | Summary: alloc::vec::from_elem; Argument[0]; ReturnValue.Element; value | nodes | test_cipher.rs:18:9:18:14 | const1 [&ref] | semmle.label | const1 [&ref] | | test_cipher.rs:18:28:18:36 | &... [&ref] | semmle.label | &... [&ref] | @@ -183,4 +200,17 @@ nodes | test_heuristic.rs:70:34:70:35 | 32 | semmle.label | 32 | | test_heuristic.rs:70:41:70:61 | ... & ... | semmle.label | ... & ... | | test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | semmle.label | 0xFFFFFFFF | +| test_heuristic.rs:72:9:72:16 | mut key1 | semmle.label | mut key1 | +| test_heuristic.rs:72:20:72:24 | "foo" | semmle.label | "foo" | +| test_heuristic.rs:72:20:72:36 | "foo".to_string() | semmle.label | "foo".to_string() | +| test_heuristic.rs:73:5:73:8 | key1 | semmle.label | key1 | +| test_heuristic.rs:73:13:73:17 | "bar" | semmle.label | "bar" | +| test_heuristic.rs:74:28:74:32 | &key1 | semmle.label | &key1 | +| test_heuristic.rs:74:29:74:32 | key1 | semmle.label | key1 | +| test_heuristic.rs:76:9:76:16 | mut key2 | semmle.label | mut key2 | +| test_heuristic.rs:76:20:76:24 | "foo" | semmle.label | "foo" | +| test_heuristic.rs:76:20:76:36 | "foo".to_string() | semmle.label | "foo".to_string() | +| test_heuristic.rs:77:5:77:8 | key2 | semmle.label | key2 | +| test_heuristic.rs:78:28:78:32 | &key2 | semmle.label | &key2 | +| test_heuristic.rs:78:29:78:32 | key2 | semmle.label | key2 | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs index f8f16a16d12f..5e0c4606b0ef 100644 --- a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs +++ b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs @@ -68,4 +68,12 @@ fn test(var_string: &str, var_data: &[u8;16], var_u64: u64) { mc2.set_salt_u64(var_u64); mc2.set_salt_u64(var_u64 + 1); // $ SPURIOUS: Alert[rust/hard-coded-cryptographic-value] mc2.set_salt_u64((var_u64 << 32) ^ (var_u64 & 0xFFFFFFFF)); // $ SPURIOUS: Alert[rust/hard-coded-cryptographic-value] + + let mut key1 = "foo".to_string(); // $ Alert[rust/hard-coded-cryptographic-value] + key1 += "bar"; // $ Alert[rust/hard-coded-cryptographic-value] + let _ = MyCryptor::new(&key1); // $ Sink + + let mut key2 = "foo".to_string(); // $ SPURIOUS: Alert[rust/hard-coded-cryptographic-value] + key2 += var_string; + let _ = MyCryptor::new(&key2); // $ Sink } From 4bda03fe8d68438a8cfb1a7374b930de27739781 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:19:24 +0100 Subject: [PATCH 2/7] Rust: Make arithmetic operations a barrier for rust/hard-coded-cryptographic-value (including string concatenation). --- .../HardcodedCryptographicValueExtensions.qll | 20 +++++ .../HardcodedCryptographicValue.expected | 87 ++++--------------- .../security/CWE-798/test_heuristic.rs | 14 +-- 3 files changed, 46 insertions(+), 75 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll index 09e2505eb5c2..63ad8b4c0142 100644 --- a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll @@ -155,4 +155,24 @@ module HardcodedCryptographicValue { ) } } + + /** + * An arithmetic or bitwise operation that acts as a barrier. + * + * This prevents false positives where a hard-coded value is combined with + * non-constant data through operations like `+`, `^`, or `+=`. + */ + private class ArithmeticOperationBarrier extends Barrier { + ArithmeticOperationBarrier() { + // binary operations (e.g. `a + b`, `a ^ b`) + this.asExpr() instanceof BinaryArithmeticOperation + or + this.asExpr() instanceof BinaryBitwiseOperation + or + // compound assignments (e.g. `a += b`, `a ^= b`) + this.asExpr() = any(AssignArithmeticOperation a | | a.getAnOperand()) + or + this.asExpr() = any(AssignBitwiseOperation a | | a.getAnOperand()) + } + } } diff --git a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected index 3aaa5608e616..c6c6ce8a6628 100644 --- a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected +++ b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected @@ -15,43 +15,37 @@ | test_heuristic.rs:64:20:64:27 | [0u8; 16] | test_heuristic.rs:64:20:64:27 | [0u8; 16] | test_heuristic.rs:64:19:64:27 | &... | This hard-coded value is used as $@. | test_heuristic.rs:64:19:64:27 | &... | a nonce | | test_heuristic.rs:65:31:65:38 | [0u8; 16] | test_heuristic.rs:65:31:65:38 | [0u8; 16] | test_heuristic.rs:65:30:65:38 | &... | This hard-coded value is used as $@. | test_heuristic.rs:65:30:65:38 | &... | a salt | | test_heuristic.rs:67:22:67:22 | 0 | test_heuristic.rs:67:22:67:22 | 0 | test_heuristic.rs:67:22:67:22 | 0 | This hard-coded value is used as $@. | test_heuristic.rs:67:22:67:22 | 0 | a salt | -| test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:22:69:32 | ... + ... | This hard-coded value is used as $@. | test_heuristic.rs:69:22:69:32 | ... + ... | a salt | -| test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:22:70:62 | ... ^ ... | This hard-coded value is used as $@. | test_heuristic.rs:70:22:70:62 | ... ^ ... | a salt | -| test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:22:70:62 | ... ^ ... | This hard-coded value is used as $@. | test_heuristic.rs:70:22:70:62 | ... ^ ... | a salt | -| test_heuristic.rs:72:20:72:24 | "foo" | test_heuristic.rs:72:20:72:24 | "foo" | test_heuristic.rs:74:28:74:32 | &key1 | This hard-coded value is used as $@. | test_heuristic.rs:74:28:74:32 | &key1 | a password | -| test_heuristic.rs:73:13:73:17 | "bar" | test_heuristic.rs:73:13:73:17 | "bar" | test_heuristic.rs:74:28:74:32 | &key1 | This hard-coded value is used as $@. | test_heuristic.rs:74:28:74:32 | &key1 | a password | -| test_heuristic.rs:76:20:76:24 | "foo" | test_heuristic.rs:76:20:76:24 | "foo" | test_heuristic.rs:78:28:78:32 | &key2 | This hard-coded value is used as $@. | test_heuristic.rs:78:28:78:32 | &key2 | a password | edges | test_cipher.rs:18:9:18:14 | const1 [&ref] | test_cipher.rs:19:73:19:78 | const1 [&ref] | provenance | | | test_cipher.rs:18:28:18:36 | &... [&ref] | test_cipher.rs:18:9:18:14 | const1 [&ref] | provenance | | | test_cipher.rs:18:29:18:36 | [0u8; 16] | test_cipher.rs:18:28:18:36 | &... [&ref] | provenance | | | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref] | test_cipher.rs:19:30:19:47 | ...::new | provenance | MaD:3 Sink:MaD:3 | -| test_cipher.rs:19:73:19:78 | const1 [&ref] | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref] | provenance | MaD:20 | +| test_cipher.rs:19:73:19:78 | const1 [&ref] | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref] | provenance | MaD:13 | | test_cipher.rs:25:9:25:14 | const4 [&ref] | test_cipher.rs:26:66:26:71 | const4 [&ref] | provenance | | | test_cipher.rs:25:28:25:36 | &... [&ref] | test_cipher.rs:25:9:25:14 | const4 [&ref] | provenance | | | test_cipher.rs:25:29:25:36 | [0u8; 16] | test_cipher.rs:25:28:25:36 | &... [&ref] | provenance | | | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref] | test_cipher.rs:26:30:26:40 | ...::new | provenance | MaD:4 Sink:MaD:4 | -| test_cipher.rs:26:66:26:71 | const4 [&ref] | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref] | provenance | MaD:20 | +| test_cipher.rs:26:66:26:71 | const4 [&ref] | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref] | provenance | MaD:13 | | test_cipher.rs:29:9:29:14 | const5 [&ref] | test_cipher.rs:30:95:30:100 | const5 [&ref] | provenance | | | test_cipher.rs:29:28:29:36 | &... [&ref] | test_cipher.rs:29:9:29:14 | const5 [&ref] | provenance | | | test_cipher.rs:29:29:29:36 | [0u8; 16] | test_cipher.rs:29:28:29:36 | &... [&ref] | provenance | | | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref] | test_cipher.rs:30:30:30:40 | ...::new | provenance | MaD:5 Sink:MaD:5 | -| test_cipher.rs:30:95:30:100 | const5 [&ref] | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref] | provenance | MaD:20 | +| test_cipher.rs:30:95:30:100 | const5 [&ref] | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref] | provenance | MaD:13 | | test_cipher.rs:37:9:37:14 | const7 | test_cipher.rs:38:74:38:79 | const7 | provenance | | | test_cipher.rs:37:27:37:74 | [...] | test_cipher.rs:37:9:37:14 | const7 | provenance | | | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | test_cipher.rs:38:30:38:47 | ...::new | provenance | MaD:3 Sink:MaD:3 | -| test_cipher.rs:38:73:38:79 | &const7 [&ref] | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | provenance | MaD:20 | +| test_cipher.rs:38:73:38:79 | &const7 [&ref] | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | provenance | MaD:13 | | test_cipher.rs:38:74:38:79 | const7 | test_cipher.rs:38:73:38:79 | &const7 [&ref] | provenance | | | test_cipher.rs:41:9:41:14 | const8 [&ref] | test_cipher.rs:42:73:42:78 | const8 [&ref] | provenance | | | test_cipher.rs:41:28:41:76 | &... [&ref] | test_cipher.rs:41:9:41:14 | const8 [&ref] | provenance | | | test_cipher.rs:41:29:41:76 | [...] | test_cipher.rs:41:28:41:76 | &... [&ref] | provenance | | | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | test_cipher.rs:42:30:42:47 | ...::new | provenance | MaD:3 Sink:MaD:3 | -| test_cipher.rs:42:73:42:78 | const8 [&ref] | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | provenance | MaD:20 | +| test_cipher.rs:42:73:42:78 | const8 [&ref] | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | provenance | MaD:13 | | test_cipher.rs:50:9:50:15 | const10 [element] | test_cipher.rs:51:75:51:81 | const10 [element] | provenance | | | test_cipher.rs:50:37:50:52 | ...::zeroed | test_cipher.rs:50:37:50:54 | ...::zeroed(...) [element] | provenance | Src:MaD:7 | | test_cipher.rs:50:37:50:54 | ...::zeroed(...) [element] | test_cipher.rs:50:9:50:15 | const10 [element] | provenance | | | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | test_cipher.rs:51:31:51:48 | ...::new | provenance | MaD:3 Sink:MaD:3 Sink:MaD:3 | -| test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | provenance | MaD:20 | +| test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | provenance | MaD:13 | | test_cipher.rs:51:75:51:81 | const10 [element] | test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | provenance | | | test_cipher.rs:73:9:73:14 | const2 [&ref] | test_cipher.rs:74:46:74:51 | const2 [&ref] | provenance | | | test_cipher.rs:73:18:73:26 | &... [&ref] | test_cipher.rs:73:9:73:14 | const2 [&ref] | provenance | | @@ -67,14 +61,14 @@ edges | test_cookie.rs:22:27:22:32 | array2 | test_cookie.rs:22:26:22:32 | &array2 [&ref] | provenance | | | test_cookie.rs:38:9:38:14 | array2 | test_cookie.rs:42:34:42:39 | array2 | provenance | | | test_cookie.rs:38:18:38:37 | ...::from(...) | test_cookie.rs:38:9:38:14 | array2 | provenance | | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:15 | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:16 | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:17 | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:18 | -| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:19 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:8 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:9 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:10 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:11 | +| test_cookie.rs:38:28:38:36 | [0u8; 64] | test_cookie.rs:38:18:38:37 | ...::from(...) | provenance | MaD:12 | | test_cookie.rs:42:34:42:39 | array2 | test_cookie.rs:42:14:42:32 | ...::from | provenance | MaD:2 Sink:MaD:2 | | test_cookie.rs:49:9:49:14 | array3 [element] | test_cookie.rs:53:34:53:39 | array3 [element] | provenance | | -| test_cookie.rs:49:23:49:25 | 0u8 | test_cookie.rs:49:23:49:29 | ...::from_elem(...) [element] | provenance | MaD:21 | +| test_cookie.rs:49:23:49:25 | 0u8 | test_cookie.rs:49:23:49:29 | ...::from_elem(...) [element] | provenance | MaD:14 | | test_cookie.rs:49:23:49:29 | ...::from_elem(...) [element] | test_cookie.rs:49:9:49:14 | array3 [element] | provenance | | | test_cookie.rs:53:34:53:39 | array3 [element] | test_cookie.rs:53:14:53:32 | ...::from | provenance | MaD:2 Sink:MaD:2 | | test_heuristic.rs:44:9:44:16 | const_iv [&ref] | test_heuristic.rs:45:41:45:48 | const_iv | provenance | | @@ -82,22 +76,6 @@ edges | test_heuristic.rs:44:31:44:38 | [0u8; 16] | test_heuristic.rs:44:30:44:38 | &... [&ref] | provenance | | | test_heuristic.rs:64:20:64:27 | [0u8; 16] | test_heuristic.rs:64:19:64:27 | &... | provenance | | | test_heuristic.rs:65:31:65:38 | [0u8; 16] | test_heuristic.rs:65:30:65:38 | &... | provenance | | -| test_heuristic.rs:69:32:69:32 | 1 | test_heuristic.rs:69:22:69:32 | ... + ... | provenance | MaD:9 | -| test_heuristic.rs:70:23:70:35 | ... << ... | test_heuristic.rs:70:22:70:62 | ... ^ ... | provenance | MaD:13 | -| test_heuristic.rs:70:34:70:35 | 32 | test_heuristic.rs:70:23:70:35 | ... << ... | provenance | MaD:14 | -| test_heuristic.rs:70:41:70:61 | ... & ... | test_heuristic.rs:70:22:70:62 | ... ^ ... | provenance | MaD:13 | -| test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | test_heuristic.rs:70:41:70:61 | ... & ... | provenance | MaD:12 | -| test_heuristic.rs:72:9:72:16 | mut key1 | test_heuristic.rs:73:5:73:8 | key1 | provenance | | -| test_heuristic.rs:72:20:72:24 | "foo" | test_heuristic.rs:72:20:72:36 | "foo".to_string() | provenance | MaD:8 | -| test_heuristic.rs:72:20:72:36 | "foo".to_string() | test_heuristic.rs:72:9:72:16 | mut key1 | provenance | | -| test_heuristic.rs:73:5:73:8 | key1 | test_heuristic.rs:74:29:74:32 | key1 | provenance | MaD:11 | -| test_heuristic.rs:73:13:73:17 | "bar" | test_heuristic.rs:74:29:74:32 | key1 | provenance | MaD:10 | -| test_heuristic.rs:74:29:74:32 | key1 | test_heuristic.rs:74:28:74:32 | &key1 | provenance | | -| test_heuristic.rs:76:9:76:16 | mut key2 | test_heuristic.rs:77:5:77:8 | key2 | provenance | | -| test_heuristic.rs:76:20:76:24 | "foo" | test_heuristic.rs:76:20:76:36 | "foo".to_string() | provenance | MaD:8 | -| test_heuristic.rs:76:20:76:36 | "foo".to_string() | test_heuristic.rs:76:9:76:16 | mut key2 | provenance | | -| test_heuristic.rs:77:5:77:8 | key2 | test_heuristic.rs:78:29:78:32 | key2 | provenance | MaD:11 | -| test_heuristic.rs:78:29:78:32 | key2 | test_heuristic.rs:78:28:78:32 | &key2 | provenance | | models | 1 | Sink: <_ as crypto_common::KeyInit>::new_from_slice; Argument[0]; credentials-key | | 2 | Sink: ::from; Argument[0]; credentials-key | @@ -106,20 +84,13 @@ models | 5 | Sink: ::new; Argument[1]; credentials-iv | | 6 | Sink: ::from; Argument[0].Reference; credentials-key | | 7 | Source: core::mem::zeroed; ReturnValue.Element; constant-source | -| 8 | Summary: <_ as alloc::string::ToString>::to_string; Argument[self].Reference; ReturnValue; taint | -| 9 | Summary: <_ as core::ops::arith::Add>::add; Argument[self,0]; ReturnValue; taint | -| 10 | Summary: <_ as core::ops::arith::AddAssign>::add_assign; Argument[0]; Argument[self].Reference; taint | -| 11 | Summary: <_ as core::ops::arith::AddAssign>::add_assign; Argument[self].Reference; Argument[self].Reference; taint | -| 12 | Summary: <_ as core::ops::bit::BitAnd>::bitand; Argument[self,0]; ReturnValue; taint | -| 13 | Summary: <_ as core::ops::bit::BitXor>::bitxor; Argument[self,0]; ReturnValue; taint | -| 14 | Summary: <_ as core::ops::bit::Shl>::shl; Argument[self,0]; ReturnValue; taint | -| 15 | Summary: ::from; Argument[0].Field[alloc::borrow::Cow::Owned(0)]; ReturnValue; value | -| 16 | Summary: ::from; Argument[0].Field[alloc::bstr::ByteString(0)]; ReturnValue; value | -| 17 | Summary: ::from; Argument[0].Field[alloc::collections::binary_heap::BinaryHeap::data]; ReturnValue; value | -| 18 | Summary: ::from; Argument[0].Field[alloc::string::String::vec]; ReturnValue; value | -| 19 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 20 | Summary: ::from_slice; Argument[0].Reference; ReturnValue.Reference; value | -| 21 | Summary: alloc::vec::from_elem; Argument[0]; ReturnValue.Element; value | +| 8 | Summary: ::from; Argument[0].Field[alloc::borrow::Cow::Owned(0)]; ReturnValue; value | +| 9 | Summary: ::from; Argument[0].Field[alloc::bstr::ByteString(0)]; ReturnValue; value | +| 10 | Summary: ::from; Argument[0].Field[alloc::collections::binary_heap::BinaryHeap::data]; ReturnValue; value | +| 11 | Summary: ::from; Argument[0].Field[alloc::string::String::vec]; ReturnValue; value | +| 12 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 13 | Summary: ::from_slice; Argument[0].Reference; ReturnValue.Reference; value | +| 14 | Summary: alloc::vec::from_elem; Argument[0]; ReturnValue.Element; value | nodes | test_cipher.rs:18:9:18:14 | const1 [&ref] | semmle.label | const1 [&ref] | | test_cipher.rs:18:28:18:36 | &... [&ref] | semmle.label | &... [&ref] | @@ -193,24 +164,4 @@ nodes | test_heuristic.rs:65:30:65:38 | &... | semmle.label | &... | | test_heuristic.rs:65:31:65:38 | [0u8; 16] | semmle.label | [0u8; 16] | | test_heuristic.rs:67:22:67:22 | 0 | semmle.label | 0 | -| test_heuristic.rs:69:22:69:32 | ... + ... | semmle.label | ... + ... | -| test_heuristic.rs:69:32:69:32 | 1 | semmle.label | 1 | -| test_heuristic.rs:70:22:70:62 | ... ^ ... | semmle.label | ... ^ ... | -| test_heuristic.rs:70:23:70:35 | ... << ... | semmle.label | ... << ... | -| test_heuristic.rs:70:34:70:35 | 32 | semmle.label | 32 | -| test_heuristic.rs:70:41:70:61 | ... & ... | semmle.label | ... & ... | -| test_heuristic.rs:70:52:70:61 | 0xFFFFFFFF | semmle.label | 0xFFFFFFFF | -| test_heuristic.rs:72:9:72:16 | mut key1 | semmle.label | mut key1 | -| test_heuristic.rs:72:20:72:24 | "foo" | semmle.label | "foo" | -| test_heuristic.rs:72:20:72:36 | "foo".to_string() | semmle.label | "foo".to_string() | -| test_heuristic.rs:73:5:73:8 | key1 | semmle.label | key1 | -| test_heuristic.rs:73:13:73:17 | "bar" | semmle.label | "bar" | -| test_heuristic.rs:74:28:74:32 | &key1 | semmle.label | &key1 | -| test_heuristic.rs:74:29:74:32 | key1 | semmle.label | key1 | -| test_heuristic.rs:76:9:76:16 | mut key2 | semmle.label | mut key2 | -| test_heuristic.rs:76:20:76:24 | "foo" | semmle.label | "foo" | -| test_heuristic.rs:76:20:76:36 | "foo".to_string() | semmle.label | "foo".to_string() | -| test_heuristic.rs:77:5:77:8 | key2 | semmle.label | key2 | -| test_heuristic.rs:78:28:78:32 | &key2 | semmle.label | &key2 | -| test_heuristic.rs:78:29:78:32 | key2 | semmle.label | key2 | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs index 5e0c4606b0ef..6f46f581f1fc 100644 --- a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs +++ b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs @@ -66,14 +66,14 @@ fn test(var_string: &str, var_data: &[u8;16], var_u64: u64) { mc2.set_salt_u64(0); // $ Alert[rust/hard-coded-cryptographic-value] mc2.set_salt_u64(var_u64); - mc2.set_salt_u64(var_u64 + 1); // $ SPURIOUS: Alert[rust/hard-coded-cryptographic-value] - mc2.set_salt_u64((var_u64 << 32) ^ (var_u64 & 0xFFFFFFFF)); // $ SPURIOUS: Alert[rust/hard-coded-cryptographic-value] + mc2.set_salt_u64(var_u64 + 1); + mc2.set_salt_u64((var_u64 << 32) ^ (var_u64 & 0xFFFFFFFF)); - let mut key1 = "foo".to_string(); // $ Alert[rust/hard-coded-cryptographic-value] - key1 += "bar"; // $ Alert[rust/hard-coded-cryptographic-value] - let _ = MyCryptor::new(&key1); // $ Sink + let mut key1 = "foo".to_string(); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + key1 += "bar"; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let _ = MyCryptor::new(&key1); - let mut key2 = "foo".to_string(); // $ SPURIOUS: Alert[rust/hard-coded-cryptographic-value] + let mut key2 = "foo".to_string(); key2 += var_string; - let _ = MyCryptor::new(&key2); // $ Sink + let _ = MyCryptor::new(&key2); } From 351d4954d73b8f3de39f8a63148617cc137c4579 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 25 Jun 2026 12:17:38 +0100 Subject: [PATCH 3/7] Rust: Change note. --- ...06-25-hard-coded-cryptographic-value-arithmetic-barrier.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/src/change-notes/2026-06-25-hard-coded-cryptographic-value-arithmetic-barrier.md diff --git a/rust/ql/src/change-notes/2026-06-25-hard-coded-cryptographic-value-arithmetic-barrier.md b/rust/ql/src/change-notes/2026-06-25-hard-coded-cryptographic-value-arithmetic-barrier.md new file mode 100644 index 000000000000..bee0af58314c --- /dev/null +++ b/rust/ql/src/change-notes/2026-06-25-hard-coded-cryptographic-value-arithmetic-barrier.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `rust/hard-coded-cryptographic-value` query now treats arithmetic and bitwise operations, including string append operations, as barriers. This addresses false positive results where hard-coded constants are combined with non-constant data, such as incrementing a nonce or appending variable data to a constant prefix. From 4aa53b6be9e44f7409891d5928d467d267298ba0 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 25 Jun 2026 15:16:38 +0100 Subject: [PATCH 4/7] Update rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll Co-authored-by: Tom Hvitved --- .../rust/security/HardcodedCryptographicValueExtensions.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll index 63ad8b4c0142..4ba846d2a8ba 100644 --- a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll @@ -170,9 +170,9 @@ module HardcodedCryptographicValue { this.asExpr() instanceof BinaryBitwiseOperation or // compound assignments (e.g. `a += b`, `a ^= b`) - this.asExpr() = any(AssignArithmeticOperation a | | a.getAnOperand()) + this.asExpr() = any(AssignArithmeticOperation a).getAnOperand() or - this.asExpr() = any(AssignBitwiseOperation a | | a.getAnOperand()) + this.asExpr() = any(AssignBitwiseOperation a).getAnOperand() } } } From 3403cffe518d460ebd3acfd3487982050b147cbb Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 25 Jun 2026 15:21:51 +0100 Subject: [PATCH 5/7] Rust: More-or-less the Copilot suggestion. --- .../rust/security/HardcodedCryptographicValueExtensions.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll index 4ba846d2a8ba..1ee03e2d4b56 100644 --- a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll @@ -160,7 +160,7 @@ module HardcodedCryptographicValue { * An arithmetic or bitwise operation that acts as a barrier. * * This prevents false positives where a hard-coded value is combined with - * non-constant data through operations like `+`, `^`, or `+=`. + * non-constant data through operations like `+`, `^`, or `+=` (including string concatenation). */ private class ArithmeticOperationBarrier extends Barrier { ArithmeticOperationBarrier() { From 8155ff7a4fb887532e6eba8e86754c8504446701 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:10:29 +0100 Subject: [PATCH 6/7] Rust: Add a few more test cases for constants / constant propagation. --- .../ql/test/query-tests/security/CWE-798/test_heuristic.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs index 6f46f581f1fc..9f5d458b4167 100644 --- a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs +++ b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs @@ -68,6 +68,9 @@ fn test(var_string: &str, var_data: &[u8;16], var_u64: u64) { mc2.set_salt_u64(var_u64); mc2.set_salt_u64(var_u64 + 1); mc2.set_salt_u64((var_u64 << 32) ^ (var_u64 & 0xFFFFFFFF)); + mc2.set_salt_u64(1 << 4); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + mc2.set_salt_u64(u64::MAX); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + mc2.set_salt_u64(u64::MAX / 4); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] let mut key1 = "foo".to_string(); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] key1 += "bar"; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] @@ -76,4 +79,8 @@ fn test(var_string: &str, var_data: &[u8;16], var_u64: u64) { let mut key2 = "foo".to_string(); key2 += var_string; let _ = MyCryptor::new(&key2); + + let mut key3 = var_string.to_string(); + key3 += "bar"; + let _ = MyCryptor::new(&key3); } From 95e030f4e352f746a5d7f90975c960d234de9d8a Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 26 Jun 2026 09:41:40 +0100 Subject: [PATCH 7/7] Rust: Detect constant accesses and perform very limited constant propagation. --- .../HardcodedCryptographicValueExtensions.qll | 33 ++++++++++--------- .../HardcodedCryptographicValue.expected | 6 ++++ .../security/CWE-798/test_heuristic.rs | 6 ++-- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll index 1ee03e2d4b56..e2cbba1ee79e 100644 --- a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll @@ -62,24 +62,27 @@ module HardcodedCryptographicValue { abstract class Barrier extends DataFlow::Node { } /** - * A literal, considered as a flow source. + * Holds if `e` is a literal or a combination of literals that is constant. */ - private class LiteralSource extends Source { - LiteralSource() { this.asExpr() instanceof LiteralExpr } + private predicate isConstant(Expr e) { + e instanceof LiteralExpr // e.g. `0` + or + e.(ArrayListExpr).getExpr(_) instanceof LiteralExpr // e.g. `[0, 0, 0, 0]` + or + e.(ArrayRepeatExpr).getRepeatOperand() instanceof LiteralExpr // e.g. `[0; 10]` + or + e instanceof ConstAccess // e.g. `u64::MAX` + or + // e.g. `1 << 4` + isConstant(e.(BinaryExpr).getLhs()) and + isConstant(e.(BinaryExpr).getRhs()) } /** - * An array initialized from a list of literals, considered as a single flow source. For example: - * ``` - * [0, 0, 0, 0] - * [0; 10] - * ``` + * A constant, considered as a flow source. */ - private class ArrayListSource extends Source { - ArrayListSource() { - this.asExpr().(ArrayListExpr).getExpr(_) instanceof LiteralExpr or - this.asExpr().(ArrayRepeatExpr).getRepeatOperand() instanceof LiteralExpr - } + private class ConstantSource extends Source { + ConstantSource() { isConstant(this.asExpr()) } } /** @@ -165,9 +168,9 @@ module HardcodedCryptographicValue { private class ArithmeticOperationBarrier extends Barrier { ArithmeticOperationBarrier() { // binary operations (e.g. `a + b`, `a ^ b`) - this.asExpr() instanceof BinaryArithmeticOperation + this.asExpr() = any(BinaryArithmeticOperation a).getAnOperand() or - this.asExpr() instanceof BinaryBitwiseOperation + this.asExpr() = any(BinaryBitwiseOperation a).getAnOperand() or // compound assignments (e.g. `a += b`, `a ^= b`) this.asExpr() = any(AssignArithmeticOperation a).getAnOperand() diff --git a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected index c6c6ce8a6628..e70bbe23763e 100644 --- a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected +++ b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected @@ -15,6 +15,9 @@ | test_heuristic.rs:64:20:64:27 | [0u8; 16] | test_heuristic.rs:64:20:64:27 | [0u8; 16] | test_heuristic.rs:64:19:64:27 | &... | This hard-coded value is used as $@. | test_heuristic.rs:64:19:64:27 | &... | a nonce | | test_heuristic.rs:65:31:65:38 | [0u8; 16] | test_heuristic.rs:65:31:65:38 | [0u8; 16] | test_heuristic.rs:65:30:65:38 | &... | This hard-coded value is used as $@. | test_heuristic.rs:65:30:65:38 | &... | a salt | | test_heuristic.rs:67:22:67:22 | 0 | test_heuristic.rs:67:22:67:22 | 0 | test_heuristic.rs:67:22:67:22 | 0 | This hard-coded value is used as $@. | test_heuristic.rs:67:22:67:22 | 0 | a salt | +| test_heuristic.rs:71:22:71:27 | ... << ... | test_heuristic.rs:71:22:71:27 | ... << ... | test_heuristic.rs:71:22:71:27 | ... << ... | This hard-coded value is used as $@. | test_heuristic.rs:71:22:71:27 | ... << ... | a salt | +| test_heuristic.rs:72:22:72:29 | ...::MAX | test_heuristic.rs:72:22:72:29 | ...::MAX | test_heuristic.rs:72:22:72:29 | ...::MAX | This hard-coded value is used as $@. | test_heuristic.rs:72:22:72:29 | ...::MAX | a salt | +| test_heuristic.rs:73:22:73:33 | ... / ... | test_heuristic.rs:73:22:73:33 | ... / ... | test_heuristic.rs:73:22:73:33 | ... / ... | This hard-coded value is used as $@. | test_heuristic.rs:73:22:73:33 | ... / ... | a salt | edges | test_cipher.rs:18:9:18:14 | const1 [&ref] | test_cipher.rs:19:73:19:78 | const1 [&ref] | provenance | | | test_cipher.rs:18:28:18:36 | &... [&ref] | test_cipher.rs:18:9:18:14 | const1 [&ref] | provenance | | @@ -164,4 +167,7 @@ nodes | test_heuristic.rs:65:30:65:38 | &... | semmle.label | &... | | test_heuristic.rs:65:31:65:38 | [0u8; 16] | semmle.label | [0u8; 16] | | test_heuristic.rs:67:22:67:22 | 0 | semmle.label | 0 | +| test_heuristic.rs:71:22:71:27 | ... << ... | semmle.label | ... << ... | +| test_heuristic.rs:72:22:72:29 | ...::MAX | semmle.label | ...::MAX | +| test_heuristic.rs:73:22:73:33 | ... / ... | semmle.label | ... / ... | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs index 9f5d458b4167..bfc07e80f16d 100644 --- a/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs +++ b/rust/ql/test/query-tests/security/CWE-798/test_heuristic.rs @@ -68,9 +68,9 @@ fn test(var_string: &str, var_data: &[u8;16], var_u64: u64) { mc2.set_salt_u64(var_u64); mc2.set_salt_u64(var_u64 + 1); mc2.set_salt_u64((var_u64 << 32) ^ (var_u64 & 0xFFFFFFFF)); - mc2.set_salt_u64(1 << 4); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] - mc2.set_salt_u64(u64::MAX); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] - mc2.set_salt_u64(u64::MAX / 4); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + mc2.set_salt_u64(1 << 4); // $ Alert[rust/hard-coded-cryptographic-value] + mc2.set_salt_u64(u64::MAX); // $ Alert[rust/hard-coded-cryptographic-value] + mc2.set_salt_u64(u64::MAX / 4); // $ Alert[rust/hard-coded-cryptographic-value] let mut key1 = "foo".to_string(); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] key1 += "bar"; // $ MISSING: Alert[rust/hard-coded-cryptographic-value]