Properly share ConceptsShared.qll

This commit is contained in:
Jeroen Ketema
2025-07-14 16:12:30 +02:00
parent f07d8ee493
commit cbde11ddc9
37 changed files with 373 additions and 903 deletions

View File

@@ -7,10 +7,14 @@
private import codeql.ruby.AST
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplSpecific
private import codeql.ruby.Frameworks
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Regexp as RE
private import codeql.concepts.ConceptsShared
private module ConceptsShared = ConceptsMake<Location, RubyDataFlow>;
/**
* A data-flow node that constructs a SQL statement.
@@ -682,7 +686,7 @@ module Http {
/** Provides classes for modeling HTTP clients. */
module Client {
import codeql.ruby.internal.ConceptsShared::Http::Client as SC
import ConceptsShared::Http::Client as SC
/**
* A method call that makes an outgoing HTTP request.
@@ -1041,7 +1045,7 @@ module Cryptography {
// modify that part of the shared concept... which means we have to explicitly
// re-export everything else.
// Using SC shorthand for "Shared Cryptography"
import codeql.ruby.internal.ConceptsShared::Cryptography as SC
import ConceptsShared::Cryptography as SC
class CryptographicAlgorithm = SC::CryptographicAlgorithm;

View File

@@ -183,8 +183,7 @@ module ActiveResource {
CollectionSource getCollection() { result = collection }
}
private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range,
ModelClassMethodCall
private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range instanceof ModelClassMethodCall
{
ModelClassMethodCallAsHttpRequest() {
this.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"]
@@ -195,20 +194,19 @@ module ActiveResource {
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
this.getModelClass().disablesCertificateValidation(disablingNode) and
super.getModelClass().disablesCertificateValidation(disablingNode) and
// TODO: highlight real argument origin
argumentOrigin = disablingNode
}
override DataFlow::Node getAUrlPart() {
result = this.getModelClass().getASiteAssignment().getAUrlPart()
result = super.getModelClass().getASiteAssignment().getAUrlPart()
}
override DataFlow::Node getResponseBody() { result = this }
}
private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range,
ModelInstanceMethodCall
private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range instanceof ModelInstanceMethodCall
{
ModelInstanceMethodCallAsHttpRequest() {
this.getMethodName() =
@@ -223,13 +221,13 @@ module ActiveResource {
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
this.getModelClass().disablesCertificateValidation(disablingNode) and
super.getModelClass().disablesCertificateValidation(disablingNode) and
// TODO: highlight real argument origin
argumentOrigin = disablingNode
}
override DataFlow::Node getAUrlPart() {
result = this.getModelClass().getASiteAssignment().getAUrlPart()
result = super.getModelClass().getASiteAssignment().getAUrlPart()
}
override DataFlow::Node getResponseBody() { result = this }

View File

@@ -23,7 +23,7 @@ private import codeql.ruby.DataFlow
* TODO: pipelining, streaming responses
* https://github.com/excon/excon/blob/master/README.md
*/
class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class ExconHttpRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
API::Node connectionNode;
DataFlow::Node connectionUse;
@@ -54,9 +54,9 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
// For one-off requests, the URL is in the first argument of the request method call.
// For connection re-use, the URL is split between the first argument of the `new` call
// and the `path` keyword argument of the request method call.
result = this.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
result = super.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
or
result = this.getKeywordArgument("path")
result = super.getKeywordArgument("path")
or
result = connectionUse.(DataFlow::CallNode).getArgument(0)
}

View File

@@ -22,7 +22,7 @@ private import codeql.ruby.DataFlow
* connection.get("/").body
* ```
*/
class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class FaradayHttpRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
API::Node connectionNode;
DataFlow::Node connectionUse;
@@ -47,7 +47,7 @@ class FaradayHttpRequest extends Http::Client::Request::Range, DataFlow::CallNod
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
override DataFlow::Node getAUrlPart() {
result = this.getArgument(0) or
result = super.getArgument(0) or
result = connectionUse.(DataFlow::CallNode).getArgument(0) or
result = connectionUse.(DataFlow::CallNode).getKeywordArgument("url")
}

View File

@@ -14,7 +14,7 @@ private import codeql.ruby.DataFlow
* HTTPClient.get_content("http://example.com")
* ```
*/
class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class HttpClientRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
API::Node connectionNode;
string method;
@@ -34,7 +34,7 @@ class HttpClientRequest extends Http::Client::Request::Range, DataFlow::CallNode
]
}
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
override DataFlow::Node getAUrlPart() { result = super.getArgument(0) }
override DataFlow::Node getResponseBody() {
// The `get_content` and `post_content` methods return the response body as

View File

@@ -23,7 +23,7 @@ private import codeql.ruby.DataFlow
* MyClass.new("http://example.com")
* ```
*/
class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class HttpartyRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
HttpartyRequest() {
@@ -33,7 +33,7 @@ class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
}
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
override DataFlow::Node getAUrlPart() { result = super.getArgument(0) }
override DataFlow::Node getResponseBody() {
// If HTTParty can recognise the response type, it will parse and return it
@@ -49,7 +49,7 @@ class HttpartyRequest extends Http::Client::Request::Range, DataFlow::CallNode {
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgumentIncludeHashArgument(["verify", "verify_peer"])
result = super.getKeywordArgumentIncludeHashArgument(["verify", "verify_peer"])
}
cached

View File

@@ -18,7 +18,7 @@ private import codeql.ruby.DataFlow
* response = req.get("/")
* ```
*/
class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class NetHttpRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
private DataFlow::CallNode request;
private API::Node requestNode;
private boolean returnsResponseBody;

View File

@@ -18,7 +18,7 @@ private import codeql.ruby.frameworks.Core
* URI.parse("http://example.com").open.read
* ```
*/
class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class OpenUriRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
OpenUriRequest() {
@@ -30,7 +30,7 @@ class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
this = requestNode.asSource()
}
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
override DataFlow::Node getAUrlPart() { result = super.getArgument(0) }
override DataFlow::Node getResponseBody() {
result = requestNode.getAMethodCall(["read", "readlines"])
@@ -38,7 +38,7 @@ class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgumentIncludeHashArgument("ssl_verify_mode")
result = super.getKeywordArgumentIncludeHashArgument("ssl_verify_mode")
}
cached
@@ -60,11 +60,10 @@ class OpenUriRequest extends Http::Client::Request::Range, DataFlow::CallNode {
* Kernel.open("http://example.com").read
* ```
*/
class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::CallNode instanceof KernelMethodCall
{
class OpenUriKernelOpenRequest extends Http::Client::Request::Range instanceof KernelMethodCall {
OpenUriKernelOpenRequest() { this.getMethodName() = "open" }
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
override DataFlow::Node getAUrlPart() { result = super.getArgument(0) }
override DataFlow::CallNode getResponseBody() {
result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
@@ -73,14 +72,14 @@ class OpenUriKernelOpenRequest extends Http::Client::Request::Range, DataFlow::C
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgument("ssl_verify_mode")
result = super.getKeywordArgument("ssl_verify_mode")
or
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key
|
// can't flow to argument 0, since that's the URL
optionsNode.flowsTo(this.getArgument(any(int i | i > 0))) and
optionsNode.flowsTo(super.getArgument(any(int i | i > 0))) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource().asExpr().getConstantValue().isStringlikeValue("ssl_verify_mode") and

View File

@@ -16,7 +16,7 @@ private import codeql.ruby.DataFlow
* RestClient::Request.execute(url: "http://example.com").body
* ```
*/
class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class RestClientHttpRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
API::Node connectionNode;
@@ -37,9 +37,9 @@ class RestClientHttpRequest extends Http::Client::Request::Range, DataFlow::Call
}
override DataFlow::Node getAUrlPart() {
result = this.getKeywordArgument("url")
result = super.getKeywordArgument("url")
or
result = this.getArgument(0) and
result = super.getArgument(0) and
// this rules out the alternative above
not result.asExpr().getExpr() instanceof Pair
}

View File

@@ -14,7 +14,7 @@ private import codeql.ruby.DataFlow
* Typhoeus.get("http://example.com").body
* ```
*/
class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
class TyphoeusHttpRequest extends Http::Client::Request::Range instanceof DataFlow::CallNode {
API::Node requestNode;
boolean directResponse;
@@ -31,7 +31,7 @@ class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNo
)
}
override DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
override DataFlow::Node getAUrlPart() { result = super.getArgument(0) }
override DataFlow::Node getResponseBody() {
directResponse = true and
@@ -43,7 +43,7 @@ class TyphoeusHttpRequest extends Http::Client::Request::Range, DataFlow::CallNo
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgumentIncludeHashArgument("ssl_verifypeer")
result = super.getKeywordArgumentIncludeHashArgument("ssl_verifypeer")
}
cached

View File

@@ -1,7 +0,0 @@
/**
* This file contains imports required for the Ruby version of `ConceptsShared.qll`.
* Since they are language-specific, they can't be placed directly in that file, as it is shared between languages.
*/
import codeql.ruby.DataFlow
import codeql.concepts.CryptoAlgorithms as CryptoAlgorithms

View File

@@ -1,181 +0,0 @@
/**
* Provides Concepts which are shared across languages.
*
* Each language has a language specific `Concepts.qll` file that can import the
* shared concepts from this file. A language can either re-export the concept directly,
* or can add additional member-predicates that are needed for that language.
*
* Moving forward, `Concepts.qll` will be the staging ground for brand new concepts from
* each language, but we will maintain a discipline of moving those concepts to
* `ConceptsShared.qll` ASAP.
*/
private import ConceptsImports
/**
* Provides models for cryptographic concepts.
*
* Note: The `CryptographicAlgorithm` class currently doesn't take weak keys into
* consideration for the `isWeak` member predicate. So RSA is always considered
* secure, although using a low number of bits will actually make it insecure. We plan
* to improve our libraries in the future to more precisely capture this aspect.
*/
module Cryptography {
class CryptographicAlgorithm = CryptoAlgorithms::CryptographicAlgorithm;
class EncryptionAlgorithm = CryptoAlgorithms::EncryptionAlgorithm;
class HashingAlgorithm = CryptoAlgorithms::HashingAlgorithm;
class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm;
/**
* A data flow node that is an application of a cryptographic algorithm. For example,
* encryption, decryption, signature-validation.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `CryptographicOperation::Range` instead.
*/
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
/** Gets the data flow node where the cryptographic algorithm used in this operation is configured. */
DataFlow::Node getInitialization() { result = super.getInitialization() }
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
DataFlow::Node getAnInput() { result = super.getAnInput() }
/**
* Gets the block mode used to perform this cryptographic operation.
*
* This predicate is only expected to have a result if two conditions hold:
* 1. The operation is an encryption operation, i.e. the algorithm used is an `EncryptionAlgorithm`, and
* 2. The algorithm used is a block cipher (not a stream cipher).
*
* If either of these conditions do not hold, then this predicate should have no result.
*/
BlockMode getBlockMode() { result = super.getBlockMode() }
}
/** Provides classes for modeling new applications of a cryptographic algorithms. */
module CryptographicOperation {
/**
* A data flow node that is an application of a cryptographic algorithm. For example,
* encryption, decryption, signature-validation.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `CryptographicOperation` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the data flow node where the cryptographic algorithm used in this operation is configured. */
abstract DataFlow::Node getInitialization();
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
abstract CryptographicAlgorithm getAlgorithm();
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
abstract DataFlow::Node getAnInput();
/**
* Gets the block mode used to perform this cryptographic operation.
*
* This predicate is only expected to have a result if two conditions hold:
* 1. The operation is an encryption operation, i.e. the algorithm used is an `EncryptionAlgorithm`, and
* 2. The algorithm used is a block cipher (not a stream cipher).
*
* If either of these conditions do not hold, then this predicate should have no result.
*/
abstract BlockMode getBlockMode();
}
}
/**
* A cryptographic block cipher mode of operation. This can be used to encrypt
* data of arbitrary length using a block encryption algorithm.
*/
class BlockMode extends string {
BlockMode() {
this =
[
"ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR", "OPENPGP",
"XTS", // https://csrc.nist.gov/publications/detail/sp/800-38e/final
"EAX" // https://en.wikipedia.org/wiki/EAX_mode
]
}
/** Holds if this block mode is considered to be insecure. */
predicate isWeak() { this = "ECB" }
/** Holds if the given string appears to match this block mode. */
bindingset[s]
predicate matchesString(string s) { s.toUpperCase().matches("%" + this + "%") }
}
}
/** Provides classes for modeling HTTP-related APIs. */
module Http {
/** Provides classes for modeling HTTP clients. */
module Client {
/**
* A data flow node that makes an outgoing HTTP request.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `Http::Client::Request::Range` instead.
*/
class Request extends DataFlow::Node instanceof Request::Range {
/**
* Gets a data flow node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
DataFlow::Node getAUrlPart() { result = super.getAUrlPart() }
/** Gets a string that identifies the framework used for this request. */
string getFramework() { result = super.getFramework() }
/**
* Holds if this request is made using a mode that disables SSL/TLS
* certificate validation, where `disablingNode` represents the point at
* which the validation was disabled, and `argumentOrigin` represents the origin
* of the argument that disabled the validation (which could be the same node as
* `disablingNode`).
*/
predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
super.disablesCertificateValidation(disablingNode, argumentOrigin)
}
}
/** Provides a class for modeling new HTTP requests. */
module Request {
/**
* A data flow node that makes an outgoing HTTP request.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `Http::Client::Request` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets a data flow node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
abstract DataFlow::Node getAUrlPart();
/** Gets a string that identifies the framework used for this request. */
abstract string getFramework();
/**
* Holds if this request is made using a mode that disables SSL/TLS
* certificate validation, where `disablingNode` represents the point at
* which the validation was disabled, and `argumentOrigin` represents the origin
* of the argument that disabled the validation (which could be the same node as
* `disablingNode`).
*/
abstract predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
);
}
}
}
}

View File

@@ -544,8 +544,7 @@ private class CipherNode extends DataFlow::Node {
}
/** An operation using the OpenSSL library that uses a cipher. */
private class CipherOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallNode
private class CipherOperation extends Cryptography::CryptographicOperation::Range instanceof DataFlow::CallNode
{
private CipherNode cipherNode;
@@ -564,8 +563,8 @@ private class CipherOperation extends Cryptography::CryptographicOperation::Rang
}
override DataFlow::Node getAnInput() {
this.getMethodName() = "update" and
result = this.getArgument(0)
super.getMethodName() = "update" and
result = super.getArgument(0)
}
override Cryptography::BlockMode getBlockMode() {