From acb19235a3a223415b012ac2d773e7b2f975af4f Mon Sep 17 00:00:00 2001 From: AkshayK Date: Wed, 26 Aug 2026 20:57:11 -0400 Subject: [PATCH] cpp: add insecure randomness query with RNG security models Add the cpp/insecure-randomness query (CWE-330/338) that flags cryptographically insecure random numbers used as security-sensitive values (keys, IVs, nonces). - shared quantum: add isCryptographicallySecure() to Crypto::RandomNumberGenerationInstance (defaults to none). - cpp quantum lib: model C stdlib, POSIX/BSD, Windows CNG, and C++ generators via the new randomNumberGeneratorModel extensible predicate; classify OpenSSL RAND_bytes/RAND_priv_bytes as secure and RAND_pseudo_bytes as insecure. - MaD: populate randomNumberGeneratorModel with the generator rows. --- .../2026-08-26-insecure-randomness-model.md | 4 + cpp/ql/lib/experimental/quantum/Language.qll | 1 + .../experimental/quantum/OpenSSL/Random.qll | 8 +- .../experimental/quantum/Standard/Random.qll | 91 ++++ .../ext/experimental.quantum.Random.model.yml | 34 ++ .../Security/CWE/CWE-330/InsecureRandomness.c | 18 + .../CWE/CWE-330/InsecureRandomness.qhelp | 52 +++ .../CWE/CWE-330/InsecureRandomness.ql | 53 +++ .../2026-08-26-insecure-randomness-query.md | 4 + .../CWE/CWE-330/InsecureRandomness.expected | 116 ++++++ .../CWE/CWE-330/InsecureRandomness.qlref | 2 + .../query-tests/Security/CWE/CWE-330/options | 1 + .../query-tests/Security/CWE/CWE-330/test.cpp | 392 ++++++++++++++++++ ...26-08-26-random-security-classification.md | 4 + .../codeql/quantum/experimental/Model.qll | 10 + 15 files changed, 789 insertions(+), 1 deletion(-) create mode 100644 cpp/ql/lib/change-notes/2026-08-26-insecure-randomness-model.md create mode 100644 cpp/ql/lib/experimental/quantum/Standard/Random.qll create mode 100644 cpp/ql/lib/ext/experimental.quantum.Random.model.yml create mode 100644 cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.c create mode 100644 cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.qhelp create mode 100644 cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.ql create mode 100644 cpp/ql/src/change-notes/2026-08-26-insecure-randomness-query.md create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.expected create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.qlref create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-330/options create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-330/test.cpp create mode 100644 shared/quantum/change-notes/2026-08-26-random-security-classification.md diff --git a/cpp/ql/lib/change-notes/2026-08-26-insecure-randomness-model.md b/cpp/ql/lib/change-notes/2026-08-26-insecure-randomness-model.md new file mode 100644 index 000000000000..8a1b00818dd1 --- /dev/null +++ b/cpp/ql/lib/change-notes/2026-08-26-insecure-randomness-model.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added models of random number generation from the C standard library, POSIX/BSD, the Windows CryptoAPI/CNG, and the C++ `` engines as instances of the `Crypto::RandomNumberGenerationInstance` concept, each classified as cryptographically secure or insecure. The set of generators is defined as data through the new `randomNumberGeneratorModel` extensible predicate, so it can be extended by data-extension packs. The OpenSSL `RAND_pseudo_bytes` function is now classified as insecure, while `RAND_bytes` and `RAND_priv_bytes` are classified as secure. diff --git a/cpp/ql/lib/experimental/quantum/Language.qll b/cpp/ql/lib/experimental/quantum/Language.qll index d3feb3fe0d98..7b5b15498693 100644 --- a/cpp/ql/lib/experimental/quantum/Language.qll +++ b/cpp/ql/lib/experimental/quantum/Language.qll @@ -110,3 +110,4 @@ private class ConstantDataSource extends Crypto::GenericConstantSourceInstance i } import OpenSSL.OpenSSL +import Standard.Random diff --git a/cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll b/cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll index d39087bcbce0..19353ee4b55d 100644 --- a/cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll +++ b/cpp/ql/lib/experimental/quantum/OpenSSL/Random.qll @@ -6,7 +6,7 @@ private import semmle.code.cpp.dataflow.new.DataFlow class OpenSslRandomNumberGeneratorInstance extends Crypto::RandomNumberGenerationInstance instanceof Call { OpenSslRandomNumberGeneratorInstance() { - this.(Call).getTarget().getName() in ["RAND_bytes", "RAND_pseudo_bytes"] + this.(Call).getTarget().getName() in ["RAND_bytes", "RAND_priv_bytes", "RAND_pseudo_bytes"] } override Crypto::DataFlowNode getOutputNode() { @@ -14,4 +14,10 @@ class OpenSslRandomNumberGeneratorInstance extends Crypto::RandomNumberGeneratio } override string getGeneratorName() { result = this.(Call).getTarget().getName() } + + override predicate isCryptographicallySecure() { + // `RAND_pseudo_bytes` is deprecated and does not guarantee cryptographically + // secure output, so it is deliberately excluded here. + this.(Call).getTarget().getName() in ["RAND_bytes", "RAND_priv_bytes"] + } } diff --git a/cpp/ql/lib/experimental/quantum/Standard/Random.qll b/cpp/ql/lib/experimental/quantum/Standard/Random.qll new file mode 100644 index 000000000000..cdf73c27995c --- /dev/null +++ b/cpp/ql/lib/experimental/quantum/Standard/Random.qll @@ -0,0 +1,91 @@ +/** + * Models random number generation from the C standard library, POSIX/BSD, the + * Windows CryptoAPI/CNG, and the C++ `` engines, as instances of the + * shared quantum `Crypto::RandomNumberGenerationInstance` concept. + * + * The set of modelled generators is defined as data through the + * `randomNumberGeneratorModel` extensible predicate, so that downstream packs can + * register additional generators without editing this library. Each row records + * whether the generator is cryptographically secure; insecure generators (e.g. + * `rand`, `std::mt19937`) leave `isCryptographicallySecure()` at its default of + * holding for no generator. + * + * Only functions that *produce* random output are modelled here. Seeding + * functions such as `srand`, `srandom`, `srand48`, and `seed48` produce no output + * artifact and are therefore out of scope for this concept. + */ + +import cpp +private import experimental.quantum.Language + +/** + * Holds if a call to the function `name` is a random number generator. + * + * `namespace` and `type` identify the function: when `type` is empty, `name` is a + * global or `std` free function (e.g. `rand`); otherwise `name` is a member + * function of the class (template) whose unqualified name is `type` (e.g. + * `operator()` of `std::mersenne_twister_engine`). + * + * `output` is the index of the argument into which the random bytes are written, + * or the empty string if the random value is the return value. + * + * `secure` holds if the generator is cryptographically secure. + */ +extensible predicate randomNumberGeneratorModel( + string namespace, string type, string name, string output, boolean secure +); + +/** + * Holds if `c` is a call to a modelled random number generator named + * `generatorName`, writing its output as described by `output` (see + * `randomNumberGeneratorModel`), where `secure` holds if it is cryptographically + * secure. + */ +private predicate randomNumberGeneratorCall( + Call c, string generatorName, string output, boolean secure +) { + exists(string namespace, string type, string name, Function f | + randomNumberGeneratorModel(namespace, type, name, output, secure) and + f = c.getTarget() + | + // A global or `std` free function, e.g. `rand` or `std::rand`. + type = "" and + f.hasGlobalOrStdName(name) and + generatorName = name + or + // A member function of a class (template), e.g. `std::mt19937::operator()`. + type != "" and + f.getName() = name and + f.getDeclaringType().getSimpleName() = type and + (if namespace = "" then generatorName = type else generatorName = namespace + "::" + type) + ) +} + +/** + * A call to a random number generator modelled through the `randomNumberGeneratorModel` + * extensible predicate. + */ +class ModeledRandomNumberGeneratorInstance extends Crypto::RandomNumberGenerationInstance instanceof Call +{ + string generatorName; + string output; + boolean secure; + + ModeledRandomNumberGeneratorInstance() { + randomNumberGeneratorCall(this, generatorName, output, secure) + } + + override Crypto::DataFlowNode getOutputNode() { + output = "" and result.asExpr() = this + or + output != "" and result.asDefiningArgument() = super.getArgument(output.toInt()) + } + + override string getGeneratorName() { result = generatorName } + + // If a call matches several `randomNumberGeneratorModel` rows with conflicting + // `secure` values (e.g. a downstream pack reclassifies a generator), the secure + // classification wins: this holds as soon as any matching row has `secure = true`. + // Rows should therefore agree on the security of a given generator. + override predicate isCryptographicallySecure() { secure = true } +} diff --git a/cpp/ql/lib/ext/experimental.quantum.Random.model.yml b/cpp/ql/lib/ext/experimental.quantum.Random.model.yml new file mode 100644 index 000000000000..130ceef5e569 --- /dev/null +++ b/cpp/ql/lib/ext/experimental.quantum.Random.model.yml @@ -0,0 +1,34 @@ +extensions: + - addsTo: + pack: codeql/cpp-all + extensible: randomNumberGeneratorModel + # namespace, type, name, output, secure + data: + # C standard library / POSIX / BSD generators returning the value (insecure). + - ["", "", "rand", "", false] + - ["", "", "random", "", false] + - ["", "", "drand48", "", false] + - ["", "", "erand48", "", false] + - ["", "", "lrand48", "", false] + - ["", "", "nrand48", "", false] + - ["", "", "mrand48", "", false] + - ["", "", "jrand48", "", false] + - ["", "", "rand_r", "", false] + # POSIX/BSD generators returning the value (secure). + - ["", "", "arc4random", "", true] + - ["", "", "arc4random_uniform", "", true] + # Generators writing to a buffer argument (secure). + - ["", "", "arc4random_buf", "0", true] + - ["", "", "getrandom", "0", true] + - ["", "", "getentropy", "0", true] + - ["", "", "RtlGenRandom", "0", true] + - ["", "", "BCryptGenRandom", "1", true] + - ["", "", "CryptGenRandom", "2", true] + # C++ engines (insecure) and std::random_device (secure). + - ["std", "mersenne_twister_engine", "operator()", "", false] + - ["std", "linear_congruential_engine", "operator()", "", false] + - ["std", "subtract_with_carry_engine", "operator()", "", false] + - ["std", "discard_block_engine", "operator()", "", false] + - ["std", "shuffle_order_engine", "operator()", "", false] + - ["std", "independent_bits_engine", "operator()", "", false] + - ["std", "random_device", "operator()", "", true] diff --git a/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.c b/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.c new file mode 100644 index 000000000000..045edb0dde93 --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.c @@ -0,0 +1,18 @@ +#include +#include +#include + +void encrypt(EVP_CIPHER_CTX *ctx, unsigned char *iv) { + unsigned char key[16]; + + // BAD: the key is derived from a cryptographically weak generator, so an + // attacker may be able to predict it. + for (int i = 0; i < 16; i++) { + key[i] = (unsigned char)rand(); + } + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, key, iv); + + // GOOD: the key is filled from a cryptographically secure generator. + RAND_bytes(key, 16); + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, key, iv); +} diff --git a/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.qhelp b/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.qhelp new file mode 100644 index 000000000000..067c54b83637 --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.qhelp @@ -0,0 +1,52 @@ + + + +

+Using a cryptographically weak pseudo-random number generator to produce a security-sensitive value, +such as an encryption key, an initialization vector, a nonce, or a session token, may allow an attacker +to predict the value. +

+ +

+A pseudo-random number generator produces a sequence of numbers that only approximates the properties of +random numbers. The sequence is completely determined by a relatively small seed value. Generators such as +rand, the drand48 family, and the C++ <random> engines +(for example std::mt19937) are not designed to resist prediction, so an attacker who observes +some output, or who can reconstruct the seed, may be able to predict future values. +

+
+ + +

+Use a cryptographically secure random number generator when the output is used in a security-sensitive +context. Suitable choices include getrandom, getentropy, the +arc4random family, OpenSSL's RAND_bytes, the Windows +BCryptGenRandom function, and C++'s std::random_device (where it is backed by a +secure source). +

+
+ + +

+The following example seeds an AES key with rand. Because rand is not +cryptographically secure, an attacker may be able to predict the key. +

+ + + +

+Instead, fill the key from a cryptographically secure generator such as RAND_bytes. +

+
+ + +
  • Wikipedia: +Pseudorandom number generator.
  • +
  • Common Weakness Enumeration: +CWE-330: Use of Insufficiently Random Values.
  • +
  • Common Weakness Enumeration: +CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).
  • +
    +
    diff --git a/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.ql b/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.ql new file mode 100644 index 000000000000..978fd46a3032 --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-330/InsecureRandomness.ql @@ -0,0 +1,53 @@ +/** + * @name Insecure randomness + * @description Using a cryptographically insecure pseudo-random number generator to generate a + * security-sensitive value may allow an attacker to predict what value will + * be generated. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.8 + * @precision medium + * @id cpp/insecure-randomness + * @tags security + * external/cwe/cwe-330 + * external/cwe/cwe-338 + */ + +import cpp +import experimental.quantum.Language +import InsecureRandomnessFlow::PathGraph + +/** + * A taint-tracking configuration for flow from a cryptographically insecure + * random number generator to security-sensitive value such as a key, IV, or nonce. + */ +module InsecureRandomnessConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(Crypto::RandomNumberGenerationInstance generator | + not generator.isCryptographicallySecure() and + source = generator.getOutputNode() + ) + } + + predicate isSink(DataFlow::Node sink) { + sink = any(Crypto::KeyOperationInstance op).getKeyConsumer() + or + sink = any(Crypto::KeyOperationInstance op).getNonceConsumer() + or + sink = any(Crypto::KeyGenerationOperationInstance op).getKeyValueConsumer() + } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node) } + + predicate isBarrierOut(DataFlow::Node node) { isSink(node) } + + predicate observeDiffInformedIncrementalMode() { any() } +} + +module InsecureRandomnessFlow = TaintTracking::Global; + +from InsecureRandomnessFlow::PathNode source, InsecureRandomnessFlow::PathNode sink +where InsecureRandomnessFlow::flowPath(source, sink) +select sink.getNode(), source, sink, + "This security-sensitive value depends on $@, which is not cryptographically secure.", + source.getNode(), "a randomly generated number" diff --git a/cpp/ql/src/change-notes/2026-08-26-insecure-randomness-query.md b/cpp/ql/src/change-notes/2026-08-26-insecure-randomness-query.md new file mode 100644 index 000000000000..56f1231367fe --- /dev/null +++ b/cpp/ql/src/change-notes/2026-08-26-insecure-randomness-query.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* Added a new query, `cpp/insecure-randomness` ("Insecure randomness"), which flags cryptographically insecure random numbers (for example from `rand` or `std::mt19937`) that are used as security-sensitive values such as encryption keys, IVs, or nonces. diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.expected new file mode 100644 index 000000000000..5b461e7f995c --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.expected @@ -0,0 +1,116 @@ +#select +| test.cpp:134:49:134:50 | *& ... | test.cpp:133:21:133:41 | call to rand | test.cpp:134:49:134:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:133:21:133:41 | call to rand | a randomly generated number | +| test.cpp:142:49:142:50 | *& ... | test.cpp:141:21:141:46 | call to rand | test.cpp:142:49:142:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:141:21:141:46 | call to rand | a randomly generated number | +| test.cpp:150:49:150:50 | *& ... | test.cpp:149:21:149:43 | call to random | test.cpp:150:49:150:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:149:21:149:43 | call to random | a randomly generated number | +| test.cpp:158:49:158:50 | *& ... | test.cpp:157:21:157:44 | call to drand48 | test.cpp:158:49:158:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:157:21:157:44 | call to drand48 | a randomly generated number | +| test.cpp:167:49:167:50 | *& ... | test.cpp:166:21:166:45 | call to erand48 | test.cpp:167:49:167:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:166:21:166:45 | call to erand48 | a randomly generated number | +| test.cpp:175:49:175:50 | *& ... | test.cpp:174:21:174:44 | call to lrand48 | test.cpp:175:49:175:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:174:21:174:44 | call to lrand48 | a randomly generated number | +| test.cpp:184:49:184:50 | *& ... | test.cpp:183:21:183:45 | call to nrand48 | test.cpp:184:49:184:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:183:21:183:45 | call to nrand48 | a randomly generated number | +| test.cpp:192:49:192:50 | *& ... | test.cpp:191:21:191:44 | call to mrand48 | test.cpp:192:49:192:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:191:21:191:44 | call to mrand48 | a randomly generated number | +| test.cpp:201:49:201:50 | *& ... | test.cpp:200:21:200:45 | call to jrand48 | test.cpp:201:49:201:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:200:21:200:45 | call to jrand48 | a randomly generated number | +| test.cpp:210:49:210:50 | *& ... | test.cpp:209:21:209:48 | call to rand_r | test.cpp:210:49:210:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:209:21:209:48 | call to rand_r | a randomly generated number | +| test.cpp:222:54:222:56 | *& ... | test.cpp:221:22:221:42 | call to rand | test.cpp:222:54:222:56 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:221:22:221:42 | call to rand | a randomly generated number | +| test.cpp:228:42:228:43 | *& ... | test.cpp:227:21:227:41 | call to rand | test.cpp:228:42:228:43 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:227:21:227:41 | call to rand | a randomly generated number | +| test.cpp:240:49:240:50 | *& ... | test.cpp:239:21:239:40 | call to operator() | test.cpp:240:49:240:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:239:21:239:40 | call to operator() | a randomly generated number | +| test.cpp:249:49:249:50 | *& ... | test.cpp:248:21:248:40 | call to operator() | test.cpp:249:49:249:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:248:21:248:40 | call to operator() | a randomly generated number | +| test.cpp:258:49:258:50 | *& ... | test.cpp:257:21:257:40 | call to operator() | test.cpp:258:49:258:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:257:21:257:40 | call to operator() | a randomly generated number | +| test.cpp:267:49:267:50 | *& ... | test.cpp:266:21:266:40 | call to operator() | test.cpp:267:49:267:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:266:21:266:40 | call to operator() | a randomly generated number | +| test.cpp:276:49:276:50 | *& ... | test.cpp:275:21:275:40 | call to operator() | test.cpp:276:49:276:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:275:21:275:40 | call to operator() | a randomly generated number | +| test.cpp:285:49:285:50 | *& ... | test.cpp:284:21:284:40 | call to operator() | test.cpp:285:49:285:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:284:21:284:40 | call to operator() | a randomly generated number | +| test.cpp:298:49:298:50 | *& ... | test.cpp:297:21:297:22 | RAND_pseudo_bytes output argument | test.cpp:298:49:298:50 | *& ... | This security-sensitive value depends on $@, which is not cryptographically secure. | test.cpp:297:21:297:22 | RAND_pseudo_bytes output argument | a randomly generated number | +edges +| test.cpp:133:21:133:41 | call to rand | test.cpp:133:21:133:41 | call to rand | provenance | | +| test.cpp:133:21:133:41 | call to rand | test.cpp:134:49:134:50 | *& ... | provenance | | +| test.cpp:141:21:141:46 | call to rand | test.cpp:141:21:141:46 | call to rand | provenance | | +| test.cpp:141:21:141:46 | call to rand | test.cpp:142:49:142:50 | *& ... | provenance | | +| test.cpp:149:21:149:43 | call to random | test.cpp:149:21:149:43 | call to random | provenance | | +| test.cpp:149:21:149:43 | call to random | test.cpp:150:49:150:50 | *& ... | provenance | | +| test.cpp:157:21:157:44 | call to drand48 | test.cpp:157:21:157:44 | call to drand48 | provenance | | +| test.cpp:157:21:157:44 | call to drand48 | test.cpp:158:49:158:50 | *& ... | provenance | | +| test.cpp:166:21:166:45 | call to erand48 | test.cpp:166:21:166:45 | call to erand48 | provenance | | +| test.cpp:166:21:166:45 | call to erand48 | test.cpp:167:49:167:50 | *& ... | provenance | | +| test.cpp:174:21:174:44 | call to lrand48 | test.cpp:174:21:174:44 | call to lrand48 | provenance | | +| test.cpp:174:21:174:44 | call to lrand48 | test.cpp:175:49:175:50 | *& ... | provenance | | +| test.cpp:183:21:183:45 | call to nrand48 | test.cpp:183:21:183:45 | call to nrand48 | provenance | | +| test.cpp:183:21:183:45 | call to nrand48 | test.cpp:184:49:184:50 | *& ... | provenance | | +| test.cpp:191:21:191:44 | call to mrand48 | test.cpp:191:21:191:44 | call to mrand48 | provenance | | +| test.cpp:191:21:191:44 | call to mrand48 | test.cpp:192:49:192:50 | *& ... | provenance | | +| test.cpp:200:21:200:45 | call to jrand48 | test.cpp:200:21:200:45 | call to jrand48 | provenance | | +| test.cpp:200:21:200:45 | call to jrand48 | test.cpp:201:49:201:50 | *& ... | provenance | | +| test.cpp:209:21:209:48 | call to rand_r | test.cpp:209:21:209:48 | call to rand_r | provenance | | +| test.cpp:209:21:209:48 | call to rand_r | test.cpp:210:49:210:50 | *& ... | provenance | | +| test.cpp:221:22:221:42 | call to rand | test.cpp:221:22:221:42 | call to rand | provenance | | +| test.cpp:221:22:221:42 | call to rand | test.cpp:222:54:222:56 | *& ... | provenance | | +| test.cpp:227:21:227:41 | call to rand | test.cpp:227:21:227:41 | call to rand | provenance | | +| test.cpp:227:21:227:41 | call to rand | test.cpp:228:42:228:43 | *& ... | provenance | | +| test.cpp:239:21:239:40 | call to operator() | test.cpp:239:21:239:40 | call to operator() | provenance | | +| test.cpp:239:21:239:40 | call to operator() | test.cpp:240:49:240:50 | *& ... | provenance | | +| test.cpp:248:21:248:40 | call to operator() | test.cpp:248:21:248:40 | call to operator() | provenance | | +| test.cpp:248:21:248:40 | call to operator() | test.cpp:249:49:249:50 | *& ... | provenance | | +| test.cpp:257:21:257:40 | call to operator() | test.cpp:257:21:257:40 | call to operator() | provenance | | +| test.cpp:257:21:257:40 | call to operator() | test.cpp:258:49:258:50 | *& ... | provenance | | +| test.cpp:266:21:266:40 | call to operator() | test.cpp:266:21:266:40 | call to operator() | provenance | | +| test.cpp:266:21:266:40 | call to operator() | test.cpp:267:49:267:50 | *& ... | provenance | | +| test.cpp:275:21:275:40 | call to operator() | test.cpp:275:21:275:40 | call to operator() | provenance | | +| test.cpp:275:21:275:40 | call to operator() | test.cpp:276:49:276:50 | *& ... | provenance | | +| test.cpp:284:21:284:40 | call to operator() | test.cpp:284:21:284:40 | call to operator() | provenance | | +| test.cpp:284:21:284:40 | call to operator() | test.cpp:285:49:285:50 | *& ... | provenance | | +| test.cpp:297:21:297:22 | RAND_pseudo_bytes output argument | test.cpp:298:49:298:50 | *& ... | provenance | | +nodes +| test.cpp:133:21:133:41 | call to rand | semmle.label | call to rand | +| test.cpp:133:21:133:41 | call to rand | semmle.label | call to rand | +| test.cpp:134:49:134:50 | *& ... | semmle.label | *& ... | +| test.cpp:141:21:141:46 | call to rand | semmle.label | call to rand | +| test.cpp:141:21:141:46 | call to rand | semmle.label | call to rand | +| test.cpp:142:49:142:50 | *& ... | semmle.label | *& ... | +| test.cpp:149:21:149:43 | call to random | semmle.label | call to random | +| test.cpp:149:21:149:43 | call to random | semmle.label | call to random | +| test.cpp:150:49:150:50 | *& ... | semmle.label | *& ... | +| test.cpp:157:21:157:44 | call to drand48 | semmle.label | call to drand48 | +| test.cpp:157:21:157:44 | call to drand48 | semmle.label | call to drand48 | +| test.cpp:158:49:158:50 | *& ... | semmle.label | *& ... | +| test.cpp:166:21:166:45 | call to erand48 | semmle.label | call to erand48 | +| test.cpp:166:21:166:45 | call to erand48 | semmle.label | call to erand48 | +| test.cpp:167:49:167:50 | *& ... | semmle.label | *& ... | +| test.cpp:174:21:174:44 | call to lrand48 | semmle.label | call to lrand48 | +| test.cpp:174:21:174:44 | call to lrand48 | semmle.label | call to lrand48 | +| test.cpp:175:49:175:50 | *& ... | semmle.label | *& ... | +| test.cpp:183:21:183:45 | call to nrand48 | semmle.label | call to nrand48 | +| test.cpp:183:21:183:45 | call to nrand48 | semmle.label | call to nrand48 | +| test.cpp:184:49:184:50 | *& ... | semmle.label | *& ... | +| test.cpp:191:21:191:44 | call to mrand48 | semmle.label | call to mrand48 | +| test.cpp:191:21:191:44 | call to mrand48 | semmle.label | call to mrand48 | +| test.cpp:192:49:192:50 | *& ... | semmle.label | *& ... | +| test.cpp:200:21:200:45 | call to jrand48 | semmle.label | call to jrand48 | +| test.cpp:200:21:200:45 | call to jrand48 | semmle.label | call to jrand48 | +| test.cpp:201:49:201:50 | *& ... | semmle.label | *& ... | +| test.cpp:209:21:209:48 | call to rand_r | semmle.label | call to rand_r | +| test.cpp:209:21:209:48 | call to rand_r | semmle.label | call to rand_r | +| test.cpp:210:49:210:50 | *& ... | semmle.label | *& ... | +| test.cpp:221:22:221:42 | call to rand | semmle.label | call to rand | +| test.cpp:221:22:221:42 | call to rand | semmle.label | call to rand | +| test.cpp:222:54:222:56 | *& ... | semmle.label | *& ... | +| test.cpp:227:21:227:41 | call to rand | semmle.label | call to rand | +| test.cpp:227:21:227:41 | call to rand | semmle.label | call to rand | +| test.cpp:228:42:228:43 | *& ... | semmle.label | *& ... | +| test.cpp:239:21:239:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:239:21:239:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:240:49:240:50 | *& ... | semmle.label | *& ... | +| test.cpp:248:21:248:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:248:21:248:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:249:49:249:50 | *& ... | semmle.label | *& ... | +| test.cpp:257:21:257:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:257:21:257:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:258:49:258:50 | *& ... | semmle.label | *& ... | +| test.cpp:266:21:266:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:266:21:266:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:267:49:267:50 | *& ... | semmle.label | *& ... | +| test.cpp:275:21:275:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:275:21:275:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:276:49:276:50 | *& ... | semmle.label | *& ... | +| test.cpp:284:21:284:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:284:21:284:40 | call to operator() | semmle.label | call to operator() | +| test.cpp:285:49:285:50 | *& ... | semmle.label | *& ... | +| test.cpp:297:21:297:22 | RAND_pseudo_bytes output argument | semmle.label | RAND_pseudo_bytes output argument | +| test.cpp:298:49:298:50 | *& ... | semmle.label | *& ... | +subpaths diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.qlref new file mode 100644 index 000000000000..6ebdd1d7668f --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-330/InsecureRandomness.qlref @@ -0,0 +1,2 @@ +query: Security/CWE/CWE-330/InsecureRandomness.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-330/options b/cpp/ql/test/query-tests/Security/CWE/CWE-330/options new file mode 100644 index 000000000000..723193577b3b --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-330/options @@ -0,0 +1 @@ +semmle-extractor-options: -I ../../../../experimental/stubs diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-330/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-330/test.cpp new file mode 100644 index 000000000000..153d9a3c9d6d --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-330/test.cpp @@ -0,0 +1,392 @@ +#include "openssl/evp.h" +#include "openssl/obj_mac.h" +#include "openssl/rand.h" + +typedef unsigned long size_t; + +// --- C standard library / POSIX / BSD random number generators (declarations) --- +extern "C" { +// Not cryptographically secure. +int rand(void); +long random(void); +double drand48(void); +double erand48(unsigned short xsubi[3]); +long lrand48(void); +long nrand48(unsigned short xsubi[3]); +long mrand48(void); +long jrand48(unsigned short xsubi[3]); +int rand_r(unsigned int *seedp); +// Cryptographically secure. +unsigned int arc4random(void); +unsigned int arc4random_uniform(unsigned int upper_bound); +void arc4random_buf(void *buf, size_t nbytes); +int getrandom(void *buf, size_t buflen, unsigned int flags); +int getentropy(void *buffer, size_t length); +} + +// --- Windows CryptoAPI / CNG (cryptographically secure, simplified signatures) --- +typedef unsigned long DWORD; +typedef int BOOL; +typedef unsigned char BYTE; +typedef void *HCRYPTPROV; +typedef long NTSTATUS; +typedef void *BCRYPT_ALG_HANDLE; +typedef unsigned long ULONG; +typedef unsigned char BOOLEAN; +BOOLEAN RtlGenRandom(void *RandomBuffer, ULONG RandomBufferLength); +BOOL CryptGenRandom(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer); +NTSTATUS BCryptGenRandom(BCRYPT_ALG_HANDLE hAlgorithm, unsigned char *pbBuffer, ULONG cbBuffer, + ULONG dwFlags); + +// --- C++ engines (faithful subset of the standard library) --- +namespace std { +int rand(void); + +template +class linear_congruential_engine { +public: + typedef UIntType result_type; + linear_congruential_engine(); + result_type operator()(); +}; + +template +class mersenne_twister_engine { +public: + typedef UIntType result_type; + mersenne_twister_engine(); + result_type operator()(); +}; + +template +class subtract_with_carry_engine { +public: + typedef UIntType result_type; + subtract_with_carry_engine(); + result_type operator()(); +}; + +template +class discard_block_engine { +public: + typedef typename Engine::result_type result_type; + discard_block_engine(); + result_type operator()(); +}; + +template +class shuffle_order_engine { +public: + typedef typename Engine::result_type result_type; + shuffle_order_engine(); + result_type operator()(); +}; + +template +class independent_bits_engine { +public: + typedef UIntType result_type; + independent_bits_engine(); + result_type operator()(); +}; + +typedef mersenne_twister_engine mt19937; +typedef mersenne_twister_engine mt19937_64; +typedef linear_congruential_engine minstd_rand0; +typedef linear_congruential_engine minstd_rand; +typedef subtract_with_carry_engine ranlux24_base; +typedef subtract_with_carry_engine ranlux48_base; +typedef discard_block_engine ranlux24; +typedef discard_block_engine ranlux48; +typedef shuffle_order_engine knuth_b; +typedef minstd_rand0 default_random_engine; + +class random_device { +public: + typedef unsigned int result_type; + random_device(); + result_type operator()(); +}; +} + +// Run an OpenSSL cipher operation so the key/IV arguments of `EVP_EncryptInit_ex` +// become key/nonce consumers. `finish` provides the (dominated) final step. +static void finish(EVP_CIPHER_CTX *ctx) { + unsigned char in[16] = {0}; + unsigned char out[64]; + int outlen = 0; + EVP_EncryptUpdate(ctx, out, &outlen, in, 16); + EVP_EncryptFinal_ex(ctx, out, &outlen); +} + +// =========================================================================== +// Insecure generators feeding a cipher key (positive). +// =========================================================================== + +void test_rand() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)rand(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_std_rand() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)std::rand(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_random() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)random(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_drand48() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)drand48(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_erand48() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned short s[3] = {0}; + unsigned char k = (unsigned char)erand48(s); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_lrand48() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)lrand48(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_nrand48() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned short s[3] = {0}; + unsigned char k = (unsigned char)nrand48(s); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_mrand48() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)mrand48(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_jrand48() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned short s[3] = {0}; + unsigned char k = (unsigned char)jrand48(s); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_rand_r() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned int seed = 0; + unsigned char k = (unsigned char)rand_r(&seed); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +// =========================================================================== +// Insecure output reaching an IV (nonce) sink and a raw MAC key sink (positive). +// =========================================================================== + +void test_rand_iv() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char key[16] = {0}; + unsigned char iv = (unsigned char)rand(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, key, &iv); // $ Alert + finish(ctx); +} + +void test_rand_mac_key() { + unsigned char k = (unsigned char)rand(); // $ Source + EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, 0, &k, 16); // $ Alert +} + +// =========================================================================== +// Insecure C++ engines feeding a cipher key (positive). +// =========================================================================== + +void test_mt19937() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::mt19937 gen; + unsigned char k = (unsigned char)gen(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_minstd_rand() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::minstd_rand gen; + unsigned char k = (unsigned char)gen(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_ranlux24() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::ranlux24 gen; + unsigned char k = (unsigned char)gen(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_knuth_b() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::knuth_b gen; + unsigned char k = (unsigned char)gen(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_independent_bits_engine() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::independent_bits_engine gen; + unsigned char k = (unsigned char)gen(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_default_random_engine() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::default_random_engine gen; + unsigned char k = (unsigned char)gen(); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +// =========================================================================== +// RAND_pseudo_bytes is insecure (positive); RAND_bytes is secure (negative). +// =========================================================================== + +void test_rand_pseudo_bytes() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + RAND_pseudo_bytes(&k, 1); // $ Source + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); // $ Alert + finish(ctx); +} + +void test_rand_bytes() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + RAND_bytes(&k, 1); // GOOD: cryptographically secure + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +// =========================================================================== +// Secure generators must NOT be flagged (negative). +// =========================================================================== + +void test_arc4random() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)arc4random(); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_arc4random_uniform() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = (unsigned char)arc4random_uniform(256); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_arc4random_buf() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + arc4random_buf(&k, 1); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_getrandom() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + getrandom(&k, 1, 0); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_getentropy() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + getentropy(&k, 1); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_rtlgenrandom() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + RtlGenRandom(&k, 1); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_cryptgenrandom() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + CryptGenRandom(0, 1, &k); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_bcryptgenrandom() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + unsigned char k = 0; + BCryptGenRandom(0, &k, 1, 0); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} + +void test_random_device() { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char iv[16] = {0}; + std::random_device rd; + unsigned char k = (unsigned char)rd(); // GOOD + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), 0, &k, iv); + finish(ctx); +} diff --git a/shared/quantum/change-notes/2026-08-26-random-security-classification.md b/shared/quantum/change-notes/2026-08-26-random-security-classification.md new file mode 100644 index 000000000000..47334d374134 --- /dev/null +++ b/shared/quantum/change-notes/2026-08-26-random-security-classification.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added a `isCryptographicallySecure()` predicate to `Crypto::RandomNumberGenerationInstance`, allowing models of random number generators to record whether the generator is cryptographically secure. It defaults to holding for no generator, so existing subclasses are unaffected until they classify themselves. diff --git a/shared/quantum/codeql/quantum/experimental/Model.qll b/shared/quantum/codeql/quantum/experimental/Model.qll index f5ab5190e1ff..37e2a4457f82 100644 --- a/shared/quantum/codeql/quantum/experimental/Model.qll +++ b/shared/quantum/codeql/quantum/experimental/Model.qll @@ -457,6 +457,16 @@ module CryptographyBase Input> { */ abstract class RandomNumberGenerationInstance extends OutputArtifactInstance { abstract string getGeneratorName(); + + /** + * Holds if this generator is cryptographically secure, i.e., suitable for + * generating security-sensitive values such as keys, IVs, nonces, and salts. + * + * Defaults to holding for no generator (i.e., "not known to be secure"), so that + * a generator is only treated as secure when a model explicitly classifies it as + * such. Models of insecure generators (e.g. libc `rand`) leave this as-is. + */ + predicate isCryptographicallySecure() { none() } } /**