mirror of
https://github.com/github/codeql.git
synced 2026-07-31 07:22:56 +02:00
Flips the Python dataflow trunk from the legacy CFG (semmle/python/Flow.qll) and legacy ESSA SSA (semmle/python/essa/*) to the new shared CFG facade (semmle.python.controlflow.internal.Cfg) and the new SSA adapter (semmle.python.dataflow.new.internal.SsaImpl), both introduced additively in the preceding PRs in this stack. This is the trunk-flip equivalent of the original draft PR #21894 (kept around as documentation), rebased on top of the four preparatory PRs: P1: Remove AstNode.getAFlowNode() and rewrite callers (#21919). P2: Qualify Flow.qll's AST references with Py:: prefix (#21920). P3: Add new shared-CFG-backed control flow graph (#21921). P4: Add new shared-SSA-backed SSA adapter (#21923). The Python dataflow library (semmle/python/dataflow/new/) now imports the new CFG facade and SSA adapter. All CFG-typed predicates (ControlFlowNode, CallNode, BasicBlock, NameNode, AttrNode, ...) are qualified with the Cfg:: prefix; SSA references switch from EssaVariable/EssaDefinition to SsaImpl::Definition/SourceVariable. GuardNode is redesigned to use the new CFG's outcome-node model (isAfterTrue / isAfterFalse) instead of the legacy ConditionBlock + flipped indirection. Only BarrierGuard<...> is preserved as public API. Framework files (Bottle, FastApi, Django, Tornado, Pyramid, Stdlib, ...) are updated to take CFG nodes from the new facade. A handful of dataflow consistency tweaks for the new CFG: - Augmented-assignment targets are treated as both load and store. - 'from X import *' produces uncertain SSA writes for unknown names. - CFG nodes are canonicalised so dataflow does not see equivalent pre/post-order pairs as distinct nodes. Two AST tweaks for the new CFG: - AstNodeImpl: omit PEP 695 type-parameter names from FunctionDefExpr / ClassDefExpr children. - ImportResolution: drop the legacy essa import. Test churn (~175 files): reblessed library- and query-test .expected files reflect slightly different CFG granularity, different toString output, and a handful of true alert deltas in security queries. Verification: all 367 lib + src + consistency-queries compile clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
220 lines
7.4 KiB
Plaintext
220 lines
7.4 KiB
Plaintext
/**
|
|
* Provides modeling of SSL/TLS functionality of the `ssl` module from the standard library.
|
|
* See https://docs.python.org/3.9/library/ssl.html
|
|
*/
|
|
|
|
private import python
|
|
private import semmle.python.ApiGraphs
|
|
private import semmle.python.controlflow.internal.Cfg as Cfg
|
|
import TlsLibraryModel
|
|
|
|
class SslContextCreation extends ContextCreation, DataFlow::CallCfgNode {
|
|
SslContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
|
|
|
|
override ProtocolVersion getProtocol() {
|
|
exists(DataFlow::Node protocolArg, Ssl ssl |
|
|
protocolArg in [this.getArg(0), this.getArgByName("protocol")]
|
|
|
|
|
protocolArg = ssl.specific_version(result).getAValueReachableFromSource()
|
|
or
|
|
protocolArg = ssl.unspecific_version().getAValueReachableFromSource() and
|
|
// see https://docs.python.org/3/library/ssl.html#id7
|
|
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
|
)
|
|
or
|
|
not exists(this.getArg(_)) and
|
|
not exists(this.getArgByName(_)) and
|
|
// see https://docs.python.org/3/library/ssl.html#id7
|
|
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
|
}
|
|
}
|
|
|
|
class SslDefaultContextCreation extends ContextCreation {
|
|
SslDefaultContextCreation() {
|
|
this = API::moduleImport("ssl").getMember("create_default_context").getACall()
|
|
}
|
|
|
|
// Allowed insecure versions are "TLSv1" and "TLSv1_1"
|
|
// see https://docs.python.org/3/library/ssl.html#context-creation
|
|
override ProtocolVersion getProtocol() { result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] }
|
|
}
|
|
|
|
/** Gets a reference to an `ssl.Context` instance. */
|
|
API::Node sslContextInstance() {
|
|
result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getReturn()
|
|
}
|
|
|
|
class WrapSocketCall extends ConnectionCreation, DataFlow::MethodCallNode {
|
|
WrapSocketCall() { this = sslContextInstance().getMember("wrap_socket").getACall() }
|
|
|
|
override DataFlow::Node getContext() { result = this.getObject() }
|
|
}
|
|
|
|
class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode {
|
|
ProtocolVersion restriction;
|
|
|
|
OptionsAugOr() {
|
|
exists(AugAssign aa, Cfg::AttrNode attr, Expr flag |
|
|
aa.getOperation().getOp() instanceof BitOr and
|
|
aa.getTarget() = attr.getNode() and
|
|
attr.getName() = "options" and
|
|
attr.getObject() = node and
|
|
flag =
|
|
API::moduleImport("ssl")
|
|
.getMember("OP_NO_" + restriction)
|
|
.getAValueReachableFromSource()
|
|
.asExpr() and
|
|
(
|
|
aa.getValue() = flag
|
|
or
|
|
impliesBitSet(aa.getValue(), flag, false, false)
|
|
)
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getContext() { result = this }
|
|
|
|
override ProtocolVersion getRestriction() { result = restriction }
|
|
}
|
|
|
|
class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode {
|
|
ProtocolVersion restriction;
|
|
|
|
OptionsAugAndNot() {
|
|
exists(AugAssign aa, Cfg::AttrNode attr, Expr flag, UnaryExpr notFlag |
|
|
aa.getOperation().getOp() instanceof BitAnd and
|
|
aa.getTarget() = attr.getNode() and
|
|
attr.getName() = "options" and
|
|
attr.getObject() = node and
|
|
notFlag.getOp() instanceof Invert and
|
|
notFlag.getOperand() = flag and
|
|
flag =
|
|
API::moduleImport("ssl")
|
|
.getMember("OP_NO_" + restriction)
|
|
.getAValueReachableFromSource()
|
|
.asExpr() and
|
|
(
|
|
aa.getValue() = notFlag
|
|
or
|
|
impliesBitSet(aa.getValue(), notFlag, true, true)
|
|
)
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getContext() { result = this }
|
|
|
|
override ProtocolVersion getUnrestriction() { result = restriction }
|
|
}
|
|
|
|
/**
|
|
* Holds if
|
|
* for every bit, _b_:
|
|
* `wholeHasBitSet` represents that _b_ is set in `whole`
|
|
* implies
|
|
* `partHasBitSet` represents that _b_ is set in `part`
|
|
*
|
|
* As an example take `whole` = `part1 & part2`. Then
|
|
* `impliesBitSet(whole, part1, true, true)` holds
|
|
* because for any bit in `whole`, if that bit is set it must also be set in `part1`.
|
|
*
|
|
* Similarly for `whole` = `part1 | part2`. Here
|
|
* `impliesBitSet(whole, part1, false, false)` holds
|
|
* because for any bit in `whole`, if that bit is not set, it cannot be set in `part1`.
|
|
*/
|
|
predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, boolean wholeHasBitSet) {
|
|
whole.getOp() instanceof BitAnd and
|
|
(
|
|
wholeHasBitSet = true and partHasBitSet = true and part in [whole.getLeft(), whole.getRight()]
|
|
or
|
|
wholeHasBitSet = true and
|
|
impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
|
|
)
|
|
or
|
|
whole.getOp() instanceof BitOr and
|
|
(
|
|
wholeHasBitSet = false and partHasBitSet = false and part in [whole.getLeft(), whole.getRight()]
|
|
or
|
|
wholeHasBitSet = false and
|
|
impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
|
|
)
|
|
}
|
|
|
|
class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, DataFlow::CfgNode {
|
|
ProtocolVersion restriction;
|
|
|
|
ContextSetVersion() {
|
|
exists(DataFlow::AttrWrite aw |
|
|
this = aw.getObject() and
|
|
aw.getAttributeName() = "minimum_version" and
|
|
aw.getValue() =
|
|
API::moduleImport("ssl")
|
|
.getMember("TLSVersion")
|
|
.getMember(restriction)
|
|
.getAValueReachableFromSource()
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getContext() { result = this }
|
|
|
|
override ProtocolVersion getRestriction() { result.lessThan(restriction) }
|
|
|
|
override ProtocolVersion getUnrestriction() {
|
|
restriction = result or restriction.lessThan(result)
|
|
}
|
|
}
|
|
|
|
// class UnspecificSslContextCreation extends SslContextCreation, UnspecificContextCreation {
|
|
// // UnspecificSslContextCreation() { library instanceof Ssl }
|
|
// override ProtocolVersion getProtocol() {
|
|
// result = UnspecificContextCreation.super.getProtocol() and
|
|
// // These are turned off by default since Python 3.6
|
|
// // see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext
|
|
// not result in ["SSLv2", "SSLv3"]
|
|
// }
|
|
// }
|
|
// class UnspecificSslDefaultContextCreation extends SslDefaultContextCreation {
|
|
// // override DataFlow::Node getContext() { result = this }
|
|
// // see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
|
|
// override ProtocolVersion getProtocol() { result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] }
|
|
// }
|
|
class Ssl extends TlsLibrary {
|
|
Ssl() { this = "ssl" }
|
|
|
|
override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
|
|
|
|
override string unspecific_version_name() {
|
|
result = "PROTOCOL_SSLv23"
|
|
or
|
|
result = "PROTOCOL_TLS" + ["", "_CLIENT", "_SERVER"]
|
|
}
|
|
|
|
override API::Node version_constants() { result = API::moduleImport("ssl") }
|
|
|
|
override ContextCreation default_context_creation() {
|
|
result instanceof SslDefaultContextCreation
|
|
}
|
|
|
|
override ContextCreation specific_context_creation() { result instanceof SslContextCreation }
|
|
|
|
override DataFlow::CallCfgNode insecure_connection_creation(ProtocolVersion version) {
|
|
result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
|
|
this.specific_version(version).getAValueReachableFromSource() =
|
|
result.getArgByName("ssl_version") and
|
|
version.isInsecure()
|
|
}
|
|
|
|
override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
|
|
|
|
override ProtocolRestriction protocol_restriction() {
|
|
result instanceof OptionsAugOr
|
|
or
|
|
result instanceof ContextSetVersion
|
|
}
|
|
|
|
override ProtocolUnrestriction protocol_unrestriction() {
|
|
result instanceof OptionsAugAndNot
|
|
or
|
|
result instanceof ContextSetVersion
|
|
}
|
|
}
|