diff --git a/rust/ql/integration-tests/query-suite/rust-code-scanning.qls.expected b/rust/ql/integration-tests/query-suite/rust-code-scanning.qls.expected index b3683f02d927..f2dc65b84006 100644 --- a/rust/ql/integration-tests/query-suite/rust-code-scanning.qls.expected +++ b/rust/ql/integration-tests/query-suite/rust-code-scanning.qls.expected @@ -15,6 +15,7 @@ ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql +ql/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.ql ql/rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql ql/rust/ql/src/queries/summary/LinesOfCode.ql ql/rust/ql/src/queries/summary/LinesOfUserCode.ql diff --git a/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected b/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected index 650bf3169412..14f1f44127cd 100644 --- a/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected +++ b/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected @@ -16,6 +16,7 @@ ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql ql/rust/ql/src/queries/security/CWE-696/BadCtorInitialization.ql ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql +ql/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.ql ql/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql ql/rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql ql/rust/ql/src/queries/summary/LinesOfCode.ql diff --git a/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected b/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected index b5df88f96eca..50ed9b311eb7 100644 --- a/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected +++ b/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected @@ -15,6 +15,7 @@ ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql +ql/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.ql ql/rust/ql/src/queries/security/CWE-825/AccessAfterLifetime.ql ql/rust/ql/src/queries/security/CWE-825/AccessInvalidPointer.ql ql/rust/ql/src/queries/summary/LinesOfCode.ql diff --git a/rust/ql/lib/codeql/rust/frameworks/genericarray.model.yml b/rust/ql/lib/codeql/rust/frameworks/genericarray.model.yml new file mode 100644 index 000000000000..76e2569a67aa --- /dev/null +++ b/rust/ql/lib/codeql/rust/frameworks/genericarray.model.yml @@ -0,0 +1,9 @@ +extensions: + - addsTo: + pack: codeql/rust-all + extensible: summaryModel + data: + - ["::from_slice", "Argument[0].Reference", "ReturnValue.Reference", "value", "manual"] + - ["::from_mut_slice", "Argument[0].Reference", "ReturnValue.Reference", "value", "manual"] + - ["::try_from_slice", "Argument[0].Reference", "ReturnValue.Field[crate::result::Result::Ok(0)].Reference", "value", "manual"] + - ["::try_from_mut_slice", "Argument[0].Reference", "ReturnValue.Field[crate::result::Result::Ok(0)].Reference", "value", "manual"] diff --git a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml index 84f3247d782e..7ceae820001c 100644 --- a/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/rustcrypto/rustcrypto.model.yml @@ -8,3 +8,28 @@ extensions: - ["<_ as digest::digest::Digest>::chain_update", "Argument[0]", "hasher-input", "manual"] - ["<_ as digest::digest::Digest>::digest", "Argument[0]", "hasher-input", "manual"] - ["md5::compute", "Argument[0]", "hasher-input", "manual"] + - ["<_ as crypto_common::KeyInit>::new", "Argument[0]", "credentials-key", "manual"] + - ["<_ as crypto_common::KeyInit>::new", "Argument[1]", "credentials-iv", "manual"] + - ["<_ as crypto_common::KeyInit>::new_from_slice", "Argument[0]", "credentials-key", "manual"] + - ["<_ as crypto_common::KeyInit>::new_from_slice", "Argument[1]", "credentials-iv", "manual"] + - ["<_ as crypto_common::KeyIvInit>::new", "Argument[0]", "credentials-key", "manual"] + - ["<_ as crypto_common::KeyIvInit>::new", "Argument[1]", "credentials-iv", "manual"] + - ["<_ as crypto_common::KeyIvInit>::new_from_slice", "Argument[0]", "credentials-key", "manual"] + - ["<_ as crypto_common::KeyIvInit>::new_from_slice", "Argument[1]", "credentials-iv", "manual"] + - ["::new", "Argument[0]", "credentials-key", "manual"] + - ["::new", "Argument[1]", "credentials-iv", "manual"] + - ["::new_from_slice", "Argument[0]", "credentials-key", "manual"] + - ["::new_from_slice", "Argument[1]", "credentials-iv", "manual"] + - ["::new", "Argument[0]", "credentials-key", "manual"] + - ["::new", "Argument[1]", "credentials-iv", "manual"] + - ["::new_from_slice", "Argument[0]", "credentials-key", "manual"] + - ["::new_from_slice", "Argument[1]", "credentials-iv", "manual"] + - ["::new", "Argument[0]", "credentials-key", "manual"] + - ["::new", "Argument[1]", "credentials-iv", "manual"] + - ["::new_from_slice", "Argument[0]", "credentials-key", "manual"] + - ["::new_from_slice", "Argument[1]", "credentials-iv", "manual"] + - ["::new", "Argument[0]", "credentials-key", "manual"] + - ["::new", "Argument[1]", "credentials-iv", "manual"] + - ["::new_from_slice", "Argument[0]", "credentials-key", "manual"] + - ["::new_from_slice", "Argument[1]", "credentials-iv", "manual"] + - ["<_ as aead::Aead>::encrypt", "Argument[0]", "credentials-nonce", "manual"] diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml b/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml index 44319a942bf5..8b9adcdf521e 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml @@ -3,6 +3,12 @@ extensions: pack: codeql/rust-all extensible: summaryModel data: + # Conversions + - ["::align_to", "Argument[self].Element", "ReturnValue.Field[0,1,2].Reference.Element", "taint", "manual"] + - ["<_ as core::convert::Into>::into", "Argument[self].Element", "ReturnValue.Element", "taint", "manual"] + - ["<_ as core::convert::Into>::into", "Argument[self].Reference.Element", "ReturnValue.Element", "taint", "manual"] + - ["::into", "Argument[self].Element", "ReturnValue.Element", "taint", "manual"] + - ["::into", "Argument[self].Reference.Element", "ReturnValue.Element", "taint", "manual"] # Iterator - ["::iter", "Argument[self].Element", "ReturnValue.Element", "value", "manual"] - ["::iter", "Argument[self].Element", "ReturnValue.Element", "value", "manual"] @@ -59,6 +65,8 @@ extensions: pack: codeql/rust-all extensible: sourceModel data: + # Mem + - ["core::mem::zeroed", "ReturnValue.Element", "constant-source", "manual"] # Ptr - ["core::ptr::drop_in_place", "Argument[0]", "pointer-invalidate", "manual"] - ["core::ptr::dangling", "ReturnValue", "pointer-invalidate", "manual"] diff --git a/rust/ql/lib/codeql/rust/security/CleartextLoggingExtensions.qll b/rust/ql/lib/codeql/rust/security/CleartextLoggingExtensions.qll index 559509ad9f8e..044236bc03c1 100644 --- a/rust/ql/lib/codeql/rust/security/CleartextLoggingExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/CleartextLoggingExtensions.qll @@ -5,7 +5,7 @@ import rust private import codeql.rust.dataflow.DataFlow -private import codeql.rust.dataflow.internal.DataFlowImpl +private import codeql.rust.dataflow.FlowSink private import codeql.rust.security.SensitiveData private import codeql.rust.Concepts diff --git a/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll new file mode 100644 index 000000000000..785a7f815bcd --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/HardcodedCryptographicValueExtensions.qll @@ -0,0 +1,109 @@ +/** + * Provides classes and predicates for reasoning about hard-coded cryptographic value + * vulnerabilities. + */ + +import rust +private import codeql.rust.dataflow.DataFlow +private import codeql.rust.dataflow.FlowSource +private import codeql.rust.dataflow.FlowSink +private import codeql.rust.Concepts +private import codeql.rust.security.SensitiveData + +/** + * A kind of cryptographic value. + */ +class CryptographicValueKind extends string { + CryptographicValueKind() { this = ["password", "key", "iv", "nonce", "salt"] } + + /** + * Gets a description of this value kind for user-facing messages. + */ + string getDescription() { + this = "password" and result = "a password" + or + this = "key" and result = "a key" + or + this = "iv" and result = "an initialization vector" + or + this = "nonce" and result = "a nonce" + or + this = "salt" and result = "a salt" + } +} + +/** + * Provides default sources, sinks and barriers for detecting hard-coded cryptographic + * value vulnerabilities, as well as extension points for adding your own. + */ +module HardcodedCryptographicValue { + /** + * A data flow source for hard-coded cryptographic value vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for hard-coded cryptographic value vulnerabilities. + */ + abstract class Sink extends QuerySink::Range { + override string getSinkType() { result = "HardcodedCryptographicValue" } + + /** + * Gets the kind of credential this sink is interpreted as. + */ + abstract CryptographicValueKind getKind(); + } + + /** + * A barrier for hard-coded cryptographic value vulnerabilities. + */ + abstract class Barrier extends DataFlow::Node { } + + /** + * A literal, considered as a flow source. + */ + private class LiteralSource extends Source { + LiteralSource() { this.asExpr().getExpr() instanceof LiteralExpr } + } + + /** + * An array initialized from a list of literals, considered as a single flow source. For example: + * ``` + * `[0, 0, 0, 0]` + * ``` + */ + private class ArrayListSource extends Source { + ArrayListSource() { this.asExpr().getExpr().(ArrayListExpr).getExpr(_) instanceof LiteralExpr } + } + + /** + * An externally modeled source for constant values. + */ + private class ModeledSource extends Source { + ModeledSource() { sourceNode(this, "constant-source") } + } + + /** + * An externally modeled sink for hard-coded cryptographic value vulnerabilities. + */ + private class ModelsAsDataSinks extends Sink { + CryptographicValueKind kind; + + ModelsAsDataSinks() { sinkNode(this, "credentials-" + kind) } + + override CryptographicValueKind getKind() { result = kind } + } + + /** + * A call to `getrandom` that is a barrier. + */ + private class GetRandomBarrier extends Barrier { + GetRandomBarrier() { + exists(CallExprBase ce | + ce.getStaticTarget().(Addressable).getCanonicalPath() = + ["getrandom::fill", "getrandom::getrandom"] and + this.asExpr().getExpr().getParentNode*() = ce.getArgList().getArg(0) + ) + } + } +} diff --git a/rust/ql/lib/codeql/rust/security/SqlInjectionExtensions.qll b/rust/ql/lib/codeql/rust/security/SqlInjectionExtensions.qll index 8ead5ac684ac..f2921ef0cc13 100644 --- a/rust/ql/lib/codeql/rust/security/SqlInjectionExtensions.qll +++ b/rust/ql/lib/codeql/rust/security/SqlInjectionExtensions.qll @@ -6,7 +6,7 @@ import rust private import codeql.rust.dataflow.DataFlow -private import codeql.rust.dataflow.internal.DataFlowImpl +private import codeql.rust.dataflow.FlowSink private import codeql.rust.Concepts private import codeql.util.Unit diff --git a/rust/ql/src/change-notes/2025-06-24-hardcoded-cryptographic-value.md b/rust/ql/src/change-notes/2025-06-24-hardcoded-cryptographic-value.md new file mode 100644 index 000000000000..73bd81f03408 --- /dev/null +++ b/rust/ql/src/change-notes/2025-06-24-hardcoded-cryptographic-value.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* Added a new query, `rust/hardcoded-crytographic-value`, for detecting use of hardcoded keys, passwords, salts and initialization vectors. diff --git a/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.qhelp b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.qhelp new file mode 100644 index 000000000000..3a6813cdef09 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.qhelp @@ -0,0 +1,58 @@ + + + + +

+Hard-coded passwords, keys, initialization vectors, and salts should not be used for cryptographic operations. +

+
    +
  • + Attackers can easily recover hard-coded values if they have access to the source code or compiled executable. +
  • +
  • + Some hard-coded values are easily guessable. +
  • +
  • + Use of hard-coded values may leave cryptographic operations vulnerable to dictionary attacks, rainbow tables, and other forms of cryptanalysis. +
  • +
+ +
+ + +

+Use randomly generated key material, initialization vectors, and salts. Use strong passwords that are not hard-coded. +

+ +
+ + +

+The following example shows instantiating a cipher with hard-coded key material, making the encrypted data vulnerable to recovery. +

+ + + +

+In the fixed code below, the key material is randomly generated and not hard-coded, which protects the encrypted data against recovery. A real application would also need a strategy for secure key management after the key has been generated. +

+ + + +
+ + +
  • +OWASP: Use of hard-coded password. +
  • +
  • +OWASP: Key Management Cheat Sheet. +
  • +
  • +O'Reilly: Using Salts, Nonces, and Initialization Vectors. +
  • + +
    +
    diff --git a/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.ql b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.ql new file mode 100644 index 000000000000..cd0dca79119b --- /dev/null +++ b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValue.ql @@ -0,0 +1,58 @@ +/** + * @name Hard-coded cryptographic value + * @description Using hard-coded keys, passwords, salts or initialization + * vectors is not secure. + * @kind path-problem + * @problem.severity warning + * @security-severity 9.8 + * @precision high + * @id rust/hard-coded-cryptographic-value + * @tags security + * external/cwe/cwe-259 + * external/cwe/cwe-321 + * external/cwe/cwe-798 + * external/cwe/cwe-1204 + */ + +import rust +import codeql.rust.security.HardcodedCryptographicValueExtensions +import codeql.rust.dataflow.DataFlow +import codeql.rust.dataflow.TaintTracking +import codeql.rust.dataflow.internal.DataFlowImpl +import codeql.rust.dataflow.internal.Content + +/** + * A taint-tracking configuration for hard-coded cryptographic value vulnerabilities. + */ +module HardcodedCryptographicValueConfig implements DataFlow::ConfigSig { + import HardcodedCryptographicValue + + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier } + + predicate isBarrierIn(DataFlow::Node node) { + // make sources barriers so that we only report the closest instance + // (this combined with sources for `ArrayListExpr` means we only get one source in + // case like `[0, 0, 0, 0]`) + isSource(node) + } + + predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { + // flow out from reference content at sinks. + isSink(node) and + c.getAReadContent() instanceof ReferenceContent + } +} + +module HardcodedCryptographicValueFlow = TaintTracking::Global; + +import HardcodedCryptographicValueFlow::PathGraph + +from + HardcodedCryptographicValueFlow::PathNode source, HardcodedCryptographicValueFlow::PathNode sink +where HardcodedCryptographicValueFlow::flowPath(source, sink) +select source.getNode(), source, sink, "This hard-coded value is used as $@.", sink, + sink.getNode().(HardcodedCryptographicValueConfig::Sink).getKind().getDescription() diff --git a/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValueBad.rs b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValueBad.rs new file mode 100644 index 000000000000..11dacfc08c42 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValueBad.rs @@ -0,0 +1,2 @@ +let key: [u8;32] = [0;32]; // BAD: Using hard-coded keys for encryption +let cipher = Aes256Gcm::new(&key.into()); diff --git a/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValueGood.rs b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValueGood.rs new file mode 100644 index 000000000000..06dc1af836d5 --- /dev/null +++ b/rust/ql/src/queries/security/CWE-798/HardcodedCryptographicValueGood.rs @@ -0,0 +1,2 @@ +let key = Aes256Gcm::generate_key(aes_gcm::aead::OsRng); // GOOD: Using randomly generated keys for encryption +let cipher = Aes256Gcm::new(&key); diff --git a/rust/ql/src/queries/summary/Stats.qll b/rust/ql/src/queries/summary/Stats.qll index 3156f1ffb26e..ba528a794338 100644 --- a/rust/ql/src/queries/summary/Stats.qll +++ b/rust/ql/src/queries/summary/Stats.qll @@ -25,6 +25,7 @@ private import codeql.rust.security.SqlInjectionExtensions private import codeql.rust.security.TaintedPathExtensions private import codeql.rust.security.UncontrolledAllocationSizeExtensions private import codeql.rust.security.WeakSensitiveDataHashingExtensions +private import codeql.rust.security.HardcodedCryptographicValueExtensions /** * Gets a count of the total number of lines of code in the database. diff --git a/rust/ql/test/query-tests/security/CWE-798/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/query-tests/security/CWE-798/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 000000000000..b0ca63000651 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-798/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,10 @@ +multipleCallTargets +| test_cipher.rs:15:30:15:77 | ...::new(...) | +| test_cipher.rs:19:30:19:80 | ...::new(...) | +| test_cipher.rs:22:30:22:98 | ...::new(...) | +| test_cipher.rs:26:30:26:101 | ...::new(...) | +| test_cipher.rs:30:30:30:102 | ...::new(...) | +| test_cipher.rs:38:30:38:81 | ...::new(...) | +| test_cipher.rs:42:30:42:80 | ...::new(...) | +| test_cipher.rs:47:30:47:85 | ...::new(...) | +| test_cipher.rs:51:31:51:83 | ...::new(...) | diff --git a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected new file mode 100644 index 000000000000..2155fef0d407 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.expected @@ -0,0 +1,122 @@ +#select +| test_cipher.rs:18:30:18:32 | 0u8 | test_cipher.rs:18:30:18:32 | 0u8 | test_cipher.rs:19:30:19:47 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:19:30:19:47 | ...::new | a key | +| test_cipher.rs:18:30:18:32 | 0u8 | test_cipher.rs:18:30:18:32 | 0u8 | test_cipher.rs:19:30:19:47 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:19:30:19:47 | ...::new | a key | +| test_cipher.rs:25:30:25:32 | 0u8 | test_cipher.rs:25:30:25:32 | 0u8 | test_cipher.rs:26:30:26:40 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:26:30:26:40 | ...::new | a key | +| test_cipher.rs:25:30:25:32 | 0u8 | test_cipher.rs:25:30:25:32 | 0u8 | test_cipher.rs:26:30:26:40 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:26:30:26:40 | ...::new | a key | +| test_cipher.rs:29:30:29:32 | 0u8 | test_cipher.rs:29:30:29:32 | 0u8 | test_cipher.rs:30:30:30:40 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:30:30:30:40 | ...::new | an initialization vector | +| test_cipher.rs:29:30:29:32 | 0u8 | test_cipher.rs:29:30:29:32 | 0u8 | test_cipher.rs:30:30:30:40 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:30:30:30:40 | ...::new | an initialization vector | +| test_cipher.rs:37:27:37:74 | [...] | test_cipher.rs:37:27:37:74 | [...] | test_cipher.rs:38:30:38:47 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:38:30:38:47 | ...::new | a key | +| test_cipher.rs:37:27:37:74 | [...] | test_cipher.rs:37:27:37:74 | [...] | test_cipher.rs:38:30:38:47 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:38:30:38:47 | ...::new | a key | +| test_cipher.rs:41:29:41:76 | [...] | test_cipher.rs:41:29:41:76 | [...] | test_cipher.rs:42:30:42:47 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:42:30:42:47 | ...::new | a key | +| test_cipher.rs:41:29:41:76 | [...] | test_cipher.rs:41:29:41:76 | [...] | test_cipher.rs:42:30:42:47 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:42:30:42:47 | ...::new | a key | +| test_cipher.rs:50:37:50:52 | ...::zeroed | test_cipher.rs:50:37:50:52 | ...::zeroed | test_cipher.rs:51:31:51:48 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:51:31:51:48 | ...::new | a key | +| test_cipher.rs:50:37:50:52 | ...::zeroed | test_cipher.rs:50:37:50:52 | ...::zeroed | test_cipher.rs:51:31:51:48 | ...::new | This hard-coded value is used as $@. | test_cipher.rs:51:31:51:48 | ...::new | a key | +| test_cipher.rs:73:20:73:22 | 0u8 | test_cipher.rs:73:20:73:22 | 0u8 | test_cipher.rs:74:23:74:44 | ...::new_from_slice | This hard-coded value is used as $@. | test_cipher.rs:74:23:74:44 | ...::new_from_slice | a key | +edges +| test_cipher.rs:18:9:18:14 | const1 [&ref, element] | test_cipher.rs:19:73:19:78 | const1 [&ref, element] | provenance | | +| test_cipher.rs:18:28:18:36 | &... [&ref, element] | test_cipher.rs:18:9:18:14 | const1 [&ref, element] | provenance | | +| test_cipher.rs:18:29:18:36 | [0u8; 16] [element] | test_cipher.rs:18:28:18:36 | &... [&ref, element] | provenance | | +| test_cipher.rs:18:30:18:32 | 0u8 | test_cipher.rs:18:29:18:36 | [0u8; 16] [element] | provenance | | +| test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref, element] | test_cipher.rs:19:30:19:47 | ...::new | provenance | MaD:2 Sink:MaD:2 Sink:MaD:2 | +| test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref, element] | test_cipher.rs:19:30:19:47 | ...::new | provenance | MaD:4 Sink:MaD:4 Sink:MaD:4 | +| test_cipher.rs:19:73:19:78 | const1 [&ref, element] | test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref, element] | provenance | MaD:7 | +| test_cipher.rs:25:9:25:14 | const4 [&ref, element] | test_cipher.rs:26:66:26:71 | const4 [&ref, element] | provenance | | +| test_cipher.rs:25:28:25:36 | &... [&ref, element] | test_cipher.rs:25:9:25:14 | const4 [&ref, element] | provenance | | +| test_cipher.rs:25:29:25:36 | [0u8; 16] [element] | test_cipher.rs:25:28:25:36 | &... [&ref, element] | provenance | | +| test_cipher.rs:25:30:25:32 | 0u8 | test_cipher.rs:25:29:25:36 | [0u8; 16] [element] | provenance | | +| test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref, element] | test_cipher.rs:26:30:26:40 | ...::new | provenance | MaD:2 Sink:MaD:2 Sink:MaD:2 | +| test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref, element] | test_cipher.rs:26:30:26:40 | ...::new | provenance | MaD:4 Sink:MaD:4 Sink:MaD:4 | +| test_cipher.rs:26:66:26:71 | const4 [&ref, element] | test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref, element] | provenance | MaD:7 | +| test_cipher.rs:29:9:29:14 | const5 [&ref, element] | test_cipher.rs:30:95:30:100 | const5 [&ref, element] | provenance | | +| test_cipher.rs:29:28:29:36 | &... [&ref, element] | test_cipher.rs:29:9:29:14 | const5 [&ref, element] | provenance | | +| test_cipher.rs:29:29:29:36 | [0u8; 16] [element] | test_cipher.rs:29:28:29:36 | &... [&ref, element] | provenance | | +| test_cipher.rs:29:30:29:32 | 0u8 | test_cipher.rs:29:29:29:36 | [0u8; 16] [element] | provenance | | +| test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref, element] | test_cipher.rs:30:30:30:40 | ...::new | provenance | MaD:3 Sink:MaD:3 Sink:MaD:3 | +| test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref, element] | test_cipher.rs:30:30:30:40 | ...::new | provenance | MaD:5 Sink:MaD:5 Sink:MaD:5 | +| test_cipher.rs:30:95:30:100 | const5 [&ref, element] | test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref, element] | provenance | MaD:7 | +| 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:2 Sink:MaD:2 | +| test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | test_cipher.rs:38:30:38:47 | ...::new | provenance | MaD:4 Sink:MaD:4 | +| test_cipher.rs:38:73:38:79 | &const7 [&ref] | test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | provenance | MaD:7 | +| 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:2 Sink:MaD:2 | +| test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | test_cipher.rs:42:30:42:47 | ...::new | provenance | MaD:4 Sink:MaD:4 | +| test_cipher.rs:42:73:42:78 | const8 [&ref] | test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | provenance | MaD:7 | +| 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:6 | +| 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:2 Sink:MaD:2 Sink:MaD:2 | +| test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | test_cipher.rs:51:31:51:48 | ...::new | provenance | MaD:4 Sink:MaD:4 Sink:MaD:4 | +| test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | provenance | MaD:7 | +| 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, element] | test_cipher.rs:74:46:74:51 | const2 [&ref, element] | provenance | | +| test_cipher.rs:73:18:73:26 | &... [&ref, element] | test_cipher.rs:73:9:73:14 | const2 [&ref, element] | provenance | | +| test_cipher.rs:73:19:73:26 | [0u8; 32] [element] | test_cipher.rs:73:18:73:26 | &... [&ref, element] | provenance | | +| test_cipher.rs:73:20:73:22 | 0u8 | test_cipher.rs:73:19:73:26 | [0u8; 32] [element] | provenance | | +| test_cipher.rs:74:46:74:51 | const2 [&ref, element] | test_cipher.rs:74:23:74:44 | ...::new_from_slice | provenance | MaD:1 Sink:MaD:1 Sink:MaD:1 | +models +| 1 | Sink: <_ as crypto_common::KeyInit>::new_from_slice; Argument[0]; credentials-key | +| 2 | Sink: ::new; Argument[0]; credentials-key | +| 3 | Sink: ::new; Argument[1]; credentials-iv | +| 4 | Sink: ::new; Argument[0]; credentials-key | +| 5 | Sink: ::new; Argument[1]; credentials-iv | +| 6 | Source: core::mem::zeroed; ReturnValue.Element; constant-source | +| 7 | Summary: ::from_slice; Argument[0].Reference; ReturnValue.Reference; value | +nodes +| test_cipher.rs:18:9:18:14 | const1 [&ref, element] | semmle.label | const1 [&ref, element] | +| test_cipher.rs:18:28:18:36 | &... [&ref, element] | semmle.label | &... [&ref, element] | +| test_cipher.rs:18:29:18:36 | [0u8; 16] [element] | semmle.label | [0u8; 16] [element] | +| test_cipher.rs:18:30:18:32 | 0u8 | semmle.label | 0u8 | +| test_cipher.rs:19:30:19:47 | ...::new | semmle.label | ...::new | +| test_cipher.rs:19:30:19:47 | ...::new | semmle.label | ...::new | +| test_cipher.rs:19:49:19:79 | ...::from_slice(...) [&ref, element] | semmle.label | ...::from_slice(...) [&ref, element] | +| test_cipher.rs:19:73:19:78 | const1 [&ref, element] | semmle.label | const1 [&ref, element] | +| test_cipher.rs:25:9:25:14 | const4 [&ref, element] | semmle.label | const4 [&ref, element] | +| test_cipher.rs:25:28:25:36 | &... [&ref, element] | semmle.label | &... [&ref, element] | +| test_cipher.rs:25:29:25:36 | [0u8; 16] [element] | semmle.label | [0u8; 16] [element] | +| test_cipher.rs:25:30:25:32 | 0u8 | semmle.label | 0u8 | +| test_cipher.rs:26:30:26:40 | ...::new | semmle.label | ...::new | +| test_cipher.rs:26:30:26:40 | ...::new | semmle.label | ...::new | +| test_cipher.rs:26:42:26:72 | ...::from_slice(...) [&ref, element] | semmle.label | ...::from_slice(...) [&ref, element] | +| test_cipher.rs:26:66:26:71 | const4 [&ref, element] | semmle.label | const4 [&ref, element] | +| test_cipher.rs:29:9:29:14 | const5 [&ref, element] | semmle.label | const5 [&ref, element] | +| test_cipher.rs:29:28:29:36 | &... [&ref, element] | semmle.label | &... [&ref, element] | +| test_cipher.rs:29:29:29:36 | [0u8; 16] [element] | semmle.label | [0u8; 16] [element] | +| test_cipher.rs:29:30:29:32 | 0u8 | semmle.label | 0u8 | +| test_cipher.rs:30:30:30:40 | ...::new | semmle.label | ...::new | +| test_cipher.rs:30:30:30:40 | ...::new | semmle.label | ...::new | +| test_cipher.rs:30:72:30:101 | ...::from_slice(...) [&ref, element] | semmle.label | ...::from_slice(...) [&ref, element] | +| test_cipher.rs:30:95:30:100 | const5 [&ref, element] | semmle.label | const5 [&ref, element] | +| test_cipher.rs:37:9:37:14 | const7 | semmle.label | const7 | +| test_cipher.rs:37:27:37:74 | [...] | semmle.label | [...] | +| test_cipher.rs:38:30:38:47 | ...::new | semmle.label | ...::new | +| test_cipher.rs:38:30:38:47 | ...::new | semmle.label | ...::new | +| test_cipher.rs:38:49:38:80 | ...::from_slice(...) [&ref] | semmle.label | ...::from_slice(...) [&ref] | +| test_cipher.rs:38:73:38:79 | &const7 [&ref] | semmle.label | &const7 [&ref] | +| test_cipher.rs:38:74:38:79 | const7 | semmle.label | const7 | +| test_cipher.rs:41:9:41:14 | const8 [&ref] | semmle.label | const8 [&ref] | +| test_cipher.rs:41:28:41:76 | &... [&ref] | semmle.label | &... [&ref] | +| test_cipher.rs:41:29:41:76 | [...] | semmle.label | [...] | +| test_cipher.rs:42:30:42:47 | ...::new | semmle.label | ...::new | +| test_cipher.rs:42:30:42:47 | ...::new | semmle.label | ...::new | +| test_cipher.rs:42:49:42:79 | ...::from_slice(...) [&ref] | semmle.label | ...::from_slice(...) [&ref] | +| test_cipher.rs:42:73:42:78 | const8 [&ref] | semmle.label | const8 [&ref] | +| test_cipher.rs:50:9:50:15 | const10 [element] | semmle.label | const10 [element] | +| test_cipher.rs:50:37:50:52 | ...::zeroed | semmle.label | ...::zeroed | +| test_cipher.rs:50:37:50:54 | ...::zeroed(...) [element] | semmle.label | ...::zeroed(...) [element] | +| test_cipher.rs:51:31:51:48 | ...::new | semmle.label | ...::new | +| test_cipher.rs:51:31:51:48 | ...::new | semmle.label | ...::new | +| test_cipher.rs:51:50:51:82 | ...::from_slice(...) [&ref, element] | semmle.label | ...::from_slice(...) [&ref, element] | +| test_cipher.rs:51:74:51:81 | &const10 [&ref, element] | semmle.label | &const10 [&ref, element] | +| test_cipher.rs:51:75:51:81 | const10 [element] | semmle.label | const10 [element] | +| test_cipher.rs:73:9:73:14 | const2 [&ref, element] | semmle.label | const2 [&ref, element] | +| test_cipher.rs:73:18:73:26 | &... [&ref, element] | semmle.label | &... [&ref, element] | +| test_cipher.rs:73:19:73:26 | [0u8; 32] [element] | semmle.label | [0u8; 32] [element] | +| test_cipher.rs:73:20:73:22 | 0u8 | semmle.label | 0u8 | +| test_cipher.rs:74:23:74:44 | ...::new_from_slice | semmle.label | ...::new_from_slice | +| test_cipher.rs:74:46:74:51 | const2 [&ref, element] | semmle.label | const2 [&ref, element] | +subpaths diff --git a/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.qlref b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.qlref new file mode 100644 index 000000000000..77c0b90160ca --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-798/HardcodedCryptographicValue.qlref @@ -0,0 +1,4 @@ +query: queries/security/CWE-798/HardcodedCryptographicValue.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql diff --git a/rust/ql/test/query-tests/security/CWE-798/options.yml b/rust/ql/test/query-tests/security/CWE-798/options.yml new file mode 100644 index 000000000000..6713251d3ebd --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-798/options.yml @@ -0,0 +1,10 @@ +qltest_cargo_check: true +qltest_dependencies: + - cipher = { version = "0.4.4" } + - rabbit = { version = "0.4.1" } + - aes = { version = "0.8.4" } + - aes-gcm = { version = "0.10.3" } + - cfb-mode = { version = "0.8.2" } + - base64 = { version = "0.22.1" } + - getrandom = { version = "0.3.1" } + - getrandom2 = { package = "getrandom", version = "0.2.15" } diff --git a/rust/ql/test/query-tests/security/CWE-798/test_cipher.rs b/rust/ql/test/query-tests/security/CWE-798/test_cipher.rs new file mode 100644 index 000000000000..7278f786b100 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-798/test_cipher.rs @@ -0,0 +1,147 @@ + +use cipher::{consts::*, StreamCipher, AsyncStreamCipher, KeyInit, KeyIvInit, BlockEncrypt}; +use rabbit::{Rabbit, RabbitKeyOnly}; +use aes::Aes256; + +// --- tests --- + +fn test_stream_cipher_rabbit( + key: &[u8;16], iv: &[u8;16], plaintext: &str +) { + let mut data = plaintext.as_bytes().to_vec(); + + // rabbit + + let mut rabbit_cipher1 = RabbitKeyOnly::new(rabbit::Key::from_slice(key)); + rabbit_cipher1.apply_keystream(&mut data); + + let const1: &[u8;16] = &[0u8;16]; // $ Alert[rust/hard-coded-cryptographic-value] + let mut rabbit_cipher2 = RabbitKeyOnly::new(rabbit::Key::from_slice(const1)); // $ Sink + rabbit_cipher2.apply_keystream(&mut data); + + let mut rabbit_cipher3 = Rabbit::new(rabbit::Key::from_slice(key), rabbit::Iv::from_slice(iv)); + rabbit_cipher3.apply_keystream(&mut data); + + let const4: &[u8;16] = &[0u8;16]; // $ Alert[rust/hard-coded-cryptographic-value] + let mut rabbit_cipher4 = Rabbit::new(rabbit::Key::from_slice(const4), rabbit::Iv::from_slice(iv)); // $ Sink + rabbit_cipher4.apply_keystream(&mut data); + + let const5: &[u8;16] = &[0u8;16]; // $ Alert[rust/hard-coded-cryptographic-value] + let mut rabbit_cipher5 = Rabbit::new(rabbit::Key::from_slice(key), rabbit::Iv::from_slice(const5)); // $ Sink + rabbit_cipher5.apply_keystream(&mut data); + + // various expressions of constant arrays + + let const6: &[u8;16] = &[0u8;16]; // (unused, so good) + + let const7: [u8;16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // $ Alert[rust/hard-coded-cryptographic-value] + let mut rabbit_cipher7 = RabbitKeyOnly::new(rabbit::Key::from_slice(&const7)); // $ Sink + rabbit_cipher7.apply_keystream(&mut data); + + let const8: &[u8;16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // $ Alert[rust/hard-coded-cryptographic-value] + let mut rabbit_cipher8 = RabbitKeyOnly::new(rabbit::Key::from_slice(const8)); // $ Sink + rabbit_cipher8.apply_keystream(&mut data); + + let const9: [u16;8] = [0, 0, 0, 0, 0, 0, 0, 0]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let const9_conv = unsafe { const9.align_to::().1 }; // convert [u16;8] -> [u8;8] + let mut rabbit_cipher9 = RabbitKeyOnly::new(rabbit::Key::from_slice(const9_conv)); // $ MISSING: Sink + rabbit_cipher9.apply_keystream(&mut data); + + let const10: [u8;16] = unsafe { std::mem::zeroed() }; // $ Alert[rust/hard-coded-cryptographic-value] + let mut rabbit_cipher10 = RabbitKeyOnly::new(rabbit::Key::from_slice(&const10)); // $ Sink + rabbit_cipher10.apply_keystream(&mut data); +} + +use base64::Engine; + +fn test_block_cipher_aes( + key: &[u8], iv: &[u8], key256: &[u8;32], key_str: &str, + block128: &mut [u8;16], input: &[u8], output: &mut [u8] +) { + // aes + + let aes_cipher1 = Aes256::new(key256.into()); + aes_cipher1.encrypt_block(block128.into()); + + let const2 = &[0u8;32]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let aes_cipher2 = Aes256::new(const2.into()); // $ MISSING: Sink + aes_cipher2.encrypt_block(block128.into()); + + let aes_cipher3 = Aes256::new_from_slice(key256).unwrap(); + aes_cipher3.encrypt_block(block128.into()); + + let const2 = &[0u8;32]; // $ Alert[rust/hard-coded-cryptographic-value] + let aes_cipher4 = Aes256::new_from_slice(const2).unwrap(); // $ Sink + aes_cipher4.encrypt_block(block128.into()); + + let aes_cipher5 = cfb_mode::Encryptor::::new(key.into(), iv.into()); + _ = aes_cipher5.encrypt_b2b(input, output).unwrap(); + + let const6 = &[0u8;32]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let aes_cipher6 = cfb_mode::Encryptor::::new(const6.into(), iv.into()); // $ MISSING: Sink + _ = aes_cipher6.encrypt_b2b(input, output).unwrap(); + + let const7 = &[0u8; 16]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let aes_cipher7 = cfb_mode::Encryptor::::new(key.into(), const7.into()); // $ MISSING: Sink + _ = aes_cipher7.encrypt_b2b(input, output).unwrap(); + + // various string conversions + + let key8: &[u8] = key_str.as_bytes(); + let aes_cipher8 = cfb_mode::Encryptor::::new(key8.into(), iv.into()); + _ = aes_cipher8.encrypt_b2b(input, output).unwrap(); + + let key9: &[u8] = "1234567890123456".as_bytes(); // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let aes_cipher9 = cfb_mode::Encryptor::::new(key9.into(), iv.into()); + _ = aes_cipher9.encrypt_b2b(input, output).unwrap(); + + let key10: [u8; 32] = match base64::engine::general_purpose::STANDARD.decode(key_str) { + Ok(x) => x.try_into().unwrap(), + Err(_) => "1234567890123456".as_bytes().try_into().unwrap() // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + }; + let aes_cipher10 = Aes256::new(&key10.into()); + aes_cipher10.encrypt_block(block128.into()); + + if let Ok(const11) = base64::engine::general_purpose::STANDARD.decode("1234567890123456") { // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let key11: [u8; 32] = const11.try_into().unwrap(); + let aes_cipher11 = Aes256::new(&key11.into()); + aes_cipher11.encrypt_block(block128.into()); + } +} + +use aes_gcm::aead::{Aead, AeadCore, OsRng}; +use aes_gcm::{Aes256Gcm, Key, Nonce}; + +fn test_aes_gcm( +) { + // aes (GCM) + + let key1 = Aes256Gcm::generate_key(aes_gcm::aead::OsRng); + let nonce1 = Aes256Gcm::generate_nonce(aes_gcm::aead::OsRng); + let cipher1 = Aes256Gcm::new(&key1); + let _ = cipher1.encrypt(&nonce1, b"plaintext".as_ref()).unwrap(); + + let key2: [u8;32] = [0;32]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let nonce2 = [0;12]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let cipher2 = Aes256Gcm::new(&key2.into()); // $ MISSING: Sink + let _ = cipher2.encrypt(&nonce2.into(), b"plaintext".as_ref()).unwrap(); // $ MISSING: Sink + + let key3_array: &[u8;32] = &[0xff;32]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let key3 = Key::::from_slice(key3_array); + let nonce3: [u8;12] = [0xff;12]; // $ MISSING: Alert[rust/hard-coded-cryptographic-value] + let cipher3 = Aes256Gcm::new(&key3); // $ MISSING: Sink + let _ = cipher3.encrypt(&nonce3.into(), b"plaintext".as_ref()).unwrap(); // $ MISSING: Sink + + // with barrier + + let mut key4 = [0u8;32]; + let mut nonce4 = [0u8;12]; + _ = getrandom::fill(&mut key4).unwrap(); + _ = getrandom2::getrandom(&mut nonce4).unwrap(); + let cipher4 = Aes256Gcm::new(&key4.into()); + let _ = cipher4.encrypt(&nonce4.into(), b"plaintext".as_ref()).unwrap(); + + let mut key5 = [0u8;32]; + _ = getrandom::fill(&mut key5).unwrap(); + let _ = Aes256::new_from_slice(&key5).unwrap(); +}