From 460eddd7812e2b50592d0679c549ad5f207921d9 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 16 Aug 2022 08:55:49 +0200
Subject: [PATCH 001/465] add ql/override-any
---
.../performance/VarUnusedInDisjunctQuery.qll | 27 +++++++++++++
.../performance/VarUnusedInDisjunct.ql | 27 +------------
ql/ql/src/queries/style/OverrideAny.ql | 40 +++++++++++++++++++
3 files changed, 68 insertions(+), 26 deletions(-)
create mode 100644 ql/ql/src/codeql_ql/performance/VarUnusedInDisjunctQuery.qll
create mode 100644 ql/ql/src/queries/style/OverrideAny.ql
diff --git a/ql/ql/src/codeql_ql/performance/VarUnusedInDisjunctQuery.qll b/ql/ql/src/codeql_ql/performance/VarUnusedInDisjunctQuery.qll
new file mode 100644
index 00000000000..503ad6db3ad
--- /dev/null
+++ b/ql/ql/src/codeql_ql/performance/VarUnusedInDisjunctQuery.qll
@@ -0,0 +1,27 @@
+import ql
+
+/**
+ * Holds if we assume `t` is a small type, and
+ * variables of this type are therefore not an issue in cartesian products.
+ */
+predicate isSmallType(Type t) {
+ t.getName() = "string" // DataFlow::Configuration and the like
+ or
+ exists(NewType newType | newType = t.getDeclaration() |
+ forex(NewTypeBranch branch | branch = newType.getABranch() | branch.getArity() = 0)
+ )
+ or
+ t.getName() = "boolean"
+ or
+ exists(NewType newType | newType = t.getDeclaration() |
+ forex(NewTypeBranch branch | branch = newType.getABranch() |
+ isSmallType(branch.getReturnType())
+ )
+ )
+ or
+ exists(NewTypeBranch branch | t = branch.getReturnType() |
+ forall(Type param | param = branch.getParameterType(_) | isSmallType(param))
+ )
+ or
+ isSmallType(t.getASuperType())
+}
diff --git a/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql b/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql
index c26b47554fe..2d85b872153 100644
--- a/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql
+++ b/ql/ql/src/queries/performance/VarUnusedInDisjunct.ql
@@ -10,6 +10,7 @@
*/
import ql
+import codeql_ql.performance.VarUnusedInDisjunctQuery
/**
* Holds if `node` bind `var` in a (transitive) child node.
@@ -48,32 +49,6 @@ predicate alwaysBindsVar(VarDef var, AstNode node) {
exists(IfFormula ifForm | ifForm = node | alwaysBindsVar(var, ifForm.getCondition()))
}
-/**
- * Holds if we assume `t` is a small type, and
- * variables of this type are therefore not an issue in cartesian products.
- */
-predicate isSmallType(Type t) {
- t.getName() = "string" // DataFlow::Configuration and the like
- or
- exists(NewType newType | newType = t.getDeclaration() |
- forex(NewTypeBranch branch | branch = newType.getABranch() | branch.getArity() = 0)
- )
- or
- t.getName() = "boolean"
- or
- exists(NewType newType | newType = t.getDeclaration() |
- forex(NewTypeBranch branch | branch = newType.getABranch() |
- isSmallType(branch.getReturnType())
- )
- )
- or
- exists(NewTypeBranch branch | t = branch.getReturnType() |
- forall(Type param | param = branch.getParameterType(_) | isSmallType(param))
- )
- or
- isSmallType(t.getASuperType())
-}
-
/**
* Holds if `pred` is inlined.
*/
diff --git a/ql/ql/src/queries/style/OverrideAny.ql b/ql/ql/src/queries/style/OverrideAny.ql
new file mode 100644
index 00000000000..0c7d82fd3c8
--- /dev/null
+++ b/ql/ql/src/queries/style/OverrideAny.ql
@@ -0,0 +1,40 @@
+/**
+ * @name Override with unmentioned parameter
+ * @description A predicate that overrides the default behavior but doesn't mention a parameter is suspicious.
+ * @kind problem
+ * @problem.severity warning
+ * @id ql/override-any
+ * @precision very-high
+ */
+
+import ql
+import codeql_ql.performance.VarUnusedInDisjunctQuery
+
+AstNode param(Predicate pred, string name, Type t) {
+ result = pred.getParameter(_) and
+ result.(VarDecl).getName() = name and
+ result.(VarDecl).getType() = t
+ or
+ result = pred.getReturnTypeExpr() and
+ name = "result" and
+ t = pred.getReturnType()
+}
+
+predicate hasAccess(Predicate pred, string name) {
+ exists(param(pred, name, _).(VarDecl).getAnAccess())
+ or
+ name = "result" and
+ exists(param(pred, name, _)) and
+ exists(ResultAccess res | res.getEnclosingPredicate() = pred)
+}
+
+from Predicate pred, AstNode param, string name, Type paramType
+where
+ pred.hasAnnotation("override") and
+ param = param(pred, name, paramType) and
+ not hasAccess(pred, name) and
+ not pred.getBody() instanceof NoneCall and
+ exists(pred.getBody()) and
+ not isSmallType(pred.getParent().(Class).getType()) and
+ not isSmallType(paramType)
+select pred, "Override predicate doesn't mention $@.", param, name
From 843fce4bcd5b83f60109f8b72d9a6d505e4f493c Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 6 Sep 2022 09:12:18 +0200
Subject: [PATCH 002/465] expand localFieldStep to use access-paths, and build
access-paths in more cases
---
.../semmle/javascript/GlobalAccessPaths.qll | 96 ++++++++++++++++
.../semmle/javascript/dataflow/DataFlow.qll | 4 +-
.../UnsafeShellCommandConstruction.expected | 10 ++
.../query-tests/Security/CWE-078/lib/lib.js | 2 +-
.../UnsafeCodeConstruction.expected | 80 +++++++++++++
.../CWE-094/CodeInjection/lib/index.js | 106 ++++++++++++++++++
.../PrototypePollutingAssignment.expected | 2 +
7 files changed, 297 insertions(+), 3 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
index e617f597870..421d5a88860 100644
--- a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
+++ b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
@@ -233,6 +233,8 @@ module AccessPath {
baseName = fromReference(write.getBase(), root)
or
baseName = fromRhs(write.getBase(), root)
+ or
+ baseName = fromRhs(GetLaterAccess::getLaterBaseAccess(write), root)
)
or
exists(GlobalVariable var |
@@ -266,6 +268,100 @@ module AccessPath {
)
}
+ /** A module for computing an access to a variable that happens after a write to that same variable */
+ private module GetLaterAccess {
+ /**
+ * Gets an access to a variable that is written to in `write`, where the access is after the write.
+ *
+ * This allows `fromRhs` to compute an access path for e.g. the below example:
+ * ```JavaScript
+ * function Template(text, opts) {
+ * opts = opts || {};
+ * var options = {};
+ * options.varName = taint;
+ * this.opts = options; // this.opts.varName is now available
+ * }
+ * ```
+ */
+ pragma[noopt]
+ DataFlow::Node getLaterBaseAccess(DataFlow::PropWrite write) {
+ exists(
+ ControlFlowNode writeNode, BindingPattern access, VarRef otherAccess, Variable variable,
+ StmtContainer container
+ |
+ access = getBaseVar(write) and
+ writeNode = write.getWriteNode() and
+ access = getAnAccessInContainer(variable, container, true) and
+ variable = getARelevantVariable() and // manual magic
+ otherAccess = getAnAccessInContainer(variable, container, false) and
+ access != otherAccess and
+ result.asExpr() = otherAccess
+ |
+ exists(BasicBlock bb, int i, int j |
+ bb.getNode(i) = writeNode and
+ bb.getNode(j) = otherAccess and
+ i < j
+ )
+ or
+ otherAccess.getBasicBlock() = getASuccessorBBThatReadsVar(write) // more manual magic - outlined into a helper predicate.
+ )
+ }
+
+ /** Gets a variable ref that `write` writes a property to. */
+ VarRef getBaseVar(DataFlow::PropWrite write) {
+ result = write.getBase().asExpr()
+ or
+ exists(Assignment assign |
+ write.getBase().asExpr() = assign.getRhs() and
+ result = assign.getLhs()
+ )
+ or
+ exists(VariableDeclarator decl |
+ write.getBase().asExpr() = decl.getInit() and
+ result = decl.getBindingPattern()
+ )
+ }
+
+ /** Gets an access to `var` inside `container` where `usedInWrite` indicates whether the access is the base of a property write. */
+ private VarRef getAnAccessInContainer(Variable var, StmtContainer container, boolean usedInWrite) {
+ result.getVariable() = var and
+ result.getContainer() = container and
+ var.isLocal() and
+ if result = getBaseVar(_) then usedInWrite = true else usedInWrite = false
+ }
+
+ /** Gets a variable that is relevant for the computations in the `GetLaterAccess` module. */
+ private Variable getARelevantVariable() {
+ // The variable might be used where `getLaterBaseAccess()` is called.
+ exists(DataFlow::Node node |
+ exists(fromRhs(node, _)) and
+ node.asExpr().(VarAccess).getVariable() = result
+ ) and
+ // There is a write that writes to the variable.
+ getBaseVar(_).getVariable() = result and
+ // It's local.
+ result.isLocal() and // we skip global variables, because that turns messy quick.
+ // There is both a "write" and "read" in the same container of the variable.
+ exists(StmtContainer container |
+ exists(getAnAccessInContainer(result, container, true)) and // a "write", an access to the variable that is the base of a property reference.
+ exists(getAnAccessInContainer(result, container, false)) // a "read", an access to the variable that is not the base of a property reference.
+ )
+ }
+
+ /** Gets a basic-block that has a read of the variable that is written to by `write`, where the basicblock occurs after `start`. */
+ private ReachableBasicBlock getASuccessorBBThatReadsVar(DataFlow::PropWrite write) {
+ exists(VarAccess baseExpr, Variable var, ControlFlowNode writeNode |
+ baseExpr = getBaseVar(write) and
+ var = baseExpr.getVariable() and
+ var = getARelevantVariable() and
+ writeNode = write.getWriteNode() and
+ writeNode.getBasicBlock().(ReachableBasicBlock).strictlyDominates(result) and
+ // manual magic.
+ result = getAnAccessInContainer(getARelevantVariable(), _, false).getBasicBlock()
+ )
+ }
+ }
+
/**
* Gets a node that refers to the given access path relative to the given `root` node,
* or `root` itself if the access path is empty.
diff --git a/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
index 20366a8f2b5..2404fecbd30 100644
--- a/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
+++ b/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
@@ -1692,10 +1692,10 @@ module DataFlow {
*/
predicate localFieldStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassNode cls, string prop |
- pred = cls.getADirectSuperClass*().getAReceiverNode().getAPropertyWrite(prop).getRhs() or
+ pred = AccessPath::getAnAssignmentTo(cls.getADirectSuperClass*().getAReceiverNode(), prop) or
pred = cls.getInstanceMethod(prop)
|
- succ = cls.getAReceiverNode().getAPropertyRead(prop)
+ succ = AccessPath::getAReferenceTo(cls.getAReceiverNode(), prop)
)
}
diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction.expected b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction.expected
index 1206af38368..9cefca6150c 100644
--- a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction.expected
@@ -170,6 +170,10 @@ nodes
| lib/lib.js:277:23:277:26 | opts |
| lib/lib.js:277:23:277:30 | opts.bla |
| lib/lib.js:277:23:277:30 | opts.bla |
+| lib/lib.js:279:19:279:22 | opts |
+| lib/lib.js:279:19:279:26 | opts.bla |
+| lib/lib.js:281:23:281:35 | this.opts.bla |
+| lib/lib.js:281:23:281:35 | this.opts.bla |
| lib/lib.js:307:39:307:42 | name |
| lib/lib.js:307:39:307:42 | name |
| lib/lib.js:308:23:308:26 | name |
@@ -504,8 +508,13 @@ edges
| lib/lib.js:268:22:268:24 | obj | lib/lib.js:268:22:268:32 | obj.version |
| lib/lib.js:276:8:276:11 | opts | lib/lib.js:277:23:277:26 | opts |
| lib/lib.js:276:8:276:11 | opts | lib/lib.js:277:23:277:26 | opts |
+| lib/lib.js:276:8:276:11 | opts | lib/lib.js:279:19:279:22 | opts |
+| lib/lib.js:276:8:276:11 | opts | lib/lib.js:279:19:279:22 | opts |
| lib/lib.js:277:23:277:26 | opts | lib/lib.js:277:23:277:30 | opts.bla |
| lib/lib.js:277:23:277:26 | opts | lib/lib.js:277:23:277:30 | opts.bla |
+| lib/lib.js:279:19:279:22 | opts | lib/lib.js:279:19:279:26 | opts.bla |
+| lib/lib.js:279:19:279:26 | opts.bla | lib/lib.js:281:23:281:35 | this.opts.bla |
+| lib/lib.js:279:19:279:26 | opts.bla | lib/lib.js:281:23:281:35 | this.opts.bla |
| lib/lib.js:307:39:307:42 | name | lib/lib.js:308:23:308:26 | name |
| lib/lib.js:307:39:307:42 | name | lib/lib.js:308:23:308:26 | name |
| lib/lib.js:307:39:307:42 | name | lib/lib.js:308:23:308:26 | name |
@@ -714,6 +723,7 @@ edges
| lib/lib.js:261:11:261:33 | "rm -rf ... + name | lib/lib.js:257:35:257:38 | name | lib/lib.js:261:30:261:33 | name | $@ based on $@ is later used in $@. | lib/lib.js:261:11:261:33 | "rm -rf ... + name | String concatenation | lib/lib.js:257:35:257:38 | name | library input | lib/lib.js:261:3:261:34 | cp.exec ... + name) | shell command |
| lib/lib.js:268:10:268:32 | "rm -rf ... version | lib/lib.js:267:46:267:48 | obj | lib/lib.js:268:22:268:32 | obj.version | $@ based on $@ is later used in $@. | lib/lib.js:268:10:268:32 | "rm -rf ... version | String concatenation | lib/lib.js:267:46:267:48 | obj | library input | lib/lib.js:268:2:268:33 | cp.exec ... ersion) | shell command |
| lib/lib.js:277:11:277:30 | "rm -rf " + opts.bla | lib/lib.js:276:8:276:11 | opts | lib/lib.js:277:23:277:30 | opts.bla | $@ based on $@ is later used in $@. | lib/lib.js:277:11:277:30 | "rm -rf " + opts.bla | String concatenation | lib/lib.js:276:8:276:11 | opts | library input | lib/lib.js:277:3:277:31 | cp.exec ... ts.bla) | shell command |
+| lib/lib.js:281:11:281:35 | "rm -rf ... pts.bla | lib/lib.js:276:8:276:11 | opts | lib/lib.js:281:23:281:35 | this.opts.bla | $@ based on $@ is later used in $@. | lib/lib.js:281:11:281:35 | "rm -rf ... pts.bla | String concatenation | lib/lib.js:276:8:276:11 | opts | library input | lib/lib.js:281:3:281:36 | cp.exec ... ts.bla) | shell command |
| lib/lib.js:308:11:308:26 | "rm -rf " + name | lib/lib.js:307:39:307:42 | name | lib/lib.js:308:23:308:26 | name | $@ based on $@ is later used in $@. | lib/lib.js:308:11:308:26 | "rm -rf " + name | String concatenation | lib/lib.js:307:39:307:42 | name | library input | lib/lib.js:308:3:308:27 | cp.exec ... + name) | shell command |
| lib/lib.js:315:10:315:25 | "rm -rf " + name | lib/lib.js:314:40:314:43 | name | lib/lib.js:315:22:315:25 | name | $@ based on $@ is later used in $@. | lib/lib.js:315:10:315:25 | "rm -rf " + name | String concatenation | lib/lib.js:314:40:314:43 | name | library input | lib/lib.js:315:2:315:26 | cp.exec ... + name) | shell command |
| lib/lib.js:320:11:320:26 | "rm -rf " + name | lib/lib.js:314:40:314:43 | name | lib/lib.js:320:23:320:26 | name | $@ based on $@ is later used in $@. | lib/lib.js:320:11:320:26 | "rm -rf " + name | String concatenation | lib/lib.js:314:40:314:43 | name | library input | lib/lib.js:320:3:320:27 | cp.exec ... + name) | shell command |
diff --git a/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js b/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js
index d94f430c57f..64b279d7d05 100644
--- a/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js
+++ b/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js
@@ -278,7 +278,7 @@ module.exports.Foo = class Foo {
this.opts = {};
this.opts.bla = opts.bla
- cp.exec("rm -rf " + this.opts.bla); // NOT OK - but FN [INCONSISTENCY]
+ cp.exec("rm -rf " + this.opts.bla); // NOT OK
}
}
diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/UnsafeCodeConstruction.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/UnsafeCodeConstruction.expected
index 49511750e6f..2018b3f45d1 100644
--- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/UnsafeCodeConstruction.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/UnsafeCodeConstruction.expected
@@ -15,6 +15,41 @@ nodes
| lib/index.js:19:26:19:29 | data |
| lib/index.js:22:7:22:10 | data |
| lib/index.js:22:7:22:10 | data |
+| lib/index.js:41:32:41:35 | opts |
+| lib/index.js:41:32:41:35 | opts |
+| lib/index.js:42:3:42:19 | opts |
+| lib/index.js:42:10:42:13 | opts |
+| lib/index.js:42:10:42:19 | opts \|\| {} |
+| lib/index.js:44:21:44:24 | opts |
+| lib/index.js:44:21:44:32 | opts.varName |
+| lib/index.js:51:21:51:32 | opts.varName |
+| lib/index.js:51:21:51:32 | opts.varName |
+| lib/index.js:51:21:51:32 | opts.varName |
+| lib/index.js:86:15:86:19 | taint |
+| lib/index.js:86:15:86:19 | taint |
+| lib/index.js:87:18:87:22 | taint |
+| lib/index.js:89:36:89:40 | taint |
+| lib/index.js:93:32:93:36 | taint |
+| lib/index.js:98:30:98:34 | taint |
+| lib/index.js:103:21:103:47 | this.op ... dOption |
+| lib/index.js:103:21:103:47 | this.op ... dOption |
+| lib/index.js:104:21:104:47 | this.op ... dOption |
+| lib/index.js:104:21:104:47 | this.op ... dOption |
+| lib/index.js:105:21:105:47 | this.op ... dOption |
+| lib/index.js:105:21:105:47 | this.op ... dOption |
+| lib/index.js:106:21:106:30 | this.taint |
+| lib/index.js:106:21:106:30 | this.taint |
+| lib/index.js:112:17:112:21 | taint |
+| lib/index.js:112:17:112:21 | taint |
+| lib/index.js:113:20:113:24 | taint |
+| lib/index.js:121:34:121:38 | taint |
+| lib/index.js:129:32:129:36 | taint |
+| lib/index.js:136:23:136:49 | this.op ... dOption |
+| lib/index.js:136:23:136:49 | this.op ... dOption |
+| lib/index.js:137:23:137:49 | this.op ... dOption |
+| lib/index.js:137:23:137:49 | this.op ... dOption |
+| lib/index.js:138:23:138:32 | this.taint |
+| lib/index.js:138:23:138:32 | this.taint |
edges
| lib/index.js:1:35:1:38 | data | lib/index.js:2:21:2:24 | data |
| lib/index.js:1:35:1:38 | data | lib/index.js:2:21:2:24 | data |
@@ -32,8 +67,53 @@ edges
| lib/index.js:19:26:19:29 | data | lib/index.js:22:7:22:10 | data |
| lib/index.js:19:26:19:29 | data | lib/index.js:22:7:22:10 | data |
| lib/index.js:19:26:19:29 | data | lib/index.js:22:7:22:10 | data |
+| lib/index.js:41:32:41:35 | opts | lib/index.js:42:10:42:13 | opts |
+| lib/index.js:41:32:41:35 | opts | lib/index.js:42:10:42:13 | opts |
+| lib/index.js:42:3:42:19 | opts | lib/index.js:44:21:44:24 | opts |
+| lib/index.js:42:10:42:13 | opts | lib/index.js:42:10:42:19 | opts \|\| {} |
+| lib/index.js:42:10:42:19 | opts \|\| {} | lib/index.js:42:3:42:19 | opts |
+| lib/index.js:44:21:44:24 | opts | lib/index.js:44:21:44:32 | opts.varName |
+| lib/index.js:44:21:44:32 | opts.varName | lib/index.js:51:21:51:32 | opts.varName |
+| lib/index.js:44:21:44:32 | opts.varName | lib/index.js:51:21:51:32 | opts.varName |
+| lib/index.js:44:21:44:32 | opts.varName | lib/index.js:51:21:51:32 | opts.varName |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:87:18:87:22 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:87:18:87:22 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:89:36:89:40 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:89:36:89:40 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:93:32:93:36 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:93:32:93:36 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:98:30:98:34 | taint |
+| lib/index.js:86:15:86:19 | taint | lib/index.js:98:30:98:34 | taint |
+| lib/index.js:87:18:87:22 | taint | lib/index.js:106:21:106:30 | this.taint |
+| lib/index.js:87:18:87:22 | taint | lib/index.js:106:21:106:30 | this.taint |
+| lib/index.js:89:36:89:40 | taint | lib/index.js:103:21:103:47 | this.op ... dOption |
+| lib/index.js:89:36:89:40 | taint | lib/index.js:103:21:103:47 | this.op ... dOption |
+| lib/index.js:93:32:93:36 | taint | lib/index.js:104:21:104:47 | this.op ... dOption |
+| lib/index.js:93:32:93:36 | taint | lib/index.js:104:21:104:47 | this.op ... dOption |
+| lib/index.js:98:30:98:34 | taint | lib/index.js:105:21:105:47 | this.op ... dOption |
+| lib/index.js:98:30:98:34 | taint | lib/index.js:105:21:105:47 | this.op ... dOption |
+| lib/index.js:112:17:112:21 | taint | lib/index.js:113:20:113:24 | taint |
+| lib/index.js:112:17:112:21 | taint | lib/index.js:113:20:113:24 | taint |
+| lib/index.js:112:17:112:21 | taint | lib/index.js:121:34:121:38 | taint |
+| lib/index.js:112:17:112:21 | taint | lib/index.js:121:34:121:38 | taint |
+| lib/index.js:112:17:112:21 | taint | lib/index.js:129:32:129:36 | taint |
+| lib/index.js:112:17:112:21 | taint | lib/index.js:129:32:129:36 | taint |
+| lib/index.js:113:20:113:24 | taint | lib/index.js:138:23:138:32 | this.taint |
+| lib/index.js:113:20:113:24 | taint | lib/index.js:138:23:138:32 | this.taint |
+| lib/index.js:121:34:121:38 | taint | lib/index.js:136:23:136:49 | this.op ... dOption |
+| lib/index.js:121:34:121:38 | taint | lib/index.js:136:23:136:49 | this.op ... dOption |
+| lib/index.js:129:32:129:36 | taint | lib/index.js:137:23:137:49 | this.op ... dOption |
+| lib/index.js:129:32:129:36 | taint | lib/index.js:137:23:137:49 | this.op ... dOption |
#select
| lib/index.js:2:21:2:24 | data | lib/index.js:1:35:1:38 | data | lib/index.js:2:21:2:24 | data | $@ flows to here and is later $@. | lib/index.js:1:35:1:38 | data | Library input | lib/index.js:2:15:2:30 | "(" + data + ")" | interpreted as code |
| lib/index.js:6:26:6:29 | name | lib/index.js:5:35:5:38 | name | lib/index.js:6:26:6:29 | name | $@ flows to here and is later $@. | lib/index.js:5:35:5:38 | name | Library input | lib/index.js:6:17:6:29 | "obj." + name | interpreted as code |
| lib/index.js:14:21:14:24 | data | lib/index.js:13:38:13:41 | data | lib/index.js:14:21:14:24 | data | $@ flows to here and is later $@. | lib/index.js:13:38:13:41 | data | Library input | lib/index.js:14:15:14:30 | "(" + data + ")" | interpreted as code |
| lib/index.js:22:7:22:10 | data | lib/index.js:19:26:19:29 | data | lib/index.js:22:7:22:10 | data | $@ flows to here and is later $@. | lib/index.js:19:26:19:29 | data | Library input | lib/index.js:25:24:25:26 | str | interpreted as code |
+| lib/index.js:51:21:51:32 | opts.varName | lib/index.js:41:32:41:35 | opts | lib/index.js:51:21:51:32 | opts.varName | $@ flows to here and is later $@. | lib/index.js:41:32:41:35 | opts | Library input | lib/index.js:51:10:51:52 | " var ... ing();" | interpreted as code |
+| lib/index.js:103:21:103:47 | this.op ... dOption | lib/index.js:86:15:86:19 | taint | lib/index.js:103:21:103:47 | this.op ... dOption | $@ flows to here and is later $@. | lib/index.js:86:15:86:19 | taint | Library input | lib/index.js:103:10:103:67 | " var ... ing();" | interpreted as code |
+| lib/index.js:104:21:104:47 | this.op ... dOption | lib/index.js:86:15:86:19 | taint | lib/index.js:104:21:104:47 | this.op ... dOption | $@ flows to here and is later $@. | lib/index.js:86:15:86:19 | taint | Library input | lib/index.js:104:10:104:67 | " var ... ing();" | interpreted as code |
+| lib/index.js:105:21:105:47 | this.op ... dOption | lib/index.js:86:15:86:19 | taint | lib/index.js:105:21:105:47 | this.op ... dOption | $@ flows to here and is later $@. | lib/index.js:86:15:86:19 | taint | Library input | lib/index.js:105:10:105:67 | " var ... ing();" | interpreted as code |
+| lib/index.js:106:21:106:30 | this.taint | lib/index.js:86:15:86:19 | taint | lib/index.js:106:21:106:30 | this.taint | $@ flows to here and is later $@. | lib/index.js:86:15:86:19 | taint | Library input | lib/index.js:106:10:106:50 | " var ... ing();" | interpreted as code |
+| lib/index.js:136:23:136:49 | this.op ... dOption | lib/index.js:112:17:112:21 | taint | lib/index.js:136:23:136:49 | this.op ... dOption | $@ flows to here and is later $@. | lib/index.js:112:17:112:21 | taint | Library input | lib/index.js:136:12:136:69 | " var ... ing();" | interpreted as code |
+| lib/index.js:137:23:137:49 | this.op ... dOption | lib/index.js:112:17:112:21 | taint | lib/index.js:137:23:137:49 | this.op ... dOption | $@ flows to here and is later $@. | lib/index.js:112:17:112:21 | taint | Library input | lib/index.js:137:12:137:69 | " var ... ing();" | interpreted as code |
+| lib/index.js:138:23:138:32 | this.taint | lib/index.js:112:17:112:21 | taint | lib/index.js:138:23:138:32 | this.taint | $@ flows to here and is later $@. | lib/index.js:112:17:112:21 | taint | Library input | lib/index.js:138:12:138:52 | " var ... ing();" | interpreted as code |
diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/lib/index.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/lib/index.js
index 4289ebfc686..9df334c56dc 100644
--- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/lib/index.js
+++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/lib/index.js
@@ -33,3 +33,109 @@ export function greySink(data) {
}
});
}
+
+function codeIsAlive() {
+ new Template().compile();
+}
+
+export function Template(text, opts) {
+ opts = opts || {};
+ var options = {};
+ options.varName = opts.varName;
+ this.opts = options;
+}
+
+Template.prototype = {
+ compile: function () {
+ var opts = this.opts;
+ eval(" var " + opts.varName + " = something();"); // NOT OK
+ },
+ // The below are justs tests that ensure the global-access-path computations terminate.
+ pathsTerminate1: function (node, prev) {
+ node.tree = {
+ ancestor: node,
+ number: rand ? prev.tree.number + 1 : 0,
+ };
+ },
+ pathsTerminate2: function (A) {
+ try {
+ var B = A.p1;
+ var C = B.p2;
+ C.p5 = C;
+ } catch (ex) {}
+ },
+ pathsTerminate3: function (A) {
+ var x = foo();
+ while (Math.random()) {
+ x.r = x;
+ }
+ },
+ pathsTerminate4: function () {
+ var dest = foo();
+ var range = foo();
+ while (Math.random() < 0.5) {
+ range.tabstop = dest;
+ if (Math.random() < 0.5) {
+ dest.firstNonLinked = range;
+ }
+ }
+ },
+};
+
+export class AccessPathClass {
+ constructor(taint) {
+ this.taint = taint;
+
+ var options1 = {taintedOption: taint};
+ this.options1 = options1;
+
+ var options2;
+ options2 = {taintedOption: taint};
+ this.options2 = options2;
+
+ var options3;
+ options3 = {};
+ options3.taintedOption = taint;
+ this.options3 = options3;
+ }
+
+ doesTaint() {
+ eval(" var " + this.options1.taintedOption + " = something();"); // NOT OK
+ eval(" var " + this.options2.taintedOption + " = something();"); // NOT OK
+ eval(" var " + this.options3.taintedOption + " = something();"); // NOT OK
+ eval(" var " + this.taint + " = something();"); // NOT OK
+ }
+}
+
+
+export class AccessPathClassBB {
+ constructor(taint) {
+ this.taint = taint;
+
+ var options1 = {taintedOption: taint};
+ if (Math.random() < 0.5) { console.log("foo"); }
+ this.options1 = options1;
+
+ var options2;
+ if (Math.random() < 0.5) { console.log("foo"); }
+ options2 = {taintedOption: taint};
+ if (Math.random() < 0.5) { console.log("foo"); }
+ this.options2 = options2;
+
+ var options3;
+ if (Math.random() < 0.5) { console.log("foo"); }
+ options3 = {};
+ if (Math.random() < 0.5) { console.log("foo"); }
+ options3.taintedOption = taint;
+ if (Math.random() < 0.5) { console.log("foo"); }
+ this.options3 = options3;
+ }
+
+ doesTaint() {
+ eval(" var " + this.options1.taintedOption + " = something();"); // NOT OK
+ eval(" var " + this.options2.taintedOption + " = something();"); // NOT OK
+ eval(" var " + this.options3.taintedOption + " = something();"); // NOT OK
+ eval(" var " + this.taint + " = something();"); // NOT OK
+ }
+ }
+
\ No newline at end of file
diff --git a/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingAssignment/PrototypePollutingAssignment.expected b/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingAssignment/PrototypePollutingAssignment.expected
index 7705d224298..92cd6bd6c45 100644
--- a/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingAssignment/PrototypePollutingAssignment.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-915/PrototypePollutingAssignment/PrototypePollutingAssignment.expected
@@ -248,7 +248,9 @@ edges
| lib.js:55:15:55:21 | path[0] | lib.js:55:11:55:22 | obj[path[0]] |
| lib.js:59:18:59:18 | s | lib.js:61:17:61:17 | s |
| lib.js:59:18:59:18 | s | lib.js:61:17:61:17 | s |
+| lib.js:61:17:61:17 | s | lib.js:68:11:68:26 | path |
| lib.js:61:17:61:17 | s | lib.js:68:18:68:26 | this.path |
+| lib.js:61:17:61:17 | s | lib.js:70:17:70:20 | path |
| lib.js:68:11:68:26 | path | lib.js:70:17:70:20 | path |
| lib.js:68:18:68:26 | this.path | lib.js:68:11:68:26 | path |
| lib.js:70:17:70:20 | path | lib.js:70:17:70:23 | path[0] |
From fedf8fc575efe60cbd5570fce624e27254739b4a Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Thu, 22 Sep 2022 15:49:29 +0200
Subject: [PATCH 003/465] correct the qldoc
Co-authored-by: Asger F
---
javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
index 421d5a88860..96451661c7d 100644
--- a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
+++ b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
@@ -268,7 +268,7 @@ module AccessPath {
)
}
- /** A module for computing an access to a variable that happens after a write to that same variable */
+ /** A module for computing an access to a variable that happens after a property has been written onto it */
private module GetLaterAccess {
/**
* Gets an access to a variable that is written to in `write`, where the access is after the write.
From 5fb44e9dd83754e6bc78bcf8909575289c410587 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Thu, 22 Sep 2022 15:57:40 +0200
Subject: [PATCH 004/465] simplify and improve the example for
getLaterBaseAccess
---
.../ql/lib/semmle/javascript/GlobalAccessPaths.qll | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
index 96451661c7d..dc28e44ecdd 100644
--- a/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
+++ b/javascript/ql/lib/semmle/javascript/GlobalAccessPaths.qll
@@ -275,11 +275,11 @@ module AccessPath {
*
* This allows `fromRhs` to compute an access path for e.g. the below example:
* ```JavaScript
- * function Template(text, opts) {
- * opts = opts || {};
- * var options = {};
- * options.varName = taint;
- * this.opts = options; // this.opts.varName is now available
+ * function foo(x) {
+ * var obj = {
+ * bar: x // `x` has the access path "foo.bar" starting from the root `this`.
+ * };
+ * this.foo = obj;
* }
* ```
*/
From b16b3c0394e27359053a1f9d0693006160c393de Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 4 Oct 2022 15:01:10 +0200
Subject: [PATCH 005/465] move cwe-078 tests into subfolders
---
.../cwe-078/{ => CommandInjection}/CommandInjection.expected | 0
.../cwe-078/{ => CommandInjection}/CommandInjection.qlref | 0
.../security/cwe-078/{ => CommandInjection}/CommandInjection.rb | 0
.../security/cwe-078/{ => KernelOpen}/KernelOpen.expected | 0
.../security/cwe-078/{ => KernelOpen}/KernelOpen.qlref | 0
.../query-tests/security/cwe-078/{ => KernelOpen}/KernelOpen.rb | 0
6 files changed, 0 insertions(+), 0 deletions(-)
rename ruby/ql/test/query-tests/security/cwe-078/{ => CommandInjection}/CommandInjection.expected (100%)
rename ruby/ql/test/query-tests/security/cwe-078/{ => CommandInjection}/CommandInjection.qlref (100%)
rename ruby/ql/test/query-tests/security/cwe-078/{ => CommandInjection}/CommandInjection.rb (100%)
rename ruby/ql/test/query-tests/security/cwe-078/{ => KernelOpen}/KernelOpen.expected (100%)
rename ruby/ql/test/query-tests/security/cwe-078/{ => KernelOpen}/KernelOpen.qlref (100%)
rename ruby/ql/test/query-tests/security/cwe-078/{ => KernelOpen}/KernelOpen.rb (100%)
diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
similarity index 100%
rename from ruby/ql/test/query-tests/security/cwe-078/CommandInjection.expected
rename to ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.qlref b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.qlref
similarity index 100%
rename from ruby/ql/test/query-tests/security/cwe-078/CommandInjection.qlref
rename to ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.qlref
diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection.rb b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.rb
similarity index 100%
rename from ruby/ql/test/query-tests/security/cwe-078/CommandInjection.rb
rename to ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.rb
diff --git a/ruby/ql/test/query-tests/security/cwe-078/KernelOpen.expected b/ruby/ql/test/query-tests/security/cwe-078/KernelOpen/KernelOpen.expected
similarity index 100%
rename from ruby/ql/test/query-tests/security/cwe-078/KernelOpen.expected
rename to ruby/ql/test/query-tests/security/cwe-078/KernelOpen/KernelOpen.expected
diff --git a/ruby/ql/test/query-tests/security/cwe-078/KernelOpen.qlref b/ruby/ql/test/query-tests/security/cwe-078/KernelOpen/KernelOpen.qlref
similarity index 100%
rename from ruby/ql/test/query-tests/security/cwe-078/KernelOpen.qlref
rename to ruby/ql/test/query-tests/security/cwe-078/KernelOpen/KernelOpen.qlref
diff --git a/ruby/ql/test/query-tests/security/cwe-078/KernelOpen.rb b/ruby/ql/test/query-tests/security/cwe-078/KernelOpen/KernelOpen.rb
similarity index 100%
rename from ruby/ql/test/query-tests/security/cwe-078/KernelOpen.rb
rename to ruby/ql/test/query-tests/security/cwe-078/KernelOpen/KernelOpen.rb
From 99b90789e5a809b906aa7388b2c050a508980a9e Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 14:04:30 +0200
Subject: [PATCH 006/465] add `.shellescape` as a sanitizer for
`rb/command-injection`
---
.../ruby/security/CommandInjectionCustomizations.qll | 3 +++
.../cwe-078/CommandInjection/CommandInjection.expected | 6 ++++++
.../cwe-078/CommandInjection/CommandInjection.rb | 10 ++++++++++
3 files changed, 19 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll
index e67ae044a00..20cc8d5b26b 100644
--- a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll
+++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll
@@ -49,6 +49,9 @@ module CommandInjection {
class ShellwordsEscapeAsSanitizer extends Sanitizer {
ShellwordsEscapeAsSanitizer() {
this = API::getTopLevelMember("Shellwords").getAMethodCall(["escape", "shellescape"])
+ or
+ // The method is also added as `String#shellescape`.
+ this.(DataFlow::CallNode).getMethodName() = "shellescape"
}
}
}
diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
index b2e4990daec..1d00f293dfd 100644
--- a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
+++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected
@@ -15,6 +15,8 @@ edges
| CommandInjection.rb:81:20:81:25 | **args : | CommandInjection.rb:82:22:82:25 | args : |
| CommandInjection.rb:82:22:82:25 | args : | CommandInjection.rb:82:22:82:37 | ...[...] : |
| CommandInjection.rb:82:22:82:37 | ...[...] : | CommandInjection.rb:82:14:82:39 | "echo #{...}" |
+| CommandInjection.rb:94:16:94:21 | call to params : | CommandInjection.rb:94:16:94:28 | ...[...] : |
+| CommandInjection.rb:94:16:94:28 | ...[...] : | CommandInjection.rb:95:16:95:28 | "cat #{...}" |
nodes
| CommandInjection.rb:6:15:6:20 | call to params : | semmle.label | call to params : |
| CommandInjection.rb:6:15:6:26 | ...[...] : | semmle.label | ...[...] : |
@@ -37,6 +39,9 @@ nodes
| CommandInjection.rb:82:14:82:39 | "echo #{...}" | semmle.label | "echo #{...}" |
| CommandInjection.rb:82:22:82:25 | args : | semmle.label | args : |
| CommandInjection.rb:82:22:82:37 | ...[...] : | semmle.label | ...[...] : |
+| CommandInjection.rb:94:16:94:21 | call to params : | semmle.label | call to params : |
+| CommandInjection.rb:94:16:94:28 | ...[...] : | semmle.label | ...[...] : |
+| CommandInjection.rb:95:16:95:28 | "cat #{...}" | semmle.label | "cat #{...}" |
subpaths
#select
| CommandInjection.rb:7:10:7:15 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value |
@@ -51,3 +56,4 @@ subpaths
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:64:18:64:23 | number | user-provided value |
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:72:23:72:33 | blah_number | user-provided value |
| CommandInjection.rb:82:14:82:39 | "echo #{...}" | CommandInjection.rb:81:20:81:25 | **args : | CommandInjection.rb:82:14:82:39 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:81:20:81:25 | **args | user-provided value |
+| CommandInjection.rb:95:16:95:28 | "cat #{...}" | CommandInjection.rb:94:16:94:21 | call to params : | CommandInjection.rb:95:16:95:28 | "cat #{...}" | This command depends on a $@. | CommandInjection.rb:94:16:94:21 | call to params | user-provided value |
diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.rb b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.rb
index ed9750128cc..75219e2131c 100644
--- a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.rb
+++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.rb
@@ -88,3 +88,13 @@ module Types
end
end
end
+
+class Foo < ActionController::Base
+ def create
+ file = params[:file]
+ system("cat #{file}")
+ # .shellescape
+ system("cat #{file.shellescape}") # OK, because file is shell escaped
+
+ end
+end
\ No newline at end of file
From 75422dfa72238e49bef88353f58724ba8fe8c4e0 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 15:02:18 +0200
Subject: [PATCH 007/465] add library for reasoning about gems and .gemspec
files
---
ruby/ql/lib/codeql/ruby/frameworks/Core.qll | 1 +
.../lib/codeql/ruby/frameworks/core/Gem.qll | 92 +++++++++++++++++++
2 files changed, 93 insertions(+)
create mode 100644 ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Core.qll b/ruby/ql/lib/codeql/ruby/frameworks/Core.qll
index 73824b123ff..3e61b50dc04 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/Core.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/Core.qll
@@ -7,6 +7,7 @@ private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
import core.BasicObject::BasicObject
import core.Object::Object
+import core.Gem::Gem
import core.Kernel::Kernel
import core.Module
import core.Array
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll
new file mode 100644
index 00000000000..41daf6ac3b8
--- /dev/null
+++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Gem.qll
@@ -0,0 +1,92 @@
+/**
+ * Provides modeling for the `Gem` module and `.gemspec` files.
+ */
+
+private import ruby
+private import Ast
+private import codeql.ruby.ApiGraphs
+
+/** Provides modeling for the `Gem` module and `.gemspec` files. */
+module Gem {
+ /** A .gemspec file that lists properties of a Ruby gem. */
+ class GemSpec instanceof File {
+ API::Node specCall;
+
+ GemSpec() {
+ this.getExtension() = "gemspec" and
+ specCall = API::root().getMember("Gem").getMember("Specification").getMethod("new") and
+ specCall.getLocation().getFile() = this
+ }
+
+ /** Gets the name of this .gemspec file. */
+ string toString() { result = File.super.getBaseName() }
+
+ /**
+ * Gets a value of the `name` property of this .gemspec file.
+ * These properties are set using the `Gem::Specification.new` method.
+ */
+ private Expr getSpecProperty(string key) {
+ exists(Expr rhs |
+ rhs =
+ specCall
+ .getBlock()
+ .getParameter(0)
+ .getMethod(key + "=")
+ .getParameter(0)
+ .asSink()
+ .asExpr()
+ .getExpr()
+ .(Ast::AssignExpr)
+ .getRightOperand()
+ |
+ result = rhs
+ or
+ // some properties are arrays, we just unfold them
+ result = rhs.(ArrayLiteral).getAnElement()
+ )
+ }
+
+ /** Gets the name of the gem */
+ string getName() { result = getSpecProperty("name").getConstantValue().getString() }
+
+ /** Gets a path that is loaded when the gem is required */
+ private string getARequirePath() {
+ result = getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()
+ or
+ not exists(getSpecProperty(["require_paths", "require_path"]).getConstantValue().getString()) and
+ result = "lib" // the default is "lib"
+ }
+
+ /** Gets a file that is loaded when the gem is required. */
+ private File getAnRequiredFile() {
+ result = File.super.getParentContainer().getFolder(getARequirePath()).getAChildContainer*()
+ }
+
+ /** Gets a class/module that is exported by this gem. */
+ private ModuleBase getAPublicModule() {
+ result.(Toplevel).getLocation().getFile() = getAnRequiredFile()
+ or
+ result = getAPublicModule().getAModule()
+ or
+ result = getAPublicModule().getAClass()
+ or
+ result = getAPublicModule().getStmt(_).(SingletonClass)
+ }
+
+ /** Gets a parameter from an exported method, which is an input to this gem. */
+ DataFlow::ParameterNode getAnInputParameter() {
+ exists(MethodBase method | method = getAPublicModule().getAMethod() |
+ result.getParameter() = method.getAParameter() and
+ method.isPublic()
+ )
+ }
+ }
+
+ /** Gets a parameter that is an input to a named gem. */
+ DataFlow::ParameterNode getALibraryInput() {
+ exists(GemSpec spec |
+ exists(spec.getName()) and // we only consider `.gemspec` files that have a name
+ result = spec.getAnInputParameter()
+ )
+ }
+}
From 0d5da42ddd95c62d80ebbaaea437b65cb5a3d87a Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 15:04:50 +0200
Subject: [PATCH 008/465] add a `getName()` utility to
`DataFlow::ParameterNode`
---
ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll | 3 +++
1 file changed, 3 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index f6ec8c4ab1b..a89e4964b8b 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -147,6 +147,9 @@ class ExprNode extends Node, TExprNode {
class ParameterNode extends Node, TParameterNode instanceof ParameterNodeImpl {
/** Gets the parameter corresponding to this node, if any. */
final Parameter getParameter() { result = super.getParameter() }
+
+ /** Gets the name of the parameter, if any. */
+ final string getName() { result = this.getParameter().(NamedParameter).getName() }
}
/**
From 557dd108964cf9e04c8436a0935dc40820661e84 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 15:07:07 +0200
Subject: [PATCH 009/465] add a `rb/unsafe-shell-command-construction` query
---
...ShellCommandConstructionCustomizations.qll | 108 ++++++++++++++++++
.../UnsafeShellCommandConstructionQuery.qll | 35 ++++++
.../cwe-078/UnsafeShellCommandConstruction.ql | 26 +++++
.../UnsafeShellCommandConstruction.expected | 40 +++++++
.../UnsafeShellCommandConstruction.qlref | 1 +
.../impl/sub/notImported.rb | 6 +
.../impl/sub/other.rb | 7 ++
.../impl/sub/other2.rb | 5 +
.../impl/unsafeShell.rb | 45 ++++++++
.../unsafe-shell.gemspec | 5 +
10 files changed, 278 insertions(+)
create mode 100644 ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
create mode 100644 ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionQuery.qll
create mode 100644 ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.ql
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.qlref
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/notImported.rb
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other.rb
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other2.rb
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/unsafeShell.rb
create mode 100644 ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/unsafe-shell.gemspec
diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
new file mode 100644
index 00000000000..a235c53462f
--- /dev/null
+++ b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
@@ -0,0 +1,108 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about
+ * shell command constructed from library input vulnerabilities, as
+ * well as extension points for adding your own.
+ */
+
+private import codeql.ruby.DataFlow
+private import codeql.ruby.DataFlow2
+private import codeql.ruby.ApiGraphs
+private import codeql.ruby.frameworks.core.Gem::Gem as Gem
+private import codeql.ruby.AST as Ast
+private import codeql.ruby.Concepts as Concepts
+
+module UnsafeShellCommandConstruction {
+ /** A source for shell command constructed from library input vulnerabilities. */
+ abstract class Source extends DataFlow::Node { }
+
+ /** An input parameter to a gem seen as a source. */
+ private class LibraryInputAsSource extends Source instanceof DataFlow::ParameterNode {
+ LibraryInputAsSource() {
+ this = Gem::getALibraryInput() and
+ // we exclude arguments named `cmd` or similar, as they seem to execute commands on purpose
+ not exists(string name | name = super.getName() |
+ name = ["cmd", "command"]
+ or
+ name.regexpMatch(".*(Cmd|Command)$")
+ )
+ }
+ }
+
+ /** A sink for shell command constructed from library input vulnerabilities. */
+ abstract class Sink extends DataFlow::Node {
+ /** Gets a description of how the string in this sink was constructed. */
+ abstract string describe();
+
+ /** Gets the dataflow node where the string is constructed. */
+ DataFlow::Node getStringConstruction() { result = this }
+
+ /** Gets the dataflow node that executed the string as a shell command. */
+ abstract DataFlow::Node getCommandExecution();
+ }
+
+ /** A dataflow-configuration for tracking flow from various string constructions to places where those strings are executed as shell commands. */
+ class TrackSystemCommand extends DataFlow2::Configuration {
+ TrackSystemCommand() { this = "StringConcatAsSink::TrackSystemCommand" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof TaintedFormat::PrintfStyleCall
+ or
+ source.asExpr().getExpr() =
+ any(Ast::StringLiteral lit |
+ lit.getComponent(_) instanceof Ast::StringInterpolationComponent
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(Concepts::SystemCommandExecution s | s.isShellInterpreted(sink))
+ }
+ }
+
+ /** Holds if the string constructed at `source` is executed at `shellExec` */
+ predicate isUsedAsShellCommand(DataFlow::Node source, Concepts::SystemCommandExecution shellExec) {
+ any(TrackSystemCommand conf)
+ .hasFlow(source, any(DataFlow::Node arg | shellExec.isShellInterpreted(arg)))
+ }
+
+ /**
+ * A string constructed from a string-literal (e.g. `"foo #{sink}"`),
+ * where the resulting string ends up being executed as a shell command.
+ */
+ class StringFormatAsSink extends Sink {
+ Concepts::SystemCommandExecution s;
+ Ast::StringLiteral lit;
+
+ StringFormatAsSink() {
+ isUsedAsShellCommand(any(DataFlow::Node n | n.asExpr().getExpr() = lit), s) and
+ this.asExpr().getExpr() = lit.getComponent(_)
+ }
+
+ override string describe() { result = "string construction" }
+
+ override DataFlow::Node getCommandExecution() { result = s }
+
+ override DataFlow::Node getStringConstruction() { result.asExpr().getExpr() = lit }
+ }
+
+ import codeql.ruby.security.TaintedFormatStringSpecific as TaintedFormat
+
+ /**
+ * A string constructed from a printf-style call,
+ * where the resulting string ends up being executed as a shell command.
+ */
+ class TaintedFormatStringAsSink extends Sink {
+ Concepts::SystemCommandExecution s;
+ TaintedFormat::PrintfStyleCall call;
+
+ TaintedFormatStringAsSink() {
+ isUsedAsShellCommand(call, s) and
+ this = [call.getFormatArgument(_), call.getFormatString()]
+ }
+
+ override string describe() { result = "formatted string" }
+
+ override DataFlow::Node getCommandExecution() { result = s }
+
+ override DataFlow::Node getStringConstruction() { result = call }
+ }
+}
diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionQuery.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionQuery.qll
new file mode 100644
index 00000000000..87b602911ae
--- /dev/null
+++ b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionQuery.qll
@@ -0,0 +1,35 @@
+/**
+ * Provides a taint tracking configuration for reasoning about shell command
+ * constructed from library input vulnerabilities
+ *
+ * Note, for performance reasons: only import this file if `Configuration` is needed,
+ * otherwise `UnsafeShellCommandConstructionCustomizations` should be imported instead.
+ */
+
+import codeql.ruby.DataFlow
+import UnsafeShellCommandConstructionCustomizations::UnsafeShellCommandConstruction
+private import codeql.ruby.TaintTracking
+private import CommandInjectionCustomizations::CommandInjection as CommandInjection
+private import codeql.ruby.dataflow.BarrierGuards
+
+/**
+ * A taint-tracking configuration for detecting shell command constructed from library input vulnerabilities.
+ */
+class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "UnsafeShellCommandConstruction" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ node instanceof CommandInjection::Sanitizer or // using all sanitizers from `rb/command-injection`
+ node instanceof StringConstCompareBarrier or
+ node instanceof StringConstArrayInclusionCallBarrier
+ }
+
+ // override to require the path doesn't have unmatched return steps
+ override DataFlow::FlowFeature getAFeature() {
+ result instanceof DataFlow::FeatureHasSourceCallContext
+ }
+}
diff --git a/ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.ql b/ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.ql
new file mode 100644
index 00000000000..53c71cfc314
--- /dev/null
+++ b/ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.ql
@@ -0,0 +1,26 @@
+/**
+ * @name Unsafe shell command constructed from library input
+ * @description Using externally controlled strings in a command line may allow a malicious
+ * user to change the meaning of the command.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 6.3
+ * @precision high
+ * @id rb/shell-command-constructed-from-input
+ * @tags correctness
+ * security
+ * external/cwe/cwe-078
+ * external/cwe/cwe-088
+ * external/cwe/cwe-073
+ */
+
+import codeql.ruby.security.UnsafeShellCommandConstructionQuery
+import DataFlow::PathGraph
+
+from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
+where
+ config.hasFlowPath(source, sink) and
+ sinkNode = sink.getNode()
+select sinkNode.getStringConstruction(), source, sink,
+ "This " + sinkNode.describe() + " which depends on $@ is later used in a $@.", source.getNode(),
+ "library input", sinkNode.getCommandExecution(), "shell command"
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected
new file mode 100644
index 00000000000..3cc91bebdd9
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected
@@ -0,0 +1,40 @@
+edges
+| impl/sub/notImported.rb:2:12:2:17 | target : | impl/sub/notImported.rb:3:19:3:27 | #{...} |
+| impl/sub/other2.rb:2:12:2:17 | target : | impl/sub/other2.rb:3:19:3:27 | #{...} |
+| impl/sub/other.rb:2:12:2:17 | target : | impl/sub/other.rb:3:19:3:27 | #{...} |
+| impl/unsafeShell.rb:2:12:2:17 | target : | impl/unsafeShell.rb:3:19:3:27 | #{...} |
+| impl/unsafeShell.rb:6:12:6:12 | x : | impl/unsafeShell.rb:7:32:7:32 | x |
+| impl/unsafeShell.rb:15:47:15:64 | innocent_file_path : | impl/unsafeShell.rb:20:21:20:41 | #{...} |
+| impl/unsafeShell.rb:23:15:23:23 | file_path : | impl/unsafeShell.rb:26:19:26:30 | #{...} |
+| impl/unsafeShell.rb:33:12:33:17 | target : | impl/unsafeShell.rb:34:19:34:27 | #{...} |
+| impl/unsafeShell.rb:37:10:37:10 | x : | impl/unsafeShell.rb:38:19:38:22 | #{...} |
+nodes
+| impl/sub/notImported.rb:2:12:2:17 | target : | semmle.label | target : |
+| impl/sub/notImported.rb:3:19:3:27 | #{...} | semmle.label | #{...} |
+| impl/sub/other2.rb:2:12:2:17 | target : | semmle.label | target : |
+| impl/sub/other2.rb:3:19:3:27 | #{...} | semmle.label | #{...} |
+| impl/sub/other.rb:2:12:2:17 | target : | semmle.label | target : |
+| impl/sub/other.rb:3:19:3:27 | #{...} | semmle.label | #{...} |
+| impl/unsafeShell.rb:2:12:2:17 | target : | semmle.label | target : |
+| impl/unsafeShell.rb:3:19:3:27 | #{...} | semmle.label | #{...} |
+| impl/unsafeShell.rb:6:12:6:12 | x : | semmle.label | x : |
+| impl/unsafeShell.rb:7:32:7:32 | x | semmle.label | x |
+| impl/unsafeShell.rb:15:47:15:64 | innocent_file_path : | semmle.label | innocent_file_path : |
+| impl/unsafeShell.rb:20:21:20:41 | #{...} | semmle.label | #{...} |
+| impl/unsafeShell.rb:23:15:23:23 | file_path : | semmle.label | file_path : |
+| impl/unsafeShell.rb:26:19:26:30 | #{...} | semmle.label | #{...} |
+| impl/unsafeShell.rb:33:12:33:17 | target : | semmle.label | target : |
+| impl/unsafeShell.rb:34:19:34:27 | #{...} | semmle.label | #{...} |
+| impl/unsafeShell.rb:37:10:37:10 | x : | semmle.label | x : |
+| impl/unsafeShell.rb:38:19:38:22 | #{...} | semmle.label | #{...} |
+subpaths
+#select
+| impl/sub/notImported.rb:3:14:3:28 | "cat #{...}" | impl/sub/notImported.rb:2:12:2:17 | target : | impl/sub/notImported.rb:3:19:3:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/sub/notImported.rb:2:12:2:17 | target | library input | impl/sub/notImported.rb:3:5:3:34 | call to popen | shell command |
+| impl/sub/other2.rb:3:14:3:28 | "cat #{...}" | impl/sub/other2.rb:2:12:2:17 | target : | impl/sub/other2.rb:3:19:3:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/sub/other2.rb:2:12:2:17 | target | library input | impl/sub/other2.rb:3:5:3:34 | call to popen | shell command |
+| impl/sub/other.rb:3:14:3:28 | "cat #{...}" | impl/sub/other.rb:2:12:2:17 | target : | impl/sub/other.rb:3:19:3:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/sub/other.rb:2:12:2:17 | target | library input | impl/sub/other.rb:3:5:3:34 | call to popen | shell command |
+| impl/unsafeShell.rb:3:14:3:28 | "cat #{...}" | impl/unsafeShell.rb:2:12:2:17 | target : | impl/unsafeShell.rb:3:19:3:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/unsafeShell.rb:2:12:2:17 | target | library input | impl/unsafeShell.rb:3:5:3:34 | call to popen | shell command |
+| impl/unsafeShell.rb:7:14:7:33 | call to sprintf | impl/unsafeShell.rb:6:12:6:12 | x : | impl/unsafeShell.rb:7:32:7:32 | x | This formatted string which depends on $@ is later used in a $@. | impl/unsafeShell.rb:6:12:6:12 | x | library input | impl/unsafeShell.rb:8:5:8:25 | call to popen | shell command |
+| impl/unsafeShell.rb:20:14:20:42 | "which #{...}" | impl/unsafeShell.rb:15:47:15:64 | innocent_file_path : | impl/unsafeShell.rb:20:21:20:41 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/unsafeShell.rb:15:47:15:64 | innocent_file_path | library input | impl/unsafeShell.rb:20:5:20:48 | call to popen | shell command |
+| impl/unsafeShell.rb:26:14:26:31 | "cat #{...}" | impl/unsafeShell.rb:23:15:23:23 | file_path : | impl/unsafeShell.rb:26:19:26:30 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/unsafeShell.rb:23:15:23:23 | file_path | library input | impl/unsafeShell.rb:26:5:26:37 | call to popen | shell command |
+| impl/unsafeShell.rb:34:14:34:28 | "cat #{...}" | impl/unsafeShell.rb:33:12:33:17 | target : | impl/unsafeShell.rb:34:19:34:27 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/unsafeShell.rb:33:12:33:17 | target | library input | impl/unsafeShell.rb:34:5:34:34 | call to popen | shell command |
+| impl/unsafeShell.rb:38:14:38:23 | "cat #{...}" | impl/unsafeShell.rb:37:10:37:10 | x : | impl/unsafeShell.rb:38:19:38:22 | #{...} | This string construction which depends on $@ is later used in a $@. | impl/unsafeShell.rb:37:10:37:10 | x | library input | impl/unsafeShell.rb:38:5:38:29 | call to popen | shell command |
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.qlref b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.qlref
new file mode 100644
index 00000000000..99292da7663
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.qlref
@@ -0,0 +1 @@
+queries/security/cwe-078/UnsafeShellCommandConstruction.ql
\ No newline at end of file
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/notImported.rb b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/notImported.rb
new file mode 100644
index 00000000000..0a385f5f6bc
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/notImported.rb
@@ -0,0 +1,6 @@
+class Foobar
+ def foo1(target)
+ IO.popen("cat #{target}", "w") # NOT OK - everything assumed to be imported...
+ end
+end
+
\ No newline at end of file
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other.rb b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other.rb
new file mode 100644
index 00000000000..22eaa13bcc0
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other.rb
@@ -0,0 +1,7 @@
+class Foobar
+ def foo1(target)
+ IO.popen("cat #{target}", "w") # NOT OK
+ end
+end
+
+require 'sub/other2'
\ No newline at end of file
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other2.rb b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other2.rb
new file mode 100644
index 00000000000..007dae343ff
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/sub/other2.rb
@@ -0,0 +1,5 @@
+class Foobar
+ def foo1(target)
+ IO.popen("cat #{target}", "w") # NOT OK
+ end
+end
\ No newline at end of file
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/unsafeShell.rb b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/unsafeShell.rb
new file mode 100644
index 00000000000..52e3016ac91
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/impl/unsafeShell.rb
@@ -0,0 +1,45 @@
+class Foobar
+ def foo1(target)
+ IO.popen("cat #{target}", "w") # NOT OK
+ end
+
+ def foo2(x)
+ format = sprintf("cat %s", x) # NOT OK
+ IO.popen(format, "w")
+ end
+
+ def fileRead1(path)
+ File.read(path) # OK
+ end
+
+ def my_exec(cmd, command, myCmd, myCommand, innocent_file_path)
+ IO.popen("which #{cmd}", "w") # OK - the parameter is named `cmd`, so it's meant to be a command
+ IO.popen("which #{command}", "w") # OK - the parameter is named `command`, so it's meant to be a command
+ IO.popen("which #{myCmd}", "w") # OK - the parameter is named `myCmd`, so it's meant to be a command
+ IO.popen("which #{myCommand}", "w") # OK - the parameter is named `myCommand`, so it's meant to be a command
+ IO.popen("which #{innocent_file_path}", "w") # NOT OK - the parameter is named `innocent_file_path`, so it's not meant to be a command
+ end
+
+ def escaped(file_path)
+ IO.popen("cat #{file_path.shellescape}", "w") # OK - the parameter is escaped
+
+ IO.popen("cat #{file_path}", "w") # NOT OK - the parameter is not escaped
+ end
+end
+
+require File.join(File.dirname(__FILE__), 'sub', 'other')
+
+class Foobar2
+ def foo1(target)
+ IO.popen("cat #{target}", "w") # NOT OK
+ end
+
+ def id(x)
+ IO.popen("cat #{x}", "w") # NOT OK - the parameter is not a constant.
+ return x
+ end
+
+ def thisIsSafe()
+ IO.popen("echo #{id('foo')}", "w") # OK - only using constants.
+ end
+end
diff --git a/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/unsafe-shell.gemspec b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/unsafe-shell.gemspec
new file mode 100644
index 00000000000..545bc14a7a6
--- /dev/null
+++ b/ruby/ql/test/query-tests/security/cwe-078/UnsafeShellCommandConstruction/unsafe-shell.gemspec
@@ -0,0 +1,5 @@
+Gem::Specification.new do |s|
+ s.name = 'unsafe-shell'
+ s.require_path = "impl"
+ end
+
\ No newline at end of file
From d427e555077158829997df0a83ad7b1f0dabbe8f Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 15:07:14 +0200
Subject: [PATCH 010/465] add qhelp
---
.../UnsafeShellCommandConstruction.qhelp | 73 +++++++++++++++++++
.../unsafe-shell-command-construction.rb | 5 ++
...unsafe-shell-command-construction_fixed.rb | 6 ++
3 files changed, 84 insertions(+)
create mode 100644 ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.qhelp
create mode 100644 ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction.rb
create mode 100644 ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction_fixed.rb
diff --git a/ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.qhelp b/ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.qhelp
new file mode 100644
index 00000000000..88cea1d80d3
--- /dev/null
+++ b/ruby/ql/src/queries/security/cwe-078/UnsafeShellCommandConstruction.qhelp
@@ -0,0 +1,73 @@
+
+
+
+
+ Dynamically constructing a shell command with inputs from exported
+ functions may inadvertently change the meaning of the shell command.
+
+ Clients using the exported function may use inputs containing
+ characters that the shell interprets in a special way, for instance
+ quotes and spaces.
+
+ This can result in the shell command misbehaving, or even
+ allowing a malicious user to execute arbitrary commands on the system.
+
+
+
+
+
+
+
+ If possible, provide the dynamic arguments to the shell as an array
+ to APIs such as system(..) to avoid interpretation by the shell.
+
+
+
+ Alternatively, if the shell command must be constructed
+ dynamically, then add code to ensure that special characters
+ do not alter the shell command unexpectedly.
+
+
+
+
+
+
+ The following example shows a dynamically constructed shell
+ command that downloads a file from a remote URL.
+
+
+
+
+
+ The shell command will, however, fail to work as intended if the
+ input contains spaces or other special characters interpreted in a
+ special way by the shell.
+
+
+
+ Even worse, a client might pass in user-controlled
+ data, not knowing that the input is interpreted as a shell command.
+ This could allow a malicious user to provide the input http://example.org; cat /etc/passwd
+ in order to execute the command cat /etc/passwd.
+
+
+
+ To avoid such potentially catastrophic behaviors, provide the
+ inputs from exported functions as an argument that does not
+ get interpreted by a shell:
+
+
+
+
+
+
+
+
+ OWASP:
+ Command Injection.
+
+
+
+
diff --git a/ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction.rb b/ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction.rb
new file mode 100644
index 00000000000..500bd49e890
--- /dev/null
+++ b/ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction.rb
@@ -0,0 +1,5 @@
+module Utils
+ def download(path)
+ system("wget #{path}") # NOT OK
+ end
+end
\ No newline at end of file
diff --git a/ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction_fixed.rb b/ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction_fixed.rb
new file mode 100644
index 00000000000..cb8730eee09
--- /dev/null
+++ b/ruby/ql/src/queries/security/cwe-078/examples/unsafe-shell-command-construction_fixed.rb
@@ -0,0 +1,6 @@
+module Utils
+ def download(path)
+ # using an array to call `system` is safe
+ system("wget", path) # OK
+ end
+end
\ No newline at end of file
From cadb948d576e938368313d53e1ca35dea6f9bc0a Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 15:07:20 +0200
Subject: [PATCH 011/465] add change-note
---
.../2022-10-10-unsafe-shell-command-construction.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 ruby/ql/src/change-notes/2022-10-10-unsafe-shell-command-construction.md
diff --git a/ruby/ql/src/change-notes/2022-10-10-unsafe-shell-command-construction.md b/ruby/ql/src/change-notes/2022-10-10-unsafe-shell-command-construction.md
new file mode 100644
index 00000000000..d61d32dcc5f
--- /dev/null
+++ b/ruby/ql/src/change-notes/2022-10-10-unsafe-shell-command-construction.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely constructs shell commands from their inputs.
From b64a1b7c42f2e7ed3214f5911bd8563c6cebaebc Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 10 Oct 2022 15:14:49 +0200
Subject: [PATCH 012/465] add a missing qldoc
---
.../security/UnsafeShellCommandConstructionCustomizations.qll | 3 +++
1 file changed, 3 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
index a235c53462f..94b16935a55 100644
--- a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
+++ b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
@@ -11,6 +11,9 @@ private import codeql.ruby.frameworks.core.Gem::Gem as Gem
private import codeql.ruby.AST as Ast
private import codeql.ruby.Concepts as Concepts
+/**
+ * Module containing sources, sinks, and sanitizers for shell command constructed from library input.
+ */
module UnsafeShellCommandConstruction {
/** A source for shell command constructed from library input vulnerabilities. */
abstract class Source extends DataFlow::Node { }
From 0220f0aa5cfb566a579dd10181875429ea78ab5e Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 11 Oct 2022 13:37:01 +0200
Subject: [PATCH 013/465] use type-tracking instead
---
...ShellCommandConstructionCustomizations.qll | 34 ++++++++-----------
1 file changed, 14 insertions(+), 20 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
index 94b16935a55..b89743fe68e 100644
--- a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
+++ b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll
@@ -43,28 +43,22 @@ module UnsafeShellCommandConstruction {
abstract DataFlow::Node getCommandExecution();
}
- /** A dataflow-configuration for tracking flow from various string constructions to places where those strings are executed as shell commands. */
- class TrackSystemCommand extends DataFlow2::Configuration {
- TrackSystemCommand() { this = "StringConcatAsSink::TrackSystemCommand" }
-
- override predicate isSource(DataFlow::Node source) {
- source instanceof TaintedFormat::PrintfStyleCall
- or
- source.asExpr().getExpr() =
- any(Ast::StringLiteral lit |
- lit.getComponent(_) instanceof Ast::StringInterpolationComponent
- )
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(Concepts::SystemCommandExecution s | s.isShellInterpreted(sink))
- }
- }
-
/** Holds if the string constructed at `source` is executed at `shellExec` */
predicate isUsedAsShellCommand(DataFlow::Node source, Concepts::SystemCommandExecution shellExec) {
- any(TrackSystemCommand conf)
- .hasFlow(source, any(DataFlow::Node arg | shellExec.isShellInterpreted(arg)))
+ source = backtrackShellExec(TypeTracker::TypeBackTracker::end(), shellExec)
+ }
+
+ import codeql.ruby.typetracking.TypeTracker as TypeTracker
+
+ private DataFlow::LocalSourceNode backtrackShellExec(
+ TypeTracker::TypeBackTracker t, Concepts::SystemCommandExecution shellExec
+ ) {
+ t.start() and
+ result = any(DataFlow::Node n | shellExec.isShellInterpreted(n)).getALocalSource()
+ or
+ exists(TypeTracker::TypeBackTracker t2 |
+ result = backtrackShellExec(t2, shellExec).backtrack(t2, t)
+ )
}
/**
From 9b7df354e63b6249ec843fad3368456bda3233d1 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 29 Sep 2022 09:41:28 -0400
Subject: [PATCH 014/465] move files
---
.../Security/CWE/CWE-326/InsufficientKeySize.java | 0
.../Security/CWE/CWE-326/InsufficientKeySize.qhelp | 10 +++++-----
.../Security/CWE/CWE-326/InsufficientKeySize.ql | 0
.../security/CWE-326/InsufficientKeySize.qlref | 1 -
.../security/CWE-326/InsufficientKeySize.expected | 0
.../security/CWE-326/InsufficientKeySize.java | 0
.../security/CWE-326/InsufficientKeySize.qlref | 1 +
7 files changed, 6 insertions(+), 6 deletions(-)
rename java/ql/src/{experimental => }/Security/CWE/CWE-326/InsufficientKeySize.java (100%)
rename java/ql/src/{experimental => }/Security/CWE/CWE-326/InsufficientKeySize.qhelp (91%)
rename java/ql/src/{experimental => }/Security/CWE/CWE-326/InsufficientKeySize.ql (100%)
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref
rename java/ql/test/{experimental => }/query-tests/security/CWE-326/InsufficientKeySize.expected (100%)
rename java/ql/test/{experimental => }/query-tests/security/CWE-326/InsufficientKeySize.java (100%)
create mode 100644 java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
rename to java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
similarity index 91%
rename from java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
rename to java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
index 4d4ec76f060..4e5faa0ddf4 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
@@ -1,15 +1,15 @@
- This rule finds uses of encryption algorithms with too small a key size. Encryption algorithms
+
This rule finds uses of encryption algorithms with too small a key size. Encryption algorithms
are vulnerable to brute force attack when too small a key size is used.
- The key should be at least 2048 bits long when using RSA and DSA encryption, 256 bits long when using EC encryption, and 128 bits long when using
+
The key should be at least 2048 bits long when using RSA and DSA encryption, 256 bits long when using EC encryption, and 128 bits long when using
symmetric encryption.
-
+
@@ -24,6 +24,6 @@ symmetric encryption.
CWE.
CWE-326: Inadequate Encryption Strength
-
+
-
\ No newline at end of file
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
rename to java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref
deleted file mode 100644
index 2b35cd6921e..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref
+++ /dev/null
@@ -1 +0,0 @@
-experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.expected
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
rename to java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.expected
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.java
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
rename to java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.java
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
new file mode 100644
index 00000000000..7831abd3b6b
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
From 3643c9e658b0b0ce69de826192f1ba8cc03e18cb Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 29 Sep 2022 14:09:39 -0400
Subject: [PATCH 015/465] update metadata
---
.../src/Security/CWE/CWE-326/InsufficientKeySize.ql | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 4510bbbc869..5607ec1bb87 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -1,9 +1,10 @@
/**
- * @name Weak encryption: Insufficient key size
- * @description Finds uses of encryption algorithms with too small a key size
- * @kind problem
- * @problem.severity warning
- * @precision medium
+ * @name Use of a cryptographic algorithm with insufficient key size
+ * @description Using cryptographic algorithms with too small a key size can
+ * allow an attacker to compromise security.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
* @id java/insufficient-key-size
* @tags security
* external/cwe/cwe-326
From 657e1e62ca07932f7ae50d06ee3350af9ed0d5f4 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 29 Sep 2022 15:25:08 -0400
Subject: [PATCH 016/465] start refactoring query logic into lib file
---
.../semmle/code/java/security/Encryption.qll | 4 +-
.../security/InsufficientKeySizeQuery.qll | 145 ++++++++++++++++++
.../CWE/CWE-326/InsufficientKeySize.ql | 136 +---------------
.../CWE-326/InsufficientKeySize.qlref | 2 +-
4 files changed, 151 insertions(+), 136 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 39e3f2d2110..9659fb92843 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -249,7 +249,9 @@ string getASecureAlgorithmName() {
result =
[
"RSA", "SHA256", "SHA512", "CCM", "GCM", "AES(?)",
- "Blowfish", "ECIES"
+ "Blowfish", "ECIES" // ! Blowfish not actually secure based on https://rules.sonarsource.com/java/type/Vulnerability/RSPEC-4426 ??
+ // ! hmm, other sources imply that it is secure...
+ // ! also no DH here, etc.?
]
}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
new file mode 100644
index 00000000000..5e883eca216
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -0,0 +1,145 @@
+import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+class ECGenParameterSpec extends RefType {
+ ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyGenerator and
+ this.hasName("init")
+ }
+}
+
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyPairGenerator and
+ this.hasName("initialize")
+ }
+}
+
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
+/** Taint configuration tracking flow from a key generator to a `init` method call. */
+class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaxCryptoKeyGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/** Taint configuration tracking flow from a keypair generator to a `initialize` method call. */
+class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaSecurityKeyPairGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/** Holds if a symmetric `KeyGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+bindingset[type]
+predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ exists(
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue() = type and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ cc.hasFlowPath(source, dest)
+ ) and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 128 and
+ msg = "Key size should be at least 128 bits for " + type + " encryption."
+}
+
+/** Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+predicate hasShortAESKey(MethodAccess ma, string msg) { hasShortSymmetricKey(ma, msg, "AES") }
+
+/** Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+bindingset[type]
+predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest)
+ ) and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ msg = "Key size should be at least 2048 bits for " + type + " encryption."
+}
+
+/** Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "DSA") or hasShortAsymmetricKeyPair(ma, msg, "DH")
+}
+
+/** Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "RSA")
+}
+
+/** Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+predicate hasShortECKeyPair(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest) and
+ DataFlow::localExprFlow(cie, ma.getArgument(0)) and
+ ma.getArgument(0).getType() instanceof ECGenParameterSpec and
+ getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
+ ) and
+ msg = "Key size should be at least 256 bits for EC encryption."
+}
+// ! refactor to something like the below,
+// ! need to adjust select clause then...
+// ! see C# and C++ queries for ideas
+// class EncryptionAlgorithm extends
+// predicate hasInsufficientKeySize() {
+// exists(Expr e, string msg |
+// hasShortAESKey(e, msg) or
+// hasShortDsaKeyPair(e, msg) or
+// hasShortRsaKeyPair(e, msg) or
+// hasShortECKeyPair(e, msg)
+// )
+// }
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 5607ec1bb87..baccf3b42c3 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -2,7 +2,7 @@
* @name Use of a cryptographic algorithm with insufficient key size
* @description Using cryptographic algorithms with too small a key size can
* allow an attacker to compromise security.
- * @kind path-problem
+ * @kind problem
* @problem.severity error
* @precision high
* @id java/insufficient-key-size
@@ -11,139 +11,7 @@
*/
import java
-import semmle.code.java.security.Encryption
-import semmle.code.java.dataflow.TaintTracking
-
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-class ECGenParameterSpec extends RefType {
- ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-class KeyGeneratorInitMethod extends Method {
- KeyGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyGenerator and
- this.hasName("init")
- }
-}
-
-/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-class KeyPairGeneratorInitMethod extends Method {
- KeyPairGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyPairGenerator and
- this.hasName("initialize")
- }
-}
-
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-
-/** Taint configuration tracking flow from a key generator to a `init` method call. */
-class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaxCryptoKeyGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/** Taint configuration tracking flow from a keypair generator to a `initialize` method call. */
-class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaSecurityKeyPairGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/** Holds if a symmetric `KeyGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-bindingset[type]
-predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
- DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue() = type and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- cc.hasFlowPath(source, dest)
- ) and
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 128 and
- msg = "Key size should be at least 128 bits for " + type + " encryption."
-}
-
-/** Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortAESKey(MethodAccess ma, string msg) { hasShortSymmetricKey(ma, msg, "AES") }
-
-/** Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-bindingset[type]
-predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest)
- ) and
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
- msg = "Key size should be at least 2048 bits for " + type + " encryption."
-}
-
-/** Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "DSA") or hasShortAsymmetricKeyPair(ma, msg, "DH")
-}
-
-/** Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "RSA")
-}
-
-/** Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortECKeyPair(MethodAccess ma, string msg) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest) and
- DataFlow::localExprFlow(cie, ma.getArgument(0)) and
- ma.getArgument(0).getType() instanceof ECGenParameterSpec and
- getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
- ) and
- msg = "Key size should be at least 256 bits for EC encryption."
-}
+import semmle.code.java.security.InsufficientKeySizeQuery
from Expr e, string msg
where
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
index 7831abd3b6b..040c083a1a1 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
@@ -1 +1 @@
-experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+Security/CWE/CWE-326/InsufficientKeySize.ql
From 9eb45c378731c15f58315a01d3c799da04e96c73 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 3 Oct 2022 15:39:09 -0400
Subject: [PATCH 017/465] refactor tests and code, update help file
---
.../security/InsufficientKeySizeQuery.qll | 91 ++++++++++++-------
.../CWE/CWE-326/InsufficientKeySize.qhelp | 75 ++++++++++++---
.../CWE/CWE-326/InsufficientKeySize.ql | 10 +-
.../CWE/CWE-326/InsufficientKeySizeBad.java | 16 ++++
.../CWE/CWE-326/InsufficientKeySizeGood.java | 16 ++++
...Size.java => InsufficientKeySize_OLD.java} | 0
.../CWE-326/InsufficientKeySize.qlref | 1 -
.../CWE-326/InsufficientKeySizeTest.expected | 0
...Size.java => InsufficientKeySizeTest.java} | 44 ++++-----
.../CWE-326/InsufficientKeySizeTest.ql | 18 ++++
...xpected => InsufficientKeySizeTestOLD.txt} | 0
11 files changed, 196 insertions(+), 75 deletions(-)
create mode 100644 java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java
create mode 100644 java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java
rename java/ql/src/Security/CWE/CWE-326/{InsufficientKeySize.java => InsufficientKeySize_OLD.java} (100%)
delete mode 100644 java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
create mode 100644 java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.expected
rename java/ql/test/query-tests/security/CWE-326/{InsufficientKeySize.java => InsufficientKeySizeTest.java} (71%)
create mode 100644 java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
rename java/ql/test/query-tests/security/CWE-326/{InsufficientKeySize.expected => InsufficientKeySizeTestOLD.txt} (100%)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 5e883eca216..eaba8977c23 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -2,12 +2,12 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
/** The Java class `java.security.spec.ECGenParameterSpec`. */
-class ECGenParameterSpec extends RefType {
+private class ECGenParameterSpec extends RefType {
ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-class KeyGeneratorInitMethod extends Method {
+private class KeyGeneratorInitMethod extends Method {
KeyGeneratorInitMethod() {
this.getDeclaringType() instanceof KeyGenerator and
this.hasName("init")
@@ -15,7 +15,7 @@ class KeyGeneratorInitMethod extends Method {
}
/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-class KeyPairGeneratorInitMethod extends Method {
+private class KeyPairGeneratorInitMethod extends Method {
KeyPairGeneratorInitMethod() {
this.getDeclaringType() instanceof KeyPairGenerator and
this.hasName("initialize")
@@ -24,7 +24,7 @@ class KeyPairGeneratorInitMethod extends Method {
/** Returns the key size in the EC algorithm string */
bindingset[algorithm]
-int getECKeySize(string algorithm) {
+private int getECKeySize(string algorithm) {
algorithm.matches("sec%") and // specification such as "secp256r1"
result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
or
@@ -36,7 +36,7 @@ int getECKeySize(string algorithm) {
}
/** Taint configuration tracking flow from a key generator to a `init` method call. */
-class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -51,8 +51,11 @@ class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
}
}
-/** Taint configuration tracking flow from a keypair generator to a `initialize` method call. */
-class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+/**
+ * Taint configuration tracking flow from a keypair generator to
+ * an `initialize` method call.
+ */
+private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -67,9 +70,14 @@ class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
}
}
-/** Holds if a symmetric `KeyGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+/**
+ * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
bindingset[type]
-predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
+private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyGeneratorInitMethod and
exists(
JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
@@ -84,12 +92,22 @@ predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
msg = "Key size should be at least 128 bits for " + type + " encryption."
}
-/** Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortAESKey(MethodAccess ma, string msg) { hasShortSymmetricKey(ma, msg, "AES") }
+/**
+ * Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size.
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortAESKey(MethodAccess ma, string msg) {
+ hasShortSymmetricKey(ma, msg, "AES")
+}
-/** Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
+/**
+ * Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
bindingset[type]
-predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
+private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
exists(
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
@@ -104,18 +122,31 @@ predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
msg = "Key size should be at least 2048 bits for " + type + " encryption."
}
-/** Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "DSA") or hasShortAsymmetricKeyPair(ma, msg, "DH")
+/**
+ * Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "DSA") or
+ hasShortAsymmetricKeyPair(ma, msg, "DH")
}
-/** Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
+/**
+ * Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
hasShortAsymmetricKeyPair(ma, msg, "RSA")
}
-/** Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
-predicate hasShortECKeyPair(MethodAccess ma, string msg) {
+/**
+ * Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortECKeyPair(MethodAccess ma, string msg) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
exists(
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
@@ -131,15 +162,11 @@ predicate hasShortECKeyPair(MethodAccess ma, string msg) {
) and
msg = "Key size should be at least 256 bits for EC encryption."
}
-// ! refactor to something like the below,
-// ! need to adjust select clause then...
-// ! see C# and C++ queries for ideas
-// class EncryptionAlgorithm extends
-// predicate hasInsufficientKeySize() {
-// exists(Expr e, string msg |
-// hasShortAESKey(e, msg) or
-// hasShortDsaKeyPair(e, msg) or
-// hasShortRsaKeyPair(e, msg) or
-// hasShortECKeyPair(e, msg)
-// )
-// }
+
+// ! refactor this so can use 'path-problem' select clause instead?
+predicate hasInsufficientKeySize(Expr e, string msg) {
+ hasShortAESKey(e, msg) or
+ hasShortDsaKeyPair(e, msg) or
+ hasShortRsaKeyPair(e, msg) or
+ hasShortECKeyPair(e, msg)
+}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
index 4e5faa0ddf4..47ef1124624 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
@@ -1,29 +1,78 @@
-
+
+
- This rule finds uses of encryption algorithms with too small a key size. Encryption algorithms
-are vulnerable to brute force attack when too small a key size is used.
+ Modern encryption relies on the computational infeasibility of breaking a cipher and decoding its
+ message without the key. As computational power increases, the ability to break ciphers grows, and key
+ sizes need to become larger as a result. Encryption algorithms that use too small of a key size are
+ vulnerable to brute force attacks, which can reveal sensitive data.
- The key should be at least 2048 bits long when using RSA and DSA encryption, 256 bits long when using EC encryption, and 128 bits long when using
-symmetric encryption.
+ Use a key of the recommended size or larger. The key size should be at least 2048 bits for RSA or
+ DSA encryption, 256 bits for elliptic curve (EC) encryption, and 128 bits for symmetric encryption,
+ such as AES.
+
+
+
+ The following code uses encryption with insufficient key sizes.
+
+
+
+
+
+ To fix the code, change the key sizes to be the recommended size or
+ larger for each algorithm.
+
+
+
+
+
+
-
- Wikipedia.
- Key size
+ Wikipedia:
+ Key size.
- SonarSource.
- Cryptographic keys should be robust
+ Wikipedia: Strong cryptography.
+
+
+
+ OWASP:
+ Cryptographic Storage Cheat Sheet.
+
+
+ OWASP:
+ Testing for Weak Encryption.
+
+
+ NIST:
+
+ Transitioning the Use of Cryptographic Algorithms and Key Lengths.
-
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index baccf3b42c3..2787fac00bf 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -1,6 +1,6 @@
/**
- * @name Use of a cryptographic algorithm with insufficient key size
- * @description Using cryptographic algorithms with too small a key size can
+ * @name Insufficient key size used with a cryptographic algorithm
+ * @description Using cryptographic algorithms with too small of a key size can
* allow an attacker to compromise security.
* @kind problem
* @problem.severity error
@@ -14,9 +14,5 @@ import java
import semmle.code.java.security.InsufficientKeySizeQuery
from Expr e, string msg
-where
- hasShortAESKey(e, msg) or
- hasShortDsaKeyPair(e, msg) or
- hasShortRsaKeyPair(e, msg) or
- hasShortECKeyPair(e, msg)
+where hasInsufficientKeySize(e, msg)
select e, msg
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java
new file mode 100644
index 00000000000..641543ca964
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java
@@ -0,0 +1,16 @@
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
+ // BAD: Key size is less than 2048
+ keyPairGen1.initialize(1024);
+
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DSA");
+ // BAD: Key size is less than 2048
+ keyPairGen2.initialize(1024);
+
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 256
+ ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
+ keyPairGen3.initialize(ecSpec1);
+
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ // BAD: Key size is less than 128
+ keyGen.init(64);
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java
new file mode 100644
index 00000000000..051f7dd2597
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java
@@ -0,0 +1,16 @@
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
+ // GOOD: Key size is no less than 2048
+ keyPairGen1.initialize(2048);
+
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DSA");
+ // GOOD: Key size is no less than 2048
+ keyPairGen2.initialize(2048);
+
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("EC");
+ // GOOD: Key size is no less than 256
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ keyPairGen3.initialize(ecSpec);
+
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ // GOOD: Key size is no less than 128
+ keyGen.init(128);
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize_OLD.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.java
rename to java/ql/src/Security/CWE/CWE-326/InsufficientKeySize_OLD.java
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
deleted file mode 100644
index 040c083a1a1..00000000000
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-326/InsufficientKeySize.ql
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.expected b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
similarity index 71%
rename from java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.java
rename to java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index a606017a27e..fbff6187855 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -2,92 +2,92 @@ import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import javax.crypto.KeyGenerator;
-public class InsufficientKeySize {
+public class InsufficientKeySizeTest {
public void CryptoMethod() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
// BAD: Key size is less than 128
- keyGen1.init(64);
+ keyGen1.init(64); // $ hasInsufficientKeySize
KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
// GOOD: Key size is no less than 128
- keyGen2.init(128);
+ keyGen2.init(128); // Safe
KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
// BAD: Key size is less than 2048
- keyPairGen1.initialize(1024);
+ keyPairGen1.initialize(1024); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
// GOOD: Key size is no less than 2048
- keyPairGen2.initialize(2048);
+ keyPairGen2.initialize(2048); // Safe
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
// BAD: Key size is less than 2048
- keyPairGen3.initialize(1024);
+ keyPairGen3.initialize(1024); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
// GOOD: Key size is no less than 2048
- keyPairGen4.initialize(2048);
+ keyPairGen4.initialize(2048); // Safe
KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
- keyPairGen5.initialize(ecSpec1);
+ keyPairGen5.initialize(ecSpec1); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
- keyPairGen6.initialize(new ECGenParameterSpec("secp112r1"));
+ keyPairGen6.initialize(new ECGenParameterSpec("secp112r1")); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
// GOOD: Key size is no less than 256
ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
- keyPairGen7.initialize(ecSpec2);
+ keyPairGen7.initialize(ecSpec2); // Safe
KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2");
- keyPairGen8.initialize(ecSpec3);
+ keyPairGen8.initialize(ecSpec3); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3");
- keyPairGen9.initialize(ecSpec4);
+ keyPairGen9.initialize(ecSpec4); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
- keyPairGen10.initialize(ecSpec5);
+ keyPairGen10.initialize(ecSpec5); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
// GOOD: Key size is no less than 256
ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1");
- keyPairGen11.initialize(ecSpec6);
+ keyPairGen11.initialize(ecSpec6); // Safe
KeyPairGenerator keyPairGen12 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2");
- keyPairGen12.initialize(ecSpec7);
+ keyPairGen12.initialize(ecSpec7); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen13 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is no less than 256
+ // BAD: Key size is no less than 256 // ! I think this comment is wrong - double-check
ECGenParameterSpec ecSpec8 = new ECGenParameterSpec("prime256v1");
- keyPairGen13.initialize(ecSpec8);
+ keyPairGen13.initialize(ecSpec8); // Safe
KeyPairGenerator keyPairGen14 = KeyPairGenerator.getInstance("EC");
// BAD: Key size is less than 256
ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1");
- keyPairGen14.initialize(ecSpec9);
+ keyPairGen14.initialize(ecSpec9); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen15 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is no less than 256
+ // BAD: Key size is no less than 256 // ! I think this comment is wrong - double-check
ECGenParameterSpec ecSpec10 = new ECGenParameterSpec("c2tnb431r1");
- keyPairGen15.initialize(ecSpec10);
+ keyPairGen15.initialize(ecSpec10); // Safe
KeyPairGenerator keyPairGen16 = KeyPairGenerator.getInstance("dh");
// BAD: Key size is less than 2048
- keyPairGen16.initialize(1024);
+ keyPairGen16.initialize(1024); // $ hasInsufficientKeySize
KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
// GOOD: Key size is no less than 2048
- keyPairGen17.initialize(2048);
+ keyPairGen17.initialize(2048); // Safe
}
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
new file mode 100644
index 00000000000..b8133b154e4
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -0,0 +1,18 @@
+import java
+import TestUtilities.InlineExpectationsTest
+import semmle.code.java.security.InsufficientKeySizeQuery
+
+class InsufficientKeySizeTest extends InlineExpectationsTest {
+ InsufficientKeySizeTest() { this = "InsufficientKeySize" }
+
+ override string getARelevantTag() { result = "hasInsufficientKeySize" }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ tag = "hasInsufficientKeySize" and
+ exists(Expr e, string msg | hasInsufficientKeySize(e, msg) |
+ e.getLocation() = location and
+ element = e.toString() and
+ value = ""
+ )
+ }
+}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.expected b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTestOLD.txt
similarity index 100%
rename from java/ql/test/query-tests/security/CWE-326/InsufficientKeySize.expected
rename to java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTestOLD.txt
From 7d94590d79c263cd5b9f59897f8e55b3b59ab1a5 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 3 Oct 2022 15:45:11 -0400
Subject: [PATCH 018/465] add change note
---
java/ql/src/change-notes/2022-10-03-insufficient-key-size.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 java/ql/src/change-notes/2022-10-03-insufficient-key-size.md
diff --git a/java/ql/src/change-notes/2022-10-03-insufficient-key-size.md b/java/ql/src/change-notes/2022-10-03-insufficient-key-size.md
new file mode 100644
index 00000000000..b0cf92e1e5a
--- /dev/null
+++ b/java/ql/src/change-notes/2022-10-03-insufficient-key-size.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* Added a new query, `java/insufficient-key-size`, to detect the use of cryptographic algorithms with insufficient key sizes.
From 75794ec7a7041c8ae3d5774c1d7fe601b2f4efe2 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 4 Oct 2022 11:59:20 -0400
Subject: [PATCH 019/465] false negative testing - before rewrite for variable
dataflow
---
.../security/InsufficientKeySizeQuery.qll | 14 +++++++++--
.../CWE-326/InsufficientKeySizeTest.java | 23 ++++++++++++++++++-
2 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index eaba8977c23..d6943adebc5 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,5 +1,6 @@
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.DataFlow3
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class ECGenParameterSpec extends RefType {
@@ -86,9 +87,18 @@ private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type)
jcg.getAlgoSpec().(StringLiteral).getValue() = type and
source.getNode().asExpr() = jcg and
dest.getNode().asExpr() = ma.getQualifier() and
- cc.hasFlowPath(source, dest)
+ //ma.getArgument(0) = var and // ! me
+ //var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and // ! me
+ cc.hasFlowPath(source, dest) //and
+ //var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 // ! me
+ ) and
+ exists(VarAccess var |
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
+ //DataFlow3::localExprFlow(var, ma.getArgument(0)) and
+ ma.getArgument(0) = var
+ //ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
) and
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 128 and
msg = "Key size should be at least 128 bits for " + type + " encryption."
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index fbff6187855..d070eaf3d76 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -3,7 +3,7 @@ import java.security.spec.ECGenParameterSpec;
import javax.crypto.KeyGenerator;
public class InsufficientKeySizeTest {
- public void CryptoMethod() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ public void cryptoMethod() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
// BAD: Key size is less than 128
keyGen1.init(64); // $ hasInsufficientKeySize
@@ -89,5 +89,26 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
// GOOD: Key size is no less than 2048
keyPairGen17.initialize(2048); // Safe
+
+
+ // FN: Test with variables as numbers
+ final int size1 = 64;
+ KeyGenerator keyGen3 = KeyGenerator.getInstance("AES");
+ // BAD: Key size is less than 128
+ keyGen3.init(size1); // $ hasInsufficientKeySize
+
+ int size2 = 1024;
+ KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
+ // BAD: Key size is less than 2048
+ keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
+
+ int keysize = 64;
+ test(keysize);
+ }
+
+ public void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
+ // BAD: Key size is less than 128
+ keyGen4.init(keySize); // $ hasInsufficientKeySize
}
}
From 7de9c05c9d3d9b5b1359ac982d5f6e103c3f725b Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 4 Oct 2022 14:39:39 -0400
Subject: [PATCH 020/465] use CompileTimeConstantExpr for FN with VarAccess,
and remove KeyGeneratorInitConfiguration
---
.../security/InsufficientKeySizeQuery.qll | 68 +++----
...ientKeySizeQuery_BeforeDataFlowRewrite.qll | 182 ++++++++++++++++++
.../CWE-326/InsufficientKeySizeTest.java | 22 +--
3 files changed, 227 insertions(+), 45 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index d6943adebc5..8cc91e4ce0a 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -36,22 +36,19 @@ private int getECKeySize(string algorithm) {
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
-/** Taint configuration tracking flow from a key generator to a `init` method call. */
-private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaxCryptoKeyGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
+// /** Taint configuration tracking flow from a key generator to a `init` method call. */
+// private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+// KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+// override predicate isSource(DataFlow::Node source) {
+// source.asExpr() instanceof JavaxCryptoKeyGenerator
+// }
+// override predicate isSink(DataFlow::Node sink) {
+// exists(MethodAccess ma |
+// ma.getMethod() instanceof KeyGeneratorInitMethod and
+// sink.asExpr() = ma.getQualifier()
+// )
+// }
+// }
/**
* Taint configuration tracking flow from a keypair generator to
* an `initialize` method call.
@@ -80,24 +77,27 @@ private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configura
bindingset[type]
private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyGeneratorInitMethod and
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
- DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue() = type and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- //ma.getArgument(0) = var and // ! me
- //var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and // ! me
- cc.hasFlowPath(source, dest) //and
- //var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 // ! me
- ) and
- exists(VarAccess var |
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- //DataFlow3::localExprFlow(var, ma.getArgument(0)) and
- ma.getArgument(0) = var
- //ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
+ // exists(JavaxCryptoKeyGenerator jcg, DataFlow::PathNode source, DataFlow::PathNode dest |
+ // jcg.getAlgoSpec().(StringLiteral).getValue() = type //and
+ // //source.getNode().asExpr() = jcg and
+ // //dest.getNode().asExpr() = ma.getQualifier() //and
+ // //cc.hasFlowPath(source, dest)
+ // ) and
+ (
+ // exists(VarAccess var |
+ // var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ // var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
+ // ma.getArgument(0) = var
+ // )
+ // or
+ // below is better than above?
+ exists(CompileTimeConstantExpr var |
+ //var.getUnderlyingExpr() instanceof IntegerLiteral and // can't include this...
+ var.getIntValue() < 128 and
+ ma.getArgument(0) = var
+ )
+ or
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
) and
msg = "Key size should be at least 128 bits for " + type + " encryption."
}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll
new file mode 100644
index 00000000000..d6943adebc5
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll
@@ -0,0 +1,182 @@
+import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.DataFlow3
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class ECGenParameterSpec extends RefType {
+ ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+private class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyGenerator and
+ this.hasName("init")
+ }
+}
+
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+private class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyPairGenerator and
+ this.hasName("initialize")
+ }
+}
+
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
+/** Taint configuration tracking flow from a key generator to a `init` method call. */
+private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaxCryptoKeyGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/**
+ * Taint configuration tracking flow from a keypair generator to
+ * an `initialize` method call.
+ */
+private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaSecurityKeyPairGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/**
+ * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+bindingset[type]
+private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ exists(
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue() = type and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ //ma.getArgument(0) = var and // ! me
+ //var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and // ! me
+ cc.hasFlowPath(source, dest) //and
+ //var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 // ! me
+ ) and
+ exists(VarAccess var |
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
+ //DataFlow3::localExprFlow(var, ma.getArgument(0)) and
+ ma.getArgument(0) = var
+ //ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
+ ) and
+ msg = "Key size should be at least 128 bits for " + type + " encryption."
+}
+
+/**
+ * Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size.
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortAESKey(MethodAccess ma, string msg) {
+ hasShortSymmetricKey(ma, msg, "AES")
+}
+
+/**
+ * Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+bindingset[type]
+private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest)
+ ) and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ msg = "Key size should be at least 2048 bits for " + type + " encryption."
+}
+
+/**
+ * Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "DSA") or
+ hasShortAsymmetricKeyPair(ma, msg, "DH")
+}
+
+/**
+ * Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "RSA")
+}
+
+/**
+ * Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortECKeyPair(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest) and
+ DataFlow::localExprFlow(cie, ma.getArgument(0)) and
+ ma.getArgument(0).getType() instanceof ECGenParameterSpec and
+ getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
+ ) and
+ msg = "Key size should be at least 256 bits for EC encryption."
+}
+
+// ! refactor this so can use 'path-problem' select clause instead?
+predicate hasInsufficientKeySize(Expr e, string msg) {
+ hasShortAESKey(e, msg) or
+ hasShortDsaKeyPair(e, msg) or
+ hasShortRsaKeyPair(e, msg) or
+ hasShortECKeyPair(e, msg)
+}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index d070eaf3d76..dfffe740a3f 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -97,18 +97,18 @@ public class InsufficientKeySizeTest {
// BAD: Key size is less than 128
keyGen3.init(size1); // $ hasInsufficientKeySize
- int size2 = 1024;
- KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
- // BAD: Key size is less than 2048
- keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
+ // int size2 = 1024;
+ // KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
+ // // BAD: Key size is less than 2048
+ // keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
- int keysize = 64;
- test(keysize);
+ // int keysize = 64;
+ // test(keysize);
}
- public void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
- // BAD: Key size is less than 128
- keyGen4.init(keySize); // $ hasInsufficientKeySize
- }
+ // public void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
+ // // BAD: Key size is less than 128
+ // keyGen4.init(keySize); // $ hasInsufficientKeySize
+ // }
}
From d3b1a04c133548e01f341df306d01f527ae6cbf7 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 4 Oct 2022 20:46:55 -0400
Subject: [PATCH 021/465] handle FN case with simple VarAccess; add draft of
dataflow config to handle complex VarAccess
---
.../security/InsufficientKeySizeQuery.qll | 117 +++++++++++++-----
.../CWE/CWE-326/InsufficientKeySize.ql | 11 +-
.../CWE-326/InsufficientKeySizeTest.java | 22 ++--
3 files changed, 104 insertions(+), 46 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 8cc91e4ce0a..39ded30d012 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,6 +1,32 @@
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
-import semmle.code.java.dataflow.DataFlow3
+import semmle.code.java.dataflow.DataFlow
+
+//import DataFlow::PathGraph
+/**
+ * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ */
+class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+ AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(IntegerLiteral integer, VarAccess var |
+ integer.getIntValue() < 2048 and
+ source.asExpr() = integer
+ or
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
+ source.asExpr() = var.getVariable().getInitializer()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class ECGenParameterSpec extends RefType {
@@ -36,19 +62,22 @@ private int getECKeySize(string algorithm) {
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
-// /** Taint configuration tracking flow from a key generator to a `init` method call. */
-// private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
-// KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-// override predicate isSource(DataFlow::Node source) {
-// source.asExpr() instanceof JavaxCryptoKeyGenerator
-// }
-// override predicate isSink(DataFlow::Node sink) {
-// exists(MethodAccess ma |
-// ma.getMethod() instanceof KeyGeneratorInitMethod and
-// sink.asExpr() = ma.getQualifier()
-// )
-// }
-// }
+/** Taint configuration tracking flow from a key generator to a `init` method call. */
+private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaxCryptoKeyGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
/**
* Taint configuration tracking flow from a keypair generator to
* an `initialize` method call.
@@ -77,26 +106,33 @@ private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configura
bindingset[type]
private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyGeneratorInitMethod and
- // exists(JavaxCryptoKeyGenerator jcg, DataFlow::PathNode source, DataFlow::PathNode dest |
- // jcg.getAlgoSpec().(StringLiteral).getValue() = type //and
- // //source.getNode().asExpr() = jcg and
- // //dest.getNode().asExpr() = ma.getQualifier() //and
- // //cc.hasFlowPath(source, dest)
- // ) and
+ // flow needed to correctly determine algorithm type and
+ // not match to ANY symmetric algorithm (although doesn't really matter since only have AES currently...)
+ exists(
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue() = type and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ cc.hasFlowPath(source, dest)
+ ) and
(
- // exists(VarAccess var |
- // var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- // var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- // ma.getArgument(0) = var
- // )
- // or
- // below is better than above?
- exists(CompileTimeConstantExpr var |
- //var.getUnderlyingExpr() instanceof IntegerLiteral and // can't include this...
- var.getIntValue() < 128 and
+ // VarAccess case needed to handle FN of key-size stored in a variable
+ // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
+ // (e.g. not declared `final` in Java)
+ exists(VarAccess var |
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
ma.getArgument(0) = var
)
or
+ // exists(CompileTimeConstantExpr var |
+ // //var.getUnderlyingExpr() instanceof IntegerLiteral and // can't include this...
+ // var.getIntValue() < 128 and
+ // ma.getArgument(0) = var
+ // )
+ // or
ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
) and
msg = "Key size should be at least 128 bits for " + type + " encryption."
@@ -119,6 +155,8 @@ private predicate hasShortAESKey(MethodAccess ma, string msg) {
bindingset[type]
private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ // flow needed to correctly determine algorithm type and
+ // not match to ANY asymmetric algorithm
exists(
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
DataFlow::PathNode source, DataFlow::PathNode dest
@@ -128,7 +166,24 @@ private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string
dest.getNode().asExpr() = ma.getQualifier() and
kc.hasFlowPath(source, dest)
) and
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ // VarAccess case needed to handle FN of key-size stored in a variable
+ // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
+ // (e.g. not declared `final` in Java)
+ (
+ exists(VarAccess var |
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
+ ma.getArgument(0) = var
+ )
+ or
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048
+ or
+ exists(
+ AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+ |
+ cfg.hasFlowPath(source, sink)
+ )
+ ) and
msg = "Key size should be at least 2048 bits for " + type + " encryption."
}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 2787fac00bf..149e2d40675 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -2,7 +2,7 @@
* @name Insufficient key size used with a cryptographic algorithm
* @description Using cryptographic algorithms with too small of a key size can
* allow an attacker to compromise security.
- * @kind problem
+ * @kind path-problem
* @problem.severity error
* @precision high
* @id java/insufficient-key-size
@@ -13,6 +13,9 @@
import java
import semmle.code.java.security.InsufficientKeySizeQuery
-from Expr e, string msg
-where hasInsufficientKeySize(e, msg)
-select e, msg
+// from Expr e, string msg
+// where hasInsufficientKeySize(e, msg)
+// select e, msg
+from AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+where cfg.hasFlowPath(source, sink)
+select sink, source, sink, "The size of this RSA key should be at least 2048 bits."
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index dfffe740a3f..4bd29e2b3e8 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -97,18 +97,18 @@ public class InsufficientKeySizeTest {
// BAD: Key size is less than 128
keyGen3.init(size1); // $ hasInsufficientKeySize
- // int size2 = 1024;
- // KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
- // // BAD: Key size is less than 2048
- // keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
+ int size2 = 1024;
+ KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
+ // BAD: Key size is less than 2048
+ keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
- // int keysize = 64;
- // test(keysize);
+ int keysize = 1024;
+ test(keysize);
}
- // public void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
- // // BAD: Key size is less than 128
- // keyGen4.init(keySize); // $ hasInsufficientKeySize
- // }
+ public void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ KeyPairGenerator keyPairGen19 = KeyPairGenerator.getInstance("RSA");
+ // BAD: Key size is less than 128
+ keyPairGen19.initialize(keySize); // $ hasInsufficientKeySize
+ }
}
From 8ffd2522e79ad22470e33e0e43a70e99f38cd287 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 4 Oct 2022 23:21:15 -0400
Subject: [PATCH 022/465] add draft code to find algo type to replace
tainttracking configs
---
.../semmle/code/java/security/Encryption.qll | 1 +
.../security/InsufficientKeySizeQuery.qll | 20 +++++++++++++++++++
.../CWE/CWE-326/InsufficientKeySize.ql | 5 ++++-
.../CWE-326/InsufficientKeySizeTest.java | 2 +-
4 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 9659fb92843..c19d51a2f4b 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -252,6 +252,7 @@ string getASecureAlgorithmName() {
"Blowfish", "ECIES" // ! Blowfish not actually secure based on https://rules.sonarsource.com/java/type/Vulnerability/RSPEC-4426 ??
// ! hmm, other sources imply that it is secure...
// ! also no DH here, etc.?
+ // ! also is ECB matched with AES?
]
}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 39ded30d012..9aef9b44c6b 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -155,6 +155,26 @@ private predicate hasShortAESKey(MethodAccess ma, string msg) {
bindingset[type]
private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ ma.getQualifier() instanceof JavaSecurityKeyPairGenerator and
+ ma.getQualifier().getBasicBlock() instanceof JavaSecurityKeyPairGenerator and
+ //ma.getQualifier().getBasicBlock().getNode(2) instanceof JavaSecurityKeyPairGenerator and
+ // ma.getQualifier()
+ // .getBasicBlock()
+ // .getANode()
+ // .(JavaSecurityKeyPairGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase() = type and
+ //ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
+ ma.getQualifier()
+ .getBasicBlock()
+ .getAPredecessor()
+ .(JavaSecurityKeyPairGenerator)
+ .getAlgoSpec()
+ .(StringLiteral)
+ .getValue()
+ .toUpperCase() = type and
// flow needed to correctly determine algorithm type and
// not match to ANY asymmetric algorithm
exists(
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 149e2d40675..66bf459f5f7 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -4,6 +4,7 @@
* allow an attacker to compromise security.
* @kind path-problem
* @problem.severity error
+ * @security-severity 7.5
* @precision high
* @id java/insufficient-key-size
* @tags security
@@ -12,10 +13,12 @@
import java
import semmle.code.java.security.InsufficientKeySizeQuery
+import DataFlow::PathGraph
// from Expr e, string msg
// where hasInsufficientKeySize(e, msg)
// select e, msg
from AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
-select sink, source, sink, "The size of this RSA key should be at least 2048 bits."
+select sink.getNode(), source, sink, "The $@ of an asymmetric key should be at least 2048 bits.",
+ sink.getNode(), "size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 4bd29e2b3e8..c5f151128fa 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -106,7 +106,7 @@ public class InsufficientKeySizeTest {
test(keysize);
}
- public void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ public static void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen19 = KeyPairGenerator.getInstance("RSA");
// BAD: Key size is less than 128
keyPairGen19.initialize(keySize); // $ hasInsufficientKeySize
From ac707198d5437a69e1b1154865b848d4dc01ab82 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 5 Oct 2022 09:48:51 -0400
Subject: [PATCH 023/465] commit before adding taint flow back (since no taint
flow doesn't capture all cases)
---
.../security/InsufficientKeySizeQuery.qll | 41 +++++++++++--------
.../CWE-326/InsufficientKeySizeTest.java | 7 +++-
2 files changed, 29 insertions(+), 19 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 9aef9b44c6b..720ed112981 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -155,8 +155,11 @@ private predicate hasShortAESKey(MethodAccess ma, string msg) {
bindingset[type]
private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- ma.getQualifier() instanceof JavaSecurityKeyPairGenerator and
- ma.getQualifier().getBasicBlock() instanceof JavaSecurityKeyPairGenerator and
+ //ma.getQualifier() instanceof JavaSecurityKeyPairGenerator and
+ //ma.getQualifier().getBasicBlock() instanceof JavaSecurityKeyPairGenerator and
+ // * USE BELOW
+ ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
+ // * USE ABOVE
//ma.getQualifier().getBasicBlock().getNode(2) instanceof JavaSecurityKeyPairGenerator and
// ma.getQualifier()
// .getBasicBlock()
@@ -167,6 +170,7 @@ private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string
// .getValue()
// .toUpperCase() = type and
//ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
+ // * USE BELOW
ma.getQualifier()
.getBasicBlock()
.getAPredecessor()
@@ -175,17 +179,20 @@ private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string
.(StringLiteral)
.getValue()
.toUpperCase() = type and
+ // * USE ABOVE
// flow needed to correctly determine algorithm type and
// not match to ANY asymmetric algorithm
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest)
- ) and
+ // * REMOVE BELOW
+ // exists(
+ // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ // DataFlow::PathNode source, DataFlow::PathNode dest
+ // |
+ // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
+ // source.getNode().asExpr() = jpg and
+ // dest.getNode().asExpr() = ma.getQualifier() and
+ // kc.hasFlowPath(source, dest)
+ // ) and
+ // * REMOVE ABOVE
// VarAccess case needed to handle FN of key-size stored in a variable
// Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
// (e.g. not declared `final` in Java)
@@ -197,12 +204,12 @@ private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string
)
or
ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048
- or
- exists(
- AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
- |
- cfg.hasFlowPath(source, sink)
- )
+ // or
+ // exists(
+ // AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+ // |
+ // cfg.hasFlowPath(source, sink)
+ // )
) and
msg = "Key size should be at least 2048 bits for " + type + " encryption."
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index c5f151128fa..9d9f5688520 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -103,12 +103,15 @@ public class InsufficientKeySizeTest {
keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
int keysize = 1024;
- test(keysize);
+ KeyPairGenerator keyPairGen20 = KeyPairGenerator.getInstance("DSA");
+ test(keysize, keyPairGen20);
}
- public static void test(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ public static void test(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen19 = KeyPairGenerator.getInstance("RSA");
// BAD: Key size is less than 128
keyPairGen19.initialize(keySize); // $ hasInsufficientKeySize
+
+ kpg.initialize(1024); // $ hasInsufficientKeySize
}
}
From 5e2ef660148f2840c3497f4ea3f483d6cbb35ae2 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 6 Oct 2022 00:40:42 -0400
Subject: [PATCH 024/465] refactoring to use both dataflow configs; commit
before deleting unused code
---
.../security/InsufficientKeySizeQuery.qll | 141 +++++++++-
...BeforeAddingTaintFlowBackForAsymmetric.qll | 264 ++++++++++++++++++
.../CWE/CWE-326/InsufficientKeySize.ql | 18 +-
.../CWE-326/InsufficientKeySizeTest.java | 245 ++++++++++------
4 files changed, 579 insertions(+), 89 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 720ed112981..869f3c7cb0f 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -2,7 +2,7 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
-//import DataFlow::PathGraph
+// ******* DATAFLOW *******************************************************************************
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
@@ -14,6 +14,8 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
integer.getIntValue() < 2048 and
source.asExpr() = integer
or
+ // The below only handles cases when variables are used (both locally in a method and between methods)
+ // The above adds handling for direct use of integers as well
var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
source.asExpr() = var.getVariable().getInitializer()
@@ -28,11 +30,101 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
}
}
+/**
+ * Symmetric (AES) key length data flow tracking configuration.
+ */
+class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+ SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(IntegerLiteral integer, VarAccess var |
+ integer.getIntValue() < 128 and
+ source.asExpr() = integer
+ or
+ // The below only handles cases when variables are used (both locally in a method and between methods)
+ // The above adds handling for direct use of integers as well
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
+ source.asExpr() = var.getVariable().getInitializer()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+/**
+ * Symmetric (AES) key length data flow tracking configuration.
+ */
+class SymmetricKeyTrackingConfiguration2 extends DataFlow::Configuration {
+ SymmetricKeyTrackingConfiguration2() { this = "SymmetricKeyTrackingConfiguration2" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof IntegerLiteral and
+ source.toString().toInt() < 128
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+class UnsafeSymmetricKeySize extends IntegerLiteral {
+ UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
+}
+
+class UnsafeAsymmetricKeySize extends IntegerLiteral {
+ UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
+}
+
+class UnsafeKeySize extends IntegerLiteral {
+ UnsafeKeySize() {
+ this instanceof UnsafeAsymmetricKeySize and
+ exists(MethodAccess ma | ma.getMethod() instanceof KeyPairGeneratorInitMethod)
+ or
+ this instanceof UnsafeSymmetricKeySize and
+ exists(MethodAccess ma | ma.getMethod() instanceof KeyGeneratorInitMethod)
+ }
+}
+
+class KeyInitMethod extends Method {
+ KeyInitMethod() {
+ this instanceof KeyGeneratorInitMethod or
+ this instanceof KeyPairGeneratorInitMethod
+ }
+}
+
+/**
+ * key length data flow tracking configuration.
+ */
+class KeyTrackingConfiguration extends DataFlow::Configuration {
+ KeyTrackingConfiguration() { this = "KeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof UnsafeKeySize }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyInitMethod and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+// ******* DATAFLOW *******************************************************************************
+// ! move to Encryption.qll?
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class ECGenParameterSpec extends RefType {
ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
+// ! move to Encryption.qll?
/** The `init` method declared in `javax.crypto.KeyGenerator`. */
private class KeyGeneratorInitMethod extends Method {
KeyGeneratorInitMethod() {
@@ -41,6 +133,7 @@ private class KeyGeneratorInitMethod extends Method {
}
}
+// ! move to Encryption.qll?
/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
private class KeyPairGeneratorInitMethod extends Method {
KeyPairGeneratorInitMethod() {
@@ -97,6 +190,46 @@ private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configura
}
}
+/**
+ * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+//bindingset[type]
+private predicate hasShortSymmetricKey_TEST() {
+ exists(
+ SymmetricKeyTrackingConfiguration2 cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+ |
+ cfg.hasFlowPath(source, sink)
+ )
+ // ma.getMethod() instanceof KeyGeneratorInitMethod and
+ // // flow needed to correctly determine algorithm type and
+ // // not match to ANY symmetric algorithm (although doesn't really matter since only have AES currently...)
+ // exists(
+ // JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
+ // DataFlow::PathNode dest
+ // |
+ // jcg.getAlgoSpec().(StringLiteral).getValue() = type and
+ // source.getNode().asExpr() = jcg and
+ // dest.getNode().asExpr() = ma.getQualifier() and
+ // cc.hasFlowPath(source, dest)
+ // ) and
+ // (
+ // // VarAccess case needed to handle FN of key-size stored in a variable
+ // // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
+ // // (e.g. not declared `final` in Java)
+ // exists(VarAccess var |
+ // var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ // var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
+ // ma.getArgument(0) = var
+ // )
+ // or
+ // ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
+ // ) and
+ // msg = "Key size should be at least 128 bits for " + type + " encryption."
+}
+
/**
* Holds if a symmetric `KeyGenerator` implementing encryption algorithm
* `type` and initialized by `ma` uses an insufficient key size.
@@ -262,3 +395,9 @@ predicate hasInsufficientKeySize(Expr e, string msg) {
hasShortRsaKeyPair(e, msg) or
hasShortECKeyPair(e, msg)
}
+
+predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+ or
+ exists(SymmetricKeyTrackingConfiguration2 config2 | config2.hasFlowPath(source, sink))
+}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll
new file mode 100644
index 00000000000..720ed112981
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll
@@ -0,0 +1,264 @@
+import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.DataFlow
+
+//import DataFlow::PathGraph
+/**
+ * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ */
+class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+ AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(IntegerLiteral integer, VarAccess var |
+ integer.getIntValue() < 2048 and
+ source.asExpr() = integer
+ or
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
+ source.asExpr() = var.getVariable().getInitializer()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class ECGenParameterSpec extends RefType {
+ ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+private class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyGenerator and
+ this.hasName("init")
+ }
+}
+
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+private class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyPairGenerator and
+ this.hasName("initialize")
+ }
+}
+
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
+/** Taint configuration tracking flow from a key generator to a `init` method call. */
+private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaxCryptoKeyGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/**
+ * Taint configuration tracking flow from a keypair generator to
+ * an `initialize` method call.
+ */
+private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaSecurityKeyPairGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/**
+ * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+bindingset[type]
+private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ // flow needed to correctly determine algorithm type and
+ // not match to ANY symmetric algorithm (although doesn't really matter since only have AES currently...)
+ exists(
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue() = type and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ cc.hasFlowPath(source, dest)
+ ) and
+ (
+ // VarAccess case needed to handle FN of key-size stored in a variable
+ // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
+ // (e.g. not declared `final` in Java)
+ exists(VarAccess var |
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
+ ma.getArgument(0) = var
+ )
+ or
+ // exists(CompileTimeConstantExpr var |
+ // //var.getUnderlyingExpr() instanceof IntegerLiteral and // can't include this...
+ // var.getIntValue() < 128 and
+ // ma.getArgument(0) = var
+ // )
+ // or
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
+ ) and
+ msg = "Key size should be at least 128 bits for " + type + " encryption."
+}
+
+/**
+ * Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size.
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortAESKey(MethodAccess ma, string msg) {
+ hasShortSymmetricKey(ma, msg, "AES")
+}
+
+/**
+ * Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm
+ * `type` and initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+bindingset[type]
+private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ //ma.getQualifier() instanceof JavaSecurityKeyPairGenerator and
+ //ma.getQualifier().getBasicBlock() instanceof JavaSecurityKeyPairGenerator and
+ // * USE BELOW
+ ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
+ // * USE ABOVE
+ //ma.getQualifier().getBasicBlock().getNode(2) instanceof JavaSecurityKeyPairGenerator and
+ // ma.getQualifier()
+ // .getBasicBlock()
+ // .getANode()
+ // .(JavaSecurityKeyPairGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase() = type and
+ //ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
+ // * USE BELOW
+ ma.getQualifier()
+ .getBasicBlock()
+ .getAPredecessor()
+ .(JavaSecurityKeyPairGenerator)
+ .getAlgoSpec()
+ .(StringLiteral)
+ .getValue()
+ .toUpperCase() = type and
+ // * USE ABOVE
+ // flow needed to correctly determine algorithm type and
+ // not match to ANY asymmetric algorithm
+ // * REMOVE BELOW
+ // exists(
+ // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ // DataFlow::PathNode source, DataFlow::PathNode dest
+ // |
+ // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
+ // source.getNode().asExpr() = jpg and
+ // dest.getNode().asExpr() = ma.getQualifier() and
+ // kc.hasFlowPath(source, dest)
+ // ) and
+ // * REMOVE ABOVE
+ // VarAccess case needed to handle FN of key-size stored in a variable
+ // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
+ // (e.g. not declared `final` in Java)
+ (
+ exists(VarAccess var |
+ var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
+ var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
+ ma.getArgument(0) = var
+ )
+ or
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048
+ // or
+ // exists(
+ // AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+ // |
+ // cfg.hasFlowPath(source, sink)
+ // )
+ ) and
+ msg = "Key size should be at least 2048 bits for " + type + " encryption."
+}
+
+/**
+ * Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "DSA") or
+ hasShortAsymmetricKeyPair(ma, msg, "DH")
+}
+
+/**
+ * Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "RSA")
+}
+
+/**
+ * Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
+ *
+ * `msg` provides a human-readable description of the problem.
+ */
+private predicate hasShortECKeyPair(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest) and
+ DataFlow::localExprFlow(cie, ma.getArgument(0)) and
+ ma.getArgument(0).getType() instanceof ECGenParameterSpec and
+ getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
+ ) and
+ msg = "Key size should be at least 256 bits for EC encryption."
+}
+
+// ! refactor this so can use 'path-problem' select clause instead?
+predicate hasInsufficientKeySize(Expr e, string msg) {
+ hasShortAESKey(e, msg) or
+ hasShortDsaKeyPair(e, msg) or
+ hasShortRsaKeyPair(e, msg) or
+ hasShortECKeyPair(e, msg)
+}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 66bf459f5f7..2979c9116c4 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -18,7 +18,17 @@ import DataFlow::PathGraph
// from Expr e, string msg
// where hasInsufficientKeySize(e, msg)
// select e, msg
-from AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
-where cfg.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "The $@ of an asymmetric key should be at least 2048 bits.",
- sink.getNode(), "size"
+// from
+// AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
+// KeyTrackingConfiguration cfg2 //, DataFlow::PathNode source2, DataFlow::PathNode sink2
+// where
+// //cfg.hasFlowPath(source, sink) //or
+// cfg2.hasFlowPath(source, sink)
+// select sink.getNode(), source, sink, "The $@ of an asymmetric key should be at least 2048 bits.",
+// sink.getNode(), "size"
+from DataFlow::PathNode source, DataFlow::PathNode sink
+where
+ //hasInsufficientKeySize2(source, sink)
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration2 config2 | config2.hasFlowPath(source, sink))
+select sink.getNode(), source, sink, "This $@ is too small.", sink.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 9d9f5688520..68c3e7f322e 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -3,115 +3,192 @@ import java.security.spec.ECGenParameterSpec;
import javax.crypto.KeyGenerator;
public class InsufficientKeySizeTest {
- public void cryptoMethod() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
- // BAD: Key size is less than 128
- keyGen1.init(64); // $ hasInsufficientKeySize
+ public void keySizeTesting() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
- // GOOD: Key size is no less than 128
- keyGen2.init(128); // Safe
+ // Test basic key generation for all algos
- KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
- // BAD: Key size is less than 2048
- keyPairGen1.initialize(1024); // $ hasInsufficientKeySize
+ // AES (Symmetric)
+ {
+ // BAD: Key size is less than 128
+ KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
+ keyGen1.init(64); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
- // GOOD: Key size is no less than 2048
- keyPairGen2.initialize(2048); // Safe
+ // GOOD: Key size is no less than 128
+ KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
+ keyGen2.init(128); // Safe
+ }
- KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
- // BAD: Key size is less than 2048
- keyPairGen3.initialize(1024); // $ hasInsufficientKeySize
+ // RSA (Asymmetric)
+ {
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen1.initialize(1024); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
- // GOOD: Key size is no less than 2048
- keyPairGen4.initialize(2048); // Safe
+ // GOOD: Key size is no less than 2048
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen2.initialize(2048); // Safe
+ }
- KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
- keyPairGen5.initialize(ecSpec1); // $ hasInsufficientKeySize
+ // DSA (Asymmetric)
+ {
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
+ keyPairGen3.initialize(1024); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- keyPairGen6.initialize(new ECGenParameterSpec("secp112r1")); // $ hasInsufficientKeySize
+ // GOOD: Key size is no less than 2048
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
+ keyPairGen4.initialize(2048); // Safe
+ }
- KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 256
- ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
- keyPairGen7.initialize(ecSpec2); // Safe
+ // DH (Asymmetric)
+ {
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen16 = KeyPairGenerator.getInstance("dh");
+ keyPairGen16.initialize(1024); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2");
- keyPairGen8.initialize(ecSpec3); // $ hasInsufficientKeySize
+ // GOOD: Key size is no less than 2048
+ KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
+ keyPairGen17.initialize(2048); // Safe
+ }
- KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3");
- keyPairGen9.initialize(ecSpec4); // $ hasInsufficientKeySize
+ // EC (Asymmetric)
+ // ! Check if I can re-use the same KeyPairGenerator instance with all of the below?
+ {
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
+ keyPairGen5.initialize(ecSpec1); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
- keyPairGen10.initialize(ecSpec5); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
+ keyPairGen6.initialize(new ECGenParameterSpec("secp112r1")); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 256
- ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1");
- keyPairGen11.initialize(ecSpec6); // Safe
+ // GOOD: Key size is no less than 256
+ KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
+ keyPairGen7.initialize(ecSpec2); // Safe
- KeyPairGenerator keyPairGen12 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2");
- keyPairGen12.initialize(ecSpec7); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2");
+ keyPairGen8.initialize(ecSpec3); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen13 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is no less than 256 // ! I think this comment is wrong - double-check
- ECGenParameterSpec ecSpec8 = new ECGenParameterSpec("prime256v1");
- keyPairGen13.initialize(ecSpec8); // Safe
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3");
+ keyPairGen9.initialize(ecSpec4); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen14 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1");
- keyPairGen14.initialize(ecSpec9); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
+ keyPairGen10.initialize(ecSpec5); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen15 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is no less than 256 // ! I think this comment is wrong - double-check
- ECGenParameterSpec ecSpec10 = new ECGenParameterSpec("c2tnb431r1");
- keyPairGen15.initialize(ecSpec10); // Safe
+ // GOOD: Key size is no less than 256
+ KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1");
+ keyPairGen11.initialize(ecSpec6); // Safe
- KeyPairGenerator keyPairGen16 = KeyPairGenerator.getInstance("dh");
- // BAD: Key size is less than 2048
- keyPairGen16.initialize(1024); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen12 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2");
+ keyPairGen12.initialize(ecSpec7); // $ hasInsufficientKeySize
- KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
- // GOOD: Key size is no less than 2048
- keyPairGen17.initialize(2048); // Safe
+ // GOOD: Key size is no less than 256
+ KeyPairGenerator keyPairGen13 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec8 = new ECGenParameterSpec("prime256v1");
+ keyPairGen13.initialize(ecSpec8); // Safe
+
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen14 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1");
+ keyPairGen14.initialize(ecSpec9); // $ hasInsufficientKeySize
+
+ // GOOD: Key size is no less than 256
+ KeyPairGenerator keyPairGen15 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec10 = new ECGenParameterSpec("c2tnb431r1");
+ keyPairGen15.initialize(ecSpec10); // Safe
+ }
+
+ // ! FN Testing Additions:
+
+ // Test local variable usage - Symmetric
+ {
+ final int size1 = 64; // compile-time constant
+ int size2 = 64; // NOT a compile-time constant
+
+ // BAD: Key size is less than 128
+ KeyGenerator keyGen3 = KeyGenerator.getInstance("AES");
+ keyGen3.init(size1); // $ hasInsufficientKeySize
+
+ // BAD: Key size is less than 128
+ KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
+ keyGen4.init(size2); // $ hasInsufficientKeySize
+ }
+
+ // Test local variable usage - Asymmetric, Not EC
+ {
+ final int size1 = 1024; // compile-time constant
+ int size2 = 1024; // NOT a compile-time constant
+
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen18.initialize(size1); // $ hasInsufficientKeySize
+
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen19 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen19.initialize(size2); // $ hasInsufficientKeySize
+ }
- // FN: Test with variables as numbers
- final int size1 = 64;
- KeyGenerator keyGen3 = KeyGenerator.getInstance("AES");
- // BAD: Key size is less than 128
- keyGen3.init(size1); // $ hasInsufficientKeySize
+ // Test variable passed to other method(s) - Symmetric
+ {
+ int size = 64; // test integer variable
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // test KeyGenerator variable
+ testSymmetric(size, keyGen);
+ }
- int size2 = 1024;
- KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
- // BAD: Key size is less than 2048
- keyPairGen18.initialize(size2); // $ hasInsufficientKeySize
- int keysize = 1024;
- KeyPairGenerator keyPairGen20 = KeyPairGenerator.getInstance("DSA");
- test(keysize, keyPairGen20);
+ // Test variables passed to other method(s) - Asymmetric, Not EC
+ {
+ int size = 1024; // test integer variable
+ KeyPairGenerator keyPairGen21 = KeyPairGenerator.getInstance("RSA"); // test KeyPairGenerator variable
+ testAsymmetricNonEC(size, keyPairGen21);
+ }
+
+ // Test variable passed to other method(s) - Asymmetric, EC
+ {
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // test ECGenParameterSpec variable
+ KeyPairGenerator keyPairGen22 = KeyPairGenerator.getInstance("EC"); // test KeyPairGenerator variable
+ testAsymmetricEC(ecSpec, keyPairGen22);
+ }
+
}
- public static void test(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- KeyPairGenerator keyPairGen19 = KeyPairGenerator.getInstance("RSA");
- // BAD: Key size is less than 128
- keyPairGen19.initialize(keySize); // $ hasInsufficientKeySize
+ public static void testSymmetric(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // BAD: Key size is less than 2048
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ keyGen.init(keySize); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 2048
+ kg.init(64); // $ hasInsufficientKeySize
+ }
+
+ public static void testAsymmetricNonEC(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
+ keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
+
+ // BAD: Key size is less than 2048
kpg.initialize(1024); // $ hasInsufficientKeySize
}
+
+ public static void testAsymmetricEC(ECGenParameterSpec spec, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // BAD: Key size is less than 256
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
+ keyPairGen.initialize(spec); // $ hasInsufficientKeySize
+
+ // BAD: Key size is less than 256
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1");
+ kpg.initialize(ecSpec); // $ hasInsufficientKeySize
+ }
}
From c414ee0e254d0a303a6fe187ef7381ea8b60ec57 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 6 Oct 2022 01:32:10 -0400
Subject: [PATCH 025/465] add ECC dataflow config; passes all test cases; still
don't have algo name tracking
---
.../security/InsufficientKeySizeQuery.qll | 337 +++---------------
.../CWE/CWE-326/InsufficientKeySize.ql | 3 +-
.../CWE-326/InsufficientKeySizeTest.java | 20 +-
.../CWE-326/InsufficientKeySizeTest.ql | 10 +-
4 files changed, 74 insertions(+), 296 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 869f3c7cb0f..4cf274f3263 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -2,7 +2,7 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
-// ******* DATAFLOW *******************************************************************************
+// ******* DATAFLOW BELOW *************************************************************************
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
@@ -10,16 +10,8 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
- exists(IntegerLiteral integer, VarAccess var |
- integer.getIntValue() < 2048 and
- source.asExpr() = integer
- or
- // The below only handles cases when variables are used (both locally in a method and between methods)
- // The above adds handling for direct use of integers as well
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
- source.asExpr() = var.getVariable().getInitializer()
- )
+ source.asExpr() instanceof IntegerLiteral and // ! this works with current test cases, but reconsider IntegerLiteral when variables are used
+ source.toString().toInt() < 2048
}
override predicate isSink(DataFlow::Node sink) {
@@ -31,27 +23,22 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
}
/**
- * Symmetric (AES) key length data flow tracking configuration.
+ * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
-class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
- SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
+class AsymmetricECCKeyTrackingConfiguration extends DataFlow::Configuration {
+ AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
- exists(IntegerLiteral integer, VarAccess var |
- integer.getIntValue() < 128 and
- source.asExpr() = integer
- or
- // The below only handles cases when variables are used (both locally in a method and between methods)
- // The above adds handling for direct use of integers as well
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- source.asExpr() = var.getVariable().getInitializer()
+ exists(ClassInstanceExpr ecGenParamSpec |
+ getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
+ source.asExpr() = ecGenParamSpec
)
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ ma.getArgument(0).getType() instanceof ECGenParameterSpec and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -60,11 +47,11 @@ class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
/**
* Symmetric (AES) key length data flow tracking configuration.
*/
-class SymmetricKeyTrackingConfiguration2 extends DataFlow::Configuration {
- SymmetricKeyTrackingConfiguration2() { this = "SymmetricKeyTrackingConfiguration2" }
+class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+ SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof IntegerLiteral and
+ source.asExpr() instanceof IntegerLiteral and // ! this works with current test cases, but reconsider IntegerLiteral when variables are used
source.toString().toInt() < 128
}
@@ -76,54 +63,34 @@ class SymmetricKeyTrackingConfiguration2 extends DataFlow::Configuration {
}
}
-class UnsafeSymmetricKeySize extends IntegerLiteral {
- UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
+// ! below doesn't work for some reason...
+predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+ or
+ exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
}
-class UnsafeAsymmetricKeySize extends IntegerLiteral {
- UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
-}
-
-class UnsafeKeySize extends IntegerLiteral {
- UnsafeKeySize() {
- this instanceof UnsafeAsymmetricKeySize and
- exists(MethodAccess ma | ma.getMethod() instanceof KeyPairGeneratorInitMethod)
- or
- this instanceof UnsafeSymmetricKeySize and
- exists(MethodAccess ma | ma.getMethod() instanceof KeyGeneratorInitMethod)
- }
-}
-
-class KeyInitMethod extends Method {
- KeyInitMethod() {
- this instanceof KeyGeneratorInitMethod or
- this instanceof KeyPairGeneratorInitMethod
- }
-}
-
-/**
- * key length data flow tracking configuration.
- */
-class KeyTrackingConfiguration extends DataFlow::Configuration {
- KeyTrackingConfiguration() { this = "KeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof UnsafeKeySize }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyInitMethod and
- sink.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-// ******* DATAFLOW *******************************************************************************
+// ******** Need the below for the above ********
// ! move to Encryption.qll?
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class ECGenParameterSpec extends RefType {
ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
+// ! move to Encryption.qll?
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
// ! move to Encryption.qll?
/** The `init` method declared in `javax.crypto.KeyGenerator`. */
private class KeyGeneratorInitMethod extends Method {
@@ -142,17 +109,19 @@ private class KeyPairGeneratorInitMethod extends Method {
}
}
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+// ******* DATAFLOW ABOVE *************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ******* OLD/UNUSED OR EXPERIMENTAL CODE BELOW **************************************************
+class UnsafeSymmetricKeySize extends IntegerLiteral {
+ UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
+}
+
+class UnsafeAsymmetricKeySize extends IntegerLiteral {
+ UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
}
/** Taint configuration tracking flow from a key generator to a `init` method call. */
@@ -189,215 +158,3 @@ private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configura
)
}
}
-
-/**
- * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-//bindingset[type]
-private predicate hasShortSymmetricKey_TEST() {
- exists(
- SymmetricKeyTrackingConfiguration2 cfg, DataFlow::PathNode source, DataFlow::PathNode sink
- |
- cfg.hasFlowPath(source, sink)
- )
- // ma.getMethod() instanceof KeyGeneratorInitMethod and
- // // flow needed to correctly determine algorithm type and
- // // not match to ANY symmetric algorithm (although doesn't really matter since only have AES currently...)
- // exists(
- // JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
- // DataFlow::PathNode dest
- // |
- // jcg.getAlgoSpec().(StringLiteral).getValue() = type and
- // source.getNode().asExpr() = jcg and
- // dest.getNode().asExpr() = ma.getQualifier() and
- // cc.hasFlowPath(source, dest)
- // ) and
- // (
- // // VarAccess case needed to handle FN of key-size stored in a variable
- // // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
- // // (e.g. not declared `final` in Java)
- // exists(VarAccess var |
- // var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- // var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- // ma.getArgument(0) = var
- // )
- // or
- // ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
- // ) and
- // msg = "Key size should be at least 128 bits for " + type + " encryption."
-}
-
-/**
- * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-bindingset[type]
-private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- // flow needed to correctly determine algorithm type and
- // not match to ANY symmetric algorithm (although doesn't really matter since only have AES currently...)
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
- DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue() = type and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- cc.hasFlowPath(source, dest)
- ) and
- (
- // VarAccess case needed to handle FN of key-size stored in a variable
- // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
- // (e.g. not declared `final` in Java)
- exists(VarAccess var |
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- ma.getArgument(0) = var
- )
- or
- // exists(CompileTimeConstantExpr var |
- // //var.getUnderlyingExpr() instanceof IntegerLiteral and // can't include this...
- // var.getIntValue() < 128 and
- // ma.getArgument(0) = var
- // )
- // or
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
- ) and
- msg = "Key size should be at least 128 bits for " + type + " encryption."
-}
-
-/**
- * Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size.
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortAESKey(MethodAccess ma, string msg) {
- hasShortSymmetricKey(ma, msg, "AES")
-}
-
-/**
- * Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-bindingset[type]
-private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- //ma.getQualifier() instanceof JavaSecurityKeyPairGenerator and
- //ma.getQualifier().getBasicBlock() instanceof JavaSecurityKeyPairGenerator and
- // * USE BELOW
- ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
- // * USE ABOVE
- //ma.getQualifier().getBasicBlock().getNode(2) instanceof JavaSecurityKeyPairGenerator and
- // ma.getQualifier()
- // .getBasicBlock()
- // .getANode()
- // .(JavaSecurityKeyPairGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase() = type and
- //ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
- // * USE BELOW
- ma.getQualifier()
- .getBasicBlock()
- .getAPredecessor()
- .(JavaSecurityKeyPairGenerator)
- .getAlgoSpec()
- .(StringLiteral)
- .getValue()
- .toUpperCase() = type and
- // * USE ABOVE
- // flow needed to correctly determine algorithm type and
- // not match to ANY asymmetric algorithm
- // * REMOVE BELOW
- // exists(
- // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- // DataFlow::PathNode source, DataFlow::PathNode dest
- // |
- // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
- // source.getNode().asExpr() = jpg and
- // dest.getNode().asExpr() = ma.getQualifier() and
- // kc.hasFlowPath(source, dest)
- // ) and
- // * REMOVE ABOVE
- // VarAccess case needed to handle FN of key-size stored in a variable
- // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
- // (e.g. not declared `final` in Java)
- (
- exists(VarAccess var |
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
- ma.getArgument(0) = var
- )
- or
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048
- // or
- // exists(
- // AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
- // |
- // cfg.hasFlowPath(source, sink)
- // )
- ) and
- msg = "Key size should be at least 2048 bits for " + type + " encryption."
-}
-
-/**
- * Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "DSA") or
- hasShortAsymmetricKeyPair(ma, msg, "DH")
-}
-
-/**
- * Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "RSA")
-}
-
-/**
- * Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortECKeyPair(MethodAccess ma, string msg) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest) and
- DataFlow::localExprFlow(cie, ma.getArgument(0)) and
- ma.getArgument(0).getType() instanceof ECGenParameterSpec and
- getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
- ) and
- msg = "Key size should be at least 256 bits for EC encryption."
-}
-
-// ! refactor this so can use 'path-problem' select clause instead?
-predicate hasInsufficientKeySize(Expr e, string msg) {
- hasShortAESKey(e, msg) or
- hasShortDsaKeyPair(e, msg) or
- hasShortRsaKeyPair(e, msg) or
- hasShortECKeyPair(e, msg)
-}
-
-predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- or
- exists(SymmetricKeyTrackingConfiguration2 config2 | config2.hasFlowPath(source, sink))
-}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 2979c9116c4..93411a101b2 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -30,5 +30,6 @@ from DataFlow::PathNode source, DataFlow::PathNode sink
where
//hasInsufficientKeySize2(source, sink)
exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration2 config2 | config2.hasFlowPath(source, sink))
+ exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
select sink.getNode(), source, sink, "This $@ is too small.", sink.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 68c3e7f322e..50dc7b4637f 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -144,7 +144,8 @@ public class InsufficientKeySizeTest {
{
int size = 64; // test integer variable
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // test KeyGenerator variable
- testSymmetric(size, keyGen);
+ testSymmetric(size, keyGen); // test with variable as key size
+ testSymmetric2(64); // test with int constant as key size
}
@@ -159,7 +160,8 @@ public class InsufficientKeySizeTest {
{
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // test ECGenParameterSpec variable
KeyPairGenerator keyPairGen22 = KeyPairGenerator.getInstance("EC"); // test KeyPairGenerator variable
- testAsymmetricEC(ecSpec, keyPairGen22);
+ testAsymmetricEC(ecSpec, keyPairGen22); // test with variable as key size
+ testAsymmetricNonEC2(1024); // test with int constant as key size
}
}
@@ -173,6 +175,13 @@ public class InsufficientKeySizeTest {
kg.init(64); // $ hasInsufficientKeySize
}
+ //! refactor this to use expected-value tag and combine with above method
+ public static void testSymmetric2(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // BAD: Key size is less than 2048
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ keyGen.init(keySize); // $ hasInsufficientKeySize
+ }
+
public static void testAsymmetricNonEC(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
// BAD: Key size is less than 2048
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
@@ -182,6 +191,13 @@ public class InsufficientKeySizeTest {
kpg.initialize(1024); // $ hasInsufficientKeySize
}
+ //! refactor this to use expected-value tag and combine with above method
+ public static void testAsymmetricNonEC2(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
+ keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
+ }
+
public static void testAsymmetricEC(ECGenParameterSpec spec, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index b8133b154e4..8514fcd2098 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -9,9 +9,13 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
- exists(Expr e, string msg | hasInsufficientKeySize(e, msg) |
- e.getLocation() = location and
- element = e.toString() and
+ exists(DataFlow::PathNode source, DataFlow::PathNode sink |
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
+ |
+ sink.getNode().getLocation() = location and
+ element = sink.getNode().toString() and
value = ""
)
}
From cdac0e2b529ef1630c5cd16fd2fc35066f7cb015 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 7 Oct 2022 15:53:02 -0400
Subject: [PATCH 026/465] add local algo name tracking, still need to add
ability to track algo name when KeyGen obj is param to other method
---
.../security/InsufficientKeySizeQuery.qll | 61 ++++++++++++++++---
.../CWE/CWE-326/InsufficientKeySize.ql | 1 -
.../CWE-326/InsufficientKeySizeTest.java | 20 ++++--
3 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 4cf274f3263..c7cb3a10331 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -2,6 +2,11 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
+// TODO:
+// todo #1: make representation of source that can be shared across the configs
+// todo #2: make representation of sink that can be shared across the configs
+// todo #3: finish adding tracking for algo type/name... need flow/taint-tracking for across methods??
+// todo #3a: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
// ******* DATAFLOW BELOW *************************************************************************
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
@@ -10,13 +15,27 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof IntegerLiteral and // ! this works with current test cases, but reconsider IntegerLiteral when variables are used
- source.toString().toInt() < 2048
+ exists(ClassInstanceExpr rsaGenParamSpec |
+ rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and // ! double-check if should just use getType() instead
+ rsaGenParamSpec.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ source.asExpr() = rsaGenParamSpec
+ )
+ or
+ source.asExpr().(IntegerLiteral).getIntValue() < 2048
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
+ exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ va.getVariable()
+ .getAnAssignedValue()
+ .(JavaSecurityKeyPairGenerator)
+ .getAlgoSpec()
+ .(StringLiteral)
+ .getValue()
+ .toUpperCase()
+ .matches(["RSA", "DSA", "DH"]) and
+ ma.getQualifier() = va and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -30,15 +49,26 @@ class AsymmetricECCKeyTrackingConfiguration extends DataFlow::Configuration {
override predicate isSource(DataFlow::Node source) {
exists(ClassInstanceExpr ecGenParamSpec |
- getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
+ getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
source.asExpr() = ecGenParamSpec
)
+ or
+ source.asExpr().(IntegerLiteral).getIntValue() < 256
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
+ exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- ma.getArgument(0).getType() instanceof ECGenParameterSpec and
+ //ma.getArgument(0).getType() instanceof ECGenParameterSpec and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
+ va.getVariable()
+ .getAnAssignedValue()
+ .(JavaSecurityKeyPairGenerator)
+ .getAlgoSpec()
+ .(StringLiteral)
+ .getValue()
+ .toUpperCase()
+ .matches(["EC%"]) and
+ ma.getQualifier() = va and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -51,13 +81,21 @@ class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof IntegerLiteral and // ! this works with current test cases, but reconsider IntegerLiteral when variables are used
- source.toString().toInt() < 128
+ source.asExpr().(IntegerLiteral).getIntValue() < 128
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
+ exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyGeneratorInitMethod and
+ va.getVariable()
+ .getAnAssignedValue()
+ .(JavaxCryptoKeyGenerator)
+ .getAlgoSpec()
+ .(StringLiteral)
+ .getValue()
+ .toUpperCase()
+ .matches(["AES"]) and
+ ma.getQualifier() = va and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -77,6 +115,11 @@ private class ECGenParameterSpec extends RefType {
ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class RSAGenParameterSpec extends RefType {
+ RSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+}
+
// ! move to Encryption.qll?
/** Returns the key size in the EC algorithm string */
bindingset[algorithm]
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 93411a101b2..c6dce740588 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -28,7 +28,6 @@ import DataFlow::PathGraph
// sink.getNode(), "size"
from DataFlow::PathNode source, DataFlow::PathNode sink
where
- //hasInsufficientKeySize2(source, sink)
exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 50dc7b4637f..b383892361b 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -1,5 +1,6 @@
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
import javax.crypto.KeyGenerator;
public class InsufficientKeySizeTest {
@@ -27,6 +28,16 @@ public class InsufficientKeySizeTest {
// GOOD: Key size is no less than 2048
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
keyPairGen2.initialize(2048); // Safe
+
+ // test with spec
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("RSA");
+ RSAKeyGenParameterSpec rsaSpec = new RSAKeyGenParameterSpec(1024, null);
+ keyPairGen3.initialize(rsaSpec); // $ hasInsufficientKeySize
+
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen4.initialize(new RSAKeyGenParameterSpec(1024, null)); // $ hasInsufficientKeySize
}
// DSA (Asymmetric)
@@ -145,7 +156,7 @@ public class InsufficientKeySizeTest {
int size = 64; // test integer variable
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // test KeyGenerator variable
testSymmetric(size, keyGen); // test with variable as key size
- testSymmetric2(64); // test with int constant as key size
+ testSymmetric2(64); // test with int literal as key size
}
@@ -153,15 +164,16 @@ public class InsufficientKeySizeTest {
{
int size = 1024; // test integer variable
KeyPairGenerator keyPairGen21 = KeyPairGenerator.getInstance("RSA"); // test KeyPairGenerator variable
- testAsymmetricNonEC(size, keyPairGen21);
+ testAsymmetricNonEC(size, keyPairGen21); // test with variable as key size
+ testAsymmetricNonEC2(1024); // test with int literal as key size
}
// Test variable passed to other method(s) - Asymmetric, EC
{
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // test ECGenParameterSpec variable
KeyPairGenerator keyPairGen22 = KeyPairGenerator.getInstance("EC"); // test KeyPairGenerator variable
- testAsymmetricEC(ecSpec, keyPairGen22); // test with variable as key size
- testAsymmetricNonEC2(1024); // test with int constant as key size
+ testAsymmetricEC(ecSpec, keyPairGen22);
+
}
}
From b7123c17f8f0c871e1cf53a51027e5abc6c69a77 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 7 Oct 2022 23:42:23 -0400
Subject: [PATCH 027/465] draft of adding kpg tracking into dataflow config
---
.../security/InsufficientKeySizeQuery.qll | 42 ++--
.../CWE/CWE-326/InsufficientKeySize.ql | 20 +-
.../CWE-326/InsufficientKeySizeTest.java | 26 ++-
.../security/CWE-326/SignatureTest.java | 196 ++++++++++++++++++
4 files changed, 261 insertions(+), 23 deletions(-)
create mode 100644 java/ql/test/query-tests/security/CWE-326/SignatureTest.java
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index c7cb3a10331..b10bc83c64a 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,4 +1,5 @@
import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
@@ -11,7 +12,7 @@ import semmle.code.java.dataflow.DataFlow
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
-class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -27,15 +28,25 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- va.getVariable()
- .getAnAssignedValue()
- .(JavaSecurityKeyPairGenerator)
- .getAlgoSpec()
- .(StringLiteral)
- .getValue()
- .toUpperCase()
- .matches(["RSA", "DSA", "DH"]) and
- ma.getQualifier() = va and
+ ma.getFile().getBaseName().matches("SignatureTest.java") and
+ // va.getVariable()
+ // .getAnAssignedValue()
+ // .(JavaSecurityKeyPairGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase()
+ // .matches(["RSA", "DSA", "DH"]) and
+ // ma.getQualifier() = va and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kpgConfig.hasFlowPath(source, dest)
+ ) and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -102,12 +113,11 @@ class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
}
// ! below doesn't work for some reason...
-predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- or
- exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
-}
-
+// predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
+// exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+// or
+// exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
+// }
// ******** Need the below for the above ********
// ! move to Encryption.qll?
/** The Java class `java.security.spec.ECGenParameterSpec`. */
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index c6dce740588..9b52707c594 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -26,9 +26,17 @@ import DataFlow::PathGraph
// cfg2.hasFlowPath(source, sink)
// select sink.getNode(), source, sink, "The $@ of an asymmetric key should be at least 2048 bits.",
// sink.getNode(), "size"
-from DataFlow::PathNode source, DataFlow::PathNode sink
-where
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
-select sink.getNode(), source, sink, "This $@ is too small.", sink.getNode(), "key size"
+// * Use Below
+// from DataFlow::PathNode source, DataFlow::PathNode sink
+// where exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) //or
+// //exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) //or
+// //exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+// select sink.getNode(), source, sink, "This $@ is too small, and flows to $@.", source.getNode(),
+// "key size", sink.getNode(), "here"
+// * Use Above
+from DataFlow::Node source, DataFlow::Node sink
+where exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) //or
+//exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) //or
+//exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+select sink, source, sink, "This $@ is too small, and flows to $@.", source, "key size", sink,
+ "here"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index b383892361b..75ba5e36e76 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -1,7 +1,11 @@
+import javax.crypto.KeyGenerator;
import java.security.KeyPairGenerator;
+
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
-import javax.crypto.KeyGenerator;
+import java.security.spec.DSAGenParameterSpec;
+import javax.crypto.spec.DHGenParameterSpec;
+
public class InsufficientKeySizeTest {
public void keySizeTesting() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
@@ -49,6 +53,16 @@ public class InsufficientKeySizeTest {
// GOOD: Key size is no less than 2048
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
keyPairGen4.initialize(2048); // Safe
+
+ // test with spec?
+ // // BAD: Key size is less than 2048
+ // KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("DSA");
+ // DSAGenParameterSpec dsaSpec = new DSAGenParameterSpec(1024, null);
+ // keyPairGen5.initialize(dsaSpec); // $ hasInsufficientKeySize
+
+ // // BAD: Key size is less than 2048
+ // KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("DSA");
+ // keyPairGen6.initialize(new DSAGenParameterSpec(1024, null)); // $ hasInsufficientKeySize
}
// DH (Asymmetric)
@@ -60,6 +74,16 @@ public class InsufficientKeySizeTest {
// GOOD: Key size is no less than 2048
KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
keyPairGen17.initialize(2048); // Safe
+
+ // test with spec?
+ // // BAD: Key size is less than 2048
+ // KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DH");
+ // DHGenParameterSpec dhSpec = new DHGenParameterSpec(1024, null);
+ // keyPairGen3.initialize(dhSpec); // $ hasInsufficientKeySize
+
+ // // BAD: Key size is less than 2048
+ // KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DH");
+ // keyPairGen4.initialize(new DHGenParameterSpec(1024, null)); // $ hasInsufficientKeySize
}
// EC (Asymmetric)
diff --git a/java/ql/test/query-tests/security/CWE-326/SignatureTest.java b/java/ql/test/query-tests/security/CWE-326/SignatureTest.java
new file mode 100644
index 00000000000..016a62a41f8
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-326/SignatureTest.java
@@ -0,0 +1,196 @@
+//package org.bouncycastle.jce.provider.test;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+
+// import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+// import org.bouncycastle.jce.provider.BouncyCastleProvider;
+// import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+// import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
+// import org.bouncycastle.util.encoders.Hex;
+// import org.bouncycastle.util.test.SimpleTest;
+
+public class SignatureTest
+ //extends SimpleTest
+{
+ // private static final byte[] DATA = Hex.decode("00000000deadbeefbeefdeadffffffff00000000");
+
+ private void checkSig(KeyPair kp, String name)
+ throws Exception
+ {
+ // Signature sig = Signature.getInstance(name, "BC");
+
+ // sig.initSign(kp.getPrivate());
+ // sig.update(DATA);
+
+ // byte[] signature1 = sig.sign();
+
+ // sig.update(DATA);
+
+ // byte[] signature2 = sig.sign();
+
+ // sig.initVerify(kp.getPublic());
+
+ // sig.update(DATA);
+ // if (!sig.verify(signature1))
+ // {
+ // fail("did not verify: " + name);
+ // }
+
+ // // After verify, should be reusable as if we are after initVerify
+ // sig.update(DATA);
+ // if (!sig.verify(signature1))
+ // {
+ // fail("second verify failed: " + name);
+ // }
+
+ // sig.update(DATA);
+ // if (!sig.verify(signature2))
+ // {
+ // fail("second verify failed (2): " + name);
+ // }
+ }
+
+ public void performTest()
+ throws Exception
+ {
+ KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpGen.initialize(2048); // Safe
+
+ KeyPair kp = kpGen.generateKeyPair();
+
+ checkSig(kp, "SHA1withRSA");
+ checkSig(kp, "SHA224withRSA");
+ checkSig(kp, "SHA256withRSA");
+ checkSig(kp, "SHA384withRSA");
+ checkSig(kp, "SHA512withRSA");
+
+ checkSig(kp, "SHA3-224withRSA");
+ checkSig(kp, "SHA3-256withRSA");
+ checkSig(kp, "SHA3-384withRSA");
+ checkSig(kp, "SHA3-512withRSA");
+
+ checkSig(kp, "MD2withRSA");
+ checkSig(kp, "MD4withRSA");
+ checkSig(kp, "MD5withRSA");
+ checkSig(kp, "RIPEMD160withRSA");
+ checkSig(kp, "RIPEMD128withRSA");
+ checkSig(kp, "RIPEMD256withRSA");
+
+ checkSig(kp, "SHA1withRSAandMGF1");
+ checkSig(kp, "SHA1withRSAandMGF1");
+ checkSig(kp, "SHA224withRSAandMGF1");
+ checkSig(kp, "SHA256withRSAandMGF1");
+ checkSig(kp, "SHA384withRSAandMGF1");
+ checkSig(kp, "SHA512withRSAandMGF1");
+
+ checkSig(kp, "SHA1withRSAandSHAKE128");
+ checkSig(kp, "SHA1withRSAandSHAKE128");
+ checkSig(kp, "SHA224withRSAandSHAKE128");
+ checkSig(kp, "SHA256withRSAandSHAKE128");
+ checkSig(kp, "SHA384withRSAandSHAKE128");
+ checkSig(kp, "SHA512withRSAandSHAKE128");
+
+ checkSig(kp, "SHA1withRSAandSHAKE256");
+ checkSig(kp, "SHA1withRSAandSHAKE256");
+ checkSig(kp, "SHA224withRSAandSHAKE256");
+ checkSig(kp, "SHA256withRSAandSHAKE256");
+ checkSig(kp, "SHA384withRSAandSHAKE256");
+ checkSig(kp, "SHA512withRSAandSHAKE256");
+
+ checkSig(kp, "SHAKE128withRSAPSS");
+ checkSig(kp, "SHAKE256withRSAPSS");
+
+ checkSig(kp, "SHA1withRSA/ISO9796-2");
+ checkSig(kp, "MD5withRSA/ISO9796-2");
+ checkSig(kp, "RIPEMD160withRSA/ISO9796-2");
+
+// checkSig(kp, "SHA1withRSA/ISO9796-2PSS");
+// checkSig(kp, "MD5withRSA/ISO9796-2PSS");
+// checkSig(kp, "RIPEMD160withRSA/ISO9796-2PSS");
+
+ checkSig(kp, "RIPEMD128withRSA/X9.31");
+ checkSig(kp, "RIPEMD160withRSA/X9.31");
+ checkSig(kp, "SHA1withRSA/X9.31");
+ checkSig(kp, "SHA224withRSA/X9.31");
+ checkSig(kp, "SHA256withRSA/X9.31");
+ checkSig(kp, "SHA384withRSA/X9.31");
+ checkSig(kp, "SHA512withRSA/X9.31");
+ checkSig(kp, "WhirlpoolwithRSA/X9.31");
+
+ kpGen = KeyPairGenerator.getInstance("DSA", "BC");
+
+ kpGen.initialize(2048); // Safe
+
+ kp = kpGen.generateKeyPair();
+
+ checkSig(kp, "SHA1withDSA");
+ checkSig(kp, "SHA224withDSA");
+ checkSig(kp, "SHA256withDSA");
+ checkSig(kp, "SHA384withDSA");
+ checkSig(kp, "SHA512withDSA");
+ checkSig(kp, "NONEwithDSA");
+
+ kpGen = KeyPairGenerator.getInstance("EC", "BC");
+
+ kpGen.initialize(256); // Safe
+
+ kp = kpGen.generateKeyPair();
+
+ checkSig(kp, "SHA1withECDSA");
+ checkSig(kp, "SHA224withECDSA");
+ checkSig(kp, "SHA256withECDSA");
+ checkSig(kp, "SHA384withECDSA");
+ checkSig(kp, "SHA512withECDSA");
+ checkSig(kp, "RIPEMD160withECDSA");
+ checkSig(kp, "SHAKE128withECDSA");
+ checkSig(kp, "SHAKE256withECDSA");
+
+ kpGen = KeyPairGenerator.getInstance("EC", "BC");
+
+ kpGen.initialize(521); // Safe
+
+ kp = kpGen.generateKeyPair();
+
+ checkSig(kp, "SHA1withECNR");
+ checkSig(kp, "SHA224withECNR");
+ checkSig(kp, "SHA256withECNR");
+ checkSig(kp, "SHA384withECNR");
+ checkSig(kp, "SHA512withECNR");
+
+ // kpGen = KeyPairGenerator.getInstance("ECGOST3410", "BC");
+
+ // kpGen.initialize(new ECNamedCurveGenParameterSpec("GostR3410-2001-CryptoPro-A"), new SecureRandom());
+
+ // kp = kpGen.generateKeyPair();
+
+ // checkSig(kp, "GOST3411withECGOST3410");
+
+ // kpGen = KeyPairGenerator.getInstance("GOST3410", "BC");
+
+ // GOST3410ParameterSpec gost3410P = new GOST3410ParameterSpec(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_A.getId());
+
+ // kpGen.initialize(gost3410P);
+
+ // kp = kpGen.generateKeyPair();
+
+ // checkSig(kp, "GOST3411withGOST3410");
+ }
+
+ public String getName()
+ {
+ return "SigNameTest";
+ }
+
+ // public static void main(
+ // String[] args)
+ // {
+ // //Security.addProvider(new BouncyCastleProvider());
+
+ // //runTest(new SignatureTest());
+ // }
+}
From b0af9f936cc97890ca0c2df398e176bd7cbe0aba Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Sun, 9 Oct 2022 22:59:45 -0400
Subject: [PATCH 028/465] added kg taintracking config to all
---
.../security/InsufficientKeySizeQuery.qll | 67 +++++++++++++------
.../CWE/CWE-326/InsufficientKeySize.ql | 16 +++--
2 files changed, 55 insertions(+), 28 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index b10bc83c64a..a364d5dc155 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -28,7 +28,7 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- ma.getFile().getBaseName().matches("SignatureTest.java") and
+ //ma.getFile().getBaseName().matches("SignatureTest.java") and
// va.getVariable()
// .getAnAssignedValue()
// .(JavaSecurityKeyPairGenerator)
@@ -52,10 +52,17 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
}
}
+// predicate hasInsufficientKeySize(string msg) { hasShortAsymmetricKeyPair(msg) }
+// predicate hasShortAsymmetricKeyPair(string msg) {
+// exists(AsymmetricKeyTrackingConfiguration config1, DataFlow::Node source, DataFlow::Node sink |
+// config1.hasFlow(source, sink)
+// ) and
+// msg = "Key size should be at least 2048 bits for " + "___" + " encryption."
+// }
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
-class AsymmetricECCKeyTrackingConfiguration extends DataFlow::Configuration {
+class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuration {
AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -71,15 +78,24 @@ class AsymmetricECCKeyTrackingConfiguration extends DataFlow::Configuration {
exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
//ma.getArgument(0).getType() instanceof ECGenParameterSpec and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
- va.getVariable()
- .getAnAssignedValue()
- .(JavaSecurityKeyPairGenerator)
- .getAlgoSpec()
- .(StringLiteral)
- .getValue()
- .toUpperCase()
- .matches(["EC%"]) and
- ma.getQualifier() = va and
+ // va.getVariable()
+ // .getAnAssignedValue()
+ // .(JavaSecurityKeyPairGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase()
+ // .matches(["EC%"]) and
+ // ma.getQualifier() = va and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kpgConfig.hasFlowPath(source, dest)
+ ) and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -88,7 +104,7 @@ class AsymmetricECCKeyTrackingConfiguration extends DataFlow::Configuration {
/**
* Symmetric (AES) key length data flow tracking configuration.
*/
-class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
override predicate isSource(DataFlow::Node source) {
@@ -98,15 +114,24 @@ class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, VarAccess va |
ma.getMethod() instanceof KeyGeneratorInitMethod and
- va.getVariable()
- .getAnAssignedValue()
- .(JavaxCryptoKeyGenerator)
- .getAlgoSpec()
- .(StringLiteral)
- .getValue()
- .toUpperCase()
- .matches(["AES"]) and
- ma.getQualifier() = va and
+ // va.getVariable()
+ // .getAnAssignedValue()
+ // .(JavaxCryptoKeyGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase()
+ // .matches(["AES"]) and
+ // ma.getQualifier() = va and
+ exists(
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("AES") and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kgConfig.hasFlowPath(source, dest)
+ ) and
sink.asExpr() = ma.getArgument(0)
)
}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 9b52707c594..c3a406514ac 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -2,7 +2,7 @@
* @name Insufficient key size used with a cryptographic algorithm
* @description Using cryptographic algorithms with too small of a key size can
* allow an attacker to compromise security.
- * @kind path-problem
+ * @kind problem
* @problem.severity error
* @security-severity 7.5
* @precision high
@@ -13,8 +13,8 @@
import java
import semmle.code.java.security.InsufficientKeySizeQuery
-import DataFlow::PathGraph
+//import DataFlow::PathGraph
// from Expr e, string msg
// where hasInsufficientKeySize(e, msg)
// select e, msg
@@ -34,9 +34,11 @@ import DataFlow::PathGraph
// select sink.getNode(), source, sink, "This $@ is too small, and flows to $@.", source.getNode(),
// "key size", sink.getNode(), "here"
// * Use Above
+// * Use Below for taint-tracking with kpg
from DataFlow::Node source, DataFlow::Node sink
-where exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) //or
-//exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) //or
-//exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
-select sink, source, sink, "This $@ is too small, and flows to $@.", source, "key size", sink,
- "here"
+where
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
+ exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
+select sink, "This $@ is too small and creates a key $@.", source, "key size", sink, "here"
+// * Use Above for taint-tracking with kpg
From f5a2fef7a3b465c45104ded6bf3b80555e958b09 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 10 Oct 2022 10:13:28 -0400
Subject: [PATCH 029/465] update tests for non-path version
---
.../security/InsufficientKeySizeQuery.qll | 2 +-
.../CWE-326/InsufficientKeySizeTest.ql | 26 ++++++++++++++-----
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index a364d5dc155..671ae64cd9d 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -60,7 +60,7 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
// msg = "Key size should be at least 2048 bits for " + "___" + " encryption."
// }
/**
- * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ * Asymmetric (EC) key length data flow tracking configuration.
*/
class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuration {
AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index 8514fcd2098..93ad76704c2 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -7,15 +7,29 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override string getARelevantTag() { result = "hasInsufficientKeySize" }
+ // * Path-problem
+ // override predicate hasActualResult(Location location, string element, string tag, string value) {
+ // tag = "hasInsufficientKeySize" and
+ // exists(DataFlow::PathNode source, DataFlow::PathNode sink |
+ // exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ // exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ // exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
+ // |
+ // sink.getNode().getLocation() = location and
+ // element = sink.getNode().toString() and
+ // value = ""
+ // )
+ // }
+ // * Not path-problem
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
- exists(DataFlow::PathNode source, DataFlow::PathNode sink |
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
+ exists(DataFlow::Node source, DataFlow::Node sink |
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
+ exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
|
- sink.getNode().getLocation() = location and
- element = sink.getNode().toString() and
+ sink.getLocation() = location and
+ element = sink.toString() and
value = ""
)
}
From 3cc7f143b23455c2b7499664b700f61d5bc39c17 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 10 Oct 2022 10:47:41 -0400
Subject: [PATCH 030/465] clean up code somewhat
---
.../security/InsufficientKeySizeQuery.qll | 175 +++++--------
...an-UpForDoubleTaintTracking-2022-10-10.qll | 239 ++++++++++++++++++
.../CWE/CWE-326/InsufficientKeySize.ql | 8 +-
.../CWE-326/InsufficientKeySizeTest.java | 8 +
4 files changed, 320 insertions(+), 110 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 671ae64cd9d..c52ae2df550 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -3,11 +3,6 @@ import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
-// TODO:
-// todo #1: make representation of source that can be shared across the configs
-// todo #2: make representation of sink that can be shared across the configs
-// todo #3: finish adding tracking for algo type/name... need flow/taint-tracking for across methods??
-// todo #3a: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
// ******* DATAFLOW BELOW *************************************************************************
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
@@ -16,8 +11,10 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
+ // ! may need to change below to still use `keysize` variable as the source, not the spec
+ // ! also need to look into specs for DSA and DH more
exists(ClassInstanceExpr rsaGenParamSpec |
- rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and // ! double-check if should just use getType() instead
+ rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and
rsaGenParamSpec.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
source.asExpr() = rsaGenParamSpec
)
@@ -26,18 +23,8 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, VarAccess va |
+ exists(MethodAccess ma |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- //ma.getFile().getBaseName().matches("SignatureTest.java") and
- // va.getVariable()
- // .getAnAssignedValue()
- // .(JavaSecurityKeyPairGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase()
- // .matches(["RSA", "DSA", "DH"]) and
- // ma.getQualifier() = va and
exists(
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
DataFlow::PathNode source, DataFlow::PathNode dest
@@ -52,13 +39,6 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
}
}
-// predicate hasInsufficientKeySize(string msg) { hasShortAsymmetricKeyPair(msg) }
-// predicate hasShortAsymmetricKeyPair(string msg) {
-// exists(AsymmetricKeyTrackingConfiguration config1, DataFlow::Node source, DataFlow::Node sink |
-// config1.hasFlow(source, sink)
-// ) and
-// msg = "Key size should be at least 2048 bits for " + "___" + " encryption."
-// }
/**
* Asymmetric (EC) key length data flow tracking configuration.
*/
@@ -66,8 +46,9 @@ class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuratio
AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
+ // ! may need to change below to still use `keysize` variable as the source, not the spec
exists(ClassInstanceExpr ecGenParamSpec |
- getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
+ getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
source.asExpr() = ecGenParamSpec
)
or
@@ -75,18 +56,8 @@ class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuratio
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, VarAccess va |
+ exists(MethodAccess ma |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- //ma.getArgument(0).getType() instanceof ECGenParameterSpec and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
- // va.getVariable()
- // .getAnAssignedValue()
- // .(JavaSecurityKeyPairGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase()
- // .matches(["EC%"]) and
- // ma.getQualifier() = va and
exists(
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
DataFlow::PathNode source, DataFlow::PathNode dest
@@ -112,17 +83,8 @@ class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, VarAccess va |
+ exists(MethodAccess ma |
ma.getMethod() instanceof KeyGeneratorInitMethod and
- // va.getVariable()
- // .getAnAssignedValue()
- // .(JavaxCryptoKeyGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase()
- // .matches(["AES"]) and
- // ma.getQualifier() = va and
exists(
JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
DataFlow::PathNode source, DataFlow::PathNode dest
@@ -137,71 +99,13 @@ class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
}
}
-// ! below doesn't work for some reason...
+// ! below predicate doesn't work
// predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
// exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
// or
// exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
// }
-// ******** Need the below for the above ********
-// ! move to Encryption.qll?
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class ECGenParameterSpec extends RefType {
- ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class RSAGenParameterSpec extends RefType {
- RSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
-}
-
-// ! move to Encryption.qll?
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-
-// ! move to Encryption.qll?
-/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-private class KeyGeneratorInitMethod extends Method {
- KeyGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyGenerator and
- this.hasName("init")
- }
-}
-
-// ! move to Encryption.qll?
-/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-private class KeyPairGeneratorInitMethod extends Method {
- KeyPairGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyPairGenerator and
- this.hasName("initialize")
- }
-}
-
-// ******* DATAFLOW ABOVE *************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ******* OLD/UNUSED OR EXPERIMENTAL CODE BELOW **************************************************
-class UnsafeSymmetricKeySize extends IntegerLiteral {
- UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
-}
-
-class UnsafeAsymmetricKeySize extends IntegerLiteral {
- UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
-}
-
+// ******** Need the below models for the above configs ********
/** Taint configuration tracking flow from a key generator to a `init` method call. */
private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
@@ -236,3 +140,62 @@ private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configura
)
}
}
+
+// ! move some/all of below to Encryption.qll or elsewhere?
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class ECGenParameterSpec extends RefType {
+ ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class RSAGenParameterSpec extends RefType {
+ RSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+}
+
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+private class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyGenerator and
+ this.hasName("init")
+ }
+}
+
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+private class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyPairGenerator and
+ this.hasName("initialize")
+ }
+}
+
+// ******* DATAFLOW ABOVE *************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ******* OLD/UNUSED OR EXPERIMENTAL CODE BELOW **************************************************
+class UnsafeSymmetricKeySize extends IntegerLiteral {
+ UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
+}
+
+class UnsafeAsymmetricKeySize extends IntegerLiteral {
+ UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
+}
+// TODO:
+// ! todo #0a: find a better way to combine the two needed taint-tracking configs so can go back to having a path-graph...
+// ! todo #0b: possible to combine the 3 dataflow configs?
+// todo #1: make representation of source that can be shared across the configs
+// todo #2: make representation of sink that can be shared across the configs
+// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
+// todo #4: refactor to be more like the Python version? (or not possible because of lack of DataFlow::Node for void method in Java?)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll
new file mode 100644
index 00000000000..eec0e086f1b
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll
@@ -0,0 +1,239 @@
+import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking2
+import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.DataFlow
+
+// TODO:
+// todo #0: find a better way to combine the two needed taint-tracking configs so can go back to having a path-graph...
+// todo #1: make representation of source that can be shared across the configs
+// todo #2: make representation of sink that can be shared across the configs
+// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
+// todo #4: refactor to be more like the Python version? (or not possible because of lack of DataFlow::Node for void method in Java?)
+// ******* DATAFLOW BELOW *************************************************************************
+/**
+ * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ */
+class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
+ AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(ClassInstanceExpr rsaGenParamSpec |
+ rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and // ! double-check if should just use getType() instead
+ rsaGenParamSpec.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ source.asExpr() = rsaGenParamSpec
+ )
+ or
+ source.asExpr().(IntegerLiteral).getIntValue() < 2048
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma, VarAccess va |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ //ma.getFile().getBaseName().matches("SignatureTest.java") and
+ // va.getVariable()
+ // .getAnAssignedValue()
+ // .(JavaSecurityKeyPairGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase()
+ // .matches(["RSA", "DSA", "DH"]) and
+ // ma.getQualifier() = va and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kpgConfig.hasFlowPath(source, dest)
+ ) and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+// predicate hasInsufficientKeySize(string msg) { hasShortAsymmetricKeyPair(msg) }
+// predicate hasShortAsymmetricKeyPair(string msg) {
+// exists(AsymmetricKeyTrackingConfiguration config1, DataFlow::Node source, DataFlow::Node sink |
+// config1.hasFlow(source, sink)
+// ) and
+// msg = "Key size should be at least 2048 bits for " + "___" + " encryption."
+// }
+/**
+ * Asymmetric (EC) key length data flow tracking configuration.
+ */
+class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuration {
+ AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(ClassInstanceExpr ecGenParamSpec |
+ getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
+ source.asExpr() = ecGenParamSpec
+ )
+ or
+ source.asExpr().(IntegerLiteral).getIntValue() < 256
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma, VarAccess va |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ //ma.getArgument(0).getType() instanceof ECGenParameterSpec and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
+ // va.getVariable()
+ // .getAnAssignedValue()
+ // .(JavaSecurityKeyPairGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase()
+ // .matches(["EC%"]) and
+ // ma.getQualifier() = va and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kpgConfig.hasFlowPath(source, dest)
+ ) and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+/**
+ * Symmetric (AES) key length data flow tracking configuration.
+ */
+class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
+ SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr().(IntegerLiteral).getIntValue() < 128
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma, VarAccess va |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ // va.getVariable()
+ // .getAnAssignedValue()
+ // .(JavaxCryptoKeyGenerator)
+ // .getAlgoSpec()
+ // .(StringLiteral)
+ // .getValue()
+ // .toUpperCase()
+ // .matches(["AES"]) and
+ // ma.getQualifier() = va and
+ exists(
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
+ DataFlow::PathNode source, DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("AES") and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kgConfig.hasFlowPath(source, dest)
+ ) and
+ sink.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+// ! below doesn't work for some reason...
+// predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
+// exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+// or
+// exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
+// }
+// ******** Need the below for the above ********
+// ! move to Encryption.qll?
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class ECGenParameterSpec extends RefType {
+ ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+private class RSAGenParameterSpec extends RefType {
+ RSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+}
+
+// ! move to Encryption.qll?
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
+// ! move to Encryption.qll?
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+private class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyGenerator and
+ this.hasName("init")
+ }
+}
+
+// ! move to Encryption.qll?
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+private class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyPairGenerator and
+ this.hasName("initialize")
+ }
+}
+
+// ******* DATAFLOW ABOVE *************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ******* OLD/UNUSED OR EXPERIMENTAL CODE BELOW **************************************************
+class UnsafeSymmetricKeySize extends IntegerLiteral {
+ UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
+}
+
+class UnsafeAsymmetricKeySize extends IntegerLiteral {
+ UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
+}
+
+/** Taint configuration tracking flow from a key generator to a `init` method call. */
+private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaxCryptoKeyGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/**
+ * Taint configuration tracking flow from a keypair generator to
+ * an `initialize` method call.
+ */
+private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof JavaSecurityKeyPairGenerator
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index c3a406514ac..81c21c7d00b 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -14,9 +14,11 @@
import java
import semmle.code.java.security.InsufficientKeySizeQuery
+// * Original:
//import DataFlow::PathGraph
// from Expr e, string msg
// where hasInsufficientKeySize(e, msg)
+// * Test data-flow config with just Asymmetric:
// select e, msg
// from
// AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
@@ -26,19 +28,17 @@ import semmle.code.java.security.InsufficientKeySizeQuery
// cfg2.hasFlowPath(source, sink)
// select sink.getNode(), source, sink, "The $@ of an asymmetric key should be at least 2048 bits.",
// sink.getNode(), "size"
-// * Use Below
+// * Data-Flow path-graph with All configs: (but doesn't track algo name properly...)
// from DataFlow::PathNode source, DataFlow::PathNode sink
// where exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) //or
// //exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) //or
// //exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
// select sink.getNode(), source, sink, "This $@ is too small, and flows to $@.", source.getNode(),
// "key size", sink.getNode(), "here"
-// * Use Above
-// * Use Below for taint-tracking with kpg
+// * Taint-tracking with kpg to track algo names
from DataFlow::Node source, DataFlow::Node sink
where
exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
select sink, "This $@ is too small and creates a key $@.", source, "key size", sink, "here"
-// * Use Above for taint-tracking with kpg
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 75ba5e36e76..367a5227b14 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -243,4 +243,12 @@ public class InsufficientKeySizeTest {
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1");
kpg.initialize(ecSpec); // $ hasInsufficientKeySize
}
+
+ // ToDo testing:
+ // todo #1: add tests for keysize variable passed to specs
+ // ? todo #2: add tests with DH and DSA specs? (or do those specs not make dev specify keysize?)
+ // ? todo #3: add test for retrieving a key from elsewhere?
+ // todo #4: add barrier-guard tests (see FP from OpenIdentityPlatform/OpenAM)
+ // ? todo #5: add tests for updated keysize variable?: e.g. keysize = 1024; keysize += 1024; so when it's used it is correctly 2048.
+ // ? todo #6: consider if some flow paths for keysize variables will be too hard to track how the keysize is updated (e.g. if calling some other method to get keysize, etc....)
}
From 0c2cff253f140b675a6f240a541bb104ecb511b6 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 10 Oct 2022 14:29:04 -0400
Subject: [PATCH 031/465] updates from discussing with Tony
---
.../java/security/InsufficientKeySizeQuery.qll | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index c52ae2df550..8ae2be5c17f 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -7,7 +7,7 @@ import semmle.code.java.dataflow.DataFlow
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
-class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
+class AsymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -15,7 +15,7 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
// ! also need to look into specs for DSA and DH more
exists(ClassInstanceExpr rsaGenParamSpec |
rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and
- rsaGenParamSpec.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ rsaGenParamSpec.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 2048 and
source.asExpr() = rsaGenParamSpec
)
or
@@ -34,7 +34,7 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
dest.getNode().asExpr() = ma.getQualifier() and
kpgConfig.hasFlowPath(source, dest)
) and
- sink.asExpr() = ma.getArgument(0)
+ sink.asExpr() = ma.getArgument(0) // ! todo: add spec as a sink
)
}
}
@@ -42,7 +42,7 @@ class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
/**
* Asymmetric (EC) key length data flow tracking configuration.
*/
-class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuration {
+class AsymmetricECCKeyTrackingConfiguration extends DataFlow2::Configuration {
AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -75,7 +75,7 @@ class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuratio
/**
* Symmetric (AES) key length data flow tracking configuration.
*/
-class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
+class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
override predicate isSource(DataFlow::Node source) {
@@ -107,7 +107,7 @@ class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
// }
// ******** Need the below models for the above configs ********
/** Taint configuration tracking flow from a key generator to a `init` method call. */
-private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -126,7 +126,7 @@ private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration
* Taint configuration tracking flow from a keypair generator to
* an `initialize` method call.
*/
-private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration {
KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
From bd76b1fcc0aa52820542a53449a7176957aa4fb6 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 10 Oct 2022 16:18:49 -0400
Subject: [PATCH 032/465] clean-up and update configurations to have specs as
sink
---
.../security/InsufficientKeySizeQuery.qll | 123 +++++++++---------
.../CWE/CWE-326/InsufficientKeySize.ql | 26 +---
.../CWE-326/InsufficientKeySizeTest.java | 73 +++++------
.../CWE-326/InsufficientKeySizeTest.ql | 18 +--
4 files changed, 99 insertions(+), 141 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 8ae2be5c17f..ed7408facf9 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,24 +1,14 @@
import semmle.code.java.security.Encryption
-import semmle.code.java.dataflow.TaintTracking2
-import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.DataFlow2
-// ******* DATAFLOW BELOW *************************************************************************
/**
* Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
-class AsymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
- AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
+class AsymmetricNonECKeyTrackingConfiguration extends DataFlow2::Configuration {
+ AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
- // ! may need to change below to still use `keysize` variable as the source, not the spec
- // ! also need to look into specs for DSA and DH more
- exists(ClassInstanceExpr rsaGenParamSpec |
- rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and
- rsaGenParamSpec.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 2048 and
- source.asExpr() = rsaGenParamSpec
- )
- or
source.asExpr().(IntegerLiteral).getIntValue() < 2048
}
@@ -34,7 +24,23 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
dest.getNode().asExpr() = ma.getQualifier() and
kpgConfig.hasFlowPath(source, dest)
) and
- sink.asExpr() = ma.getArgument(0) // ! todo: add spec as a sink
+ sink.asExpr() = ma.getArgument(0)
+ )
+ or
+ // TODO: combine below three for less duplicated code
+ exists(ClassInstanceExpr rsaKeyGenParamSpec |
+ rsaKeyGenParamSpec.getConstructedType() instanceof RSAKeyGenParameterSpec and
+ sink.asExpr() = rsaKeyGenParamSpec.getArgument(0)
+ )
+ or
+ exists(ClassInstanceExpr dsaGenParamSpec |
+ dsaGenParamSpec.getConstructedType() instanceof DSAGenParameterSpec and
+ sink.asExpr() = dsaGenParamSpec.getArgument(0)
+ )
+ or
+ exists(ClassInstanceExpr dhGenParamSpec |
+ dhGenParamSpec.getConstructedType() instanceof DHGenParameterSpec and
+ sink.asExpr() = dhGenParamSpec.getArgument(0)
)
}
}
@@ -42,17 +48,12 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
/**
* Asymmetric (EC) key length data flow tracking configuration.
*/
-class AsymmetricECCKeyTrackingConfiguration extends DataFlow2::Configuration {
- AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
+class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
+ AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
- // ! may need to change below to still use `keysize` variable as the source, not the spec
- exists(ClassInstanceExpr ecGenParamSpec |
- getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
- source.asExpr() = ecGenParamSpec
- )
- or
- source.asExpr().(IntegerLiteral).getIntValue() < 256
+ source.asExpr().(IntegerLiteral).getIntValue() < 256 or
+ getECKeySize(source.asExpr().(StringLiteral).getValue()) < 256 // need this for the cases when the key size is embedded in the curve name.
}
override predicate isSink(DataFlow::Node sink) {
@@ -69,6 +70,12 @@ class AsymmetricECCKeyTrackingConfiguration extends DataFlow2::Configuration {
) and
sink.asExpr() = ma.getArgument(0)
)
+ or
+ exists(ClassInstanceExpr ecGenParamSpec |
+ ecGenParamSpec.getConstructedType() instanceof ECGenParameterSpec and
+ //getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
+ sink.asExpr() = ecGenParamSpec.getArgument(0)
+ )
}
}
@@ -76,7 +83,7 @@ class AsymmetricECCKeyTrackingConfiguration extends DataFlow2::Configuration {
* Symmetric (AES) key length data flow tracking configuration.
*/
class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
- SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
+ SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
source.asExpr().(IntegerLiteral).getIntValue() < 128
@@ -99,14 +106,9 @@ class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
}
}
-// ! below predicate doesn't work
-// predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
-// exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
-// or
-// exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
-// }
-// ******** Need the below models for the above configs ********
-/** Taint configuration tracking flow from a key generator to a `init` method call. */
+// ********************** Need the below models for the above configs **********************
+// todo: move some/all of below to Encryption.qll or elsewhere?
+/** Data flow configuration tracking flow from a key generator to an `init` method call. */
private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
@@ -122,10 +124,7 @@ private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
}
}
-/**
- * Taint configuration tracking flow from a keypair generator to
- * an `initialize` method call.
- */
+/** Data flow configuration tracking flow from a keypair generator to an `initialize` method call. */
private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration {
KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
@@ -141,28 +140,24 @@ private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration
}
}
-// ! move some/all of below to Encryption.qll or elsewhere?
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class ECGenParameterSpec extends RefType {
ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class RSAGenParameterSpec extends RefType {
- RSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
+private class RSAKeyGenParameterSpec extends RefType {
+ RSAKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
}
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+/** The Java class `java.security.spec.DSAGenParameterSpec`. */
+private class DSAGenParameterSpec extends RefType {
+ DSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
+}
+
+/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
+private class DHGenParameterSpec extends RefType {
+ DHGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
}
/** The `init` method declared in `javax.crypto.KeyGenerator`. */
@@ -181,21 +176,21 @@ private class KeyPairGeneratorInitMethod extends Method {
}
}
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
// ******* DATAFLOW ABOVE *************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ******* OLD/UNUSED OR EXPERIMENTAL CODE BELOW **************************************************
-class UnsafeSymmetricKeySize extends IntegerLiteral {
- UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
-}
-
-class UnsafeAsymmetricKeySize extends IntegerLiteral {
- UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
-}
// TODO:
-// ! todo #0a: find a better way to combine the two needed taint-tracking configs so can go back to having a path-graph...
-// ! todo #0b: possible to combine the 3 dataflow configs?
// todo #1: make representation of source that can be shared across the configs
// todo #2: make representation of sink that can be shared across the configs
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
-// todo #4: refactor to be more like the Python version? (or not possible because of lack of DataFlow::Node for void method in Java?)
+// todo #4: refactor to be more like the Python (or C#) version? (or not possible because of lack of DataFlow::Node for void method in Java?)
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 81c21c7d00b..a5401dfb157 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -14,31 +14,9 @@
import java
import semmle.code.java.security.InsufficientKeySizeQuery
-// * Original:
-//import DataFlow::PathGraph
-// from Expr e, string msg
-// where hasInsufficientKeySize(e, msg)
-// * Test data-flow config with just Asymmetric:
-// select e, msg
-// from
-// AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
-// KeyTrackingConfiguration cfg2 //, DataFlow::PathNode source2, DataFlow::PathNode sink2
-// where
-// //cfg.hasFlowPath(source, sink) //or
-// cfg2.hasFlowPath(source, sink)
-// select sink.getNode(), source, sink, "The $@ of an asymmetric key should be at least 2048 bits.",
-// sink.getNode(), "size"
-// * Data-Flow path-graph with All configs: (but doesn't track algo name properly...)
-// from DataFlow::PathNode source, DataFlow::PathNode sink
-// where exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) //or
-// //exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) //or
-// //exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
-// select sink.getNode(), source, sink, "This $@ is too small, and flows to $@.", source.getNode(),
-// "key size", sink.getNode(), "here"
-// * Taint-tracking with kpg to track algo names
from DataFlow::Node source, DataFlow::Node sink
where
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
- exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
+ exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
select sink, "This $@ is too small and creates a key $@.", source, "key size", sink, "here"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 367a5227b14..9ba11f1c632 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -36,8 +36,8 @@ public class InsufficientKeySizeTest {
// test with spec
// BAD: Key size is less than 2048
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("RSA");
- RSAKeyGenParameterSpec rsaSpec = new RSAKeyGenParameterSpec(1024, null);
- keyPairGen3.initialize(rsaSpec); // $ hasInsufficientKeySize
+ RSAKeyGenParameterSpec rsaSpec = new RSAKeyGenParameterSpec(1024, null); // $ hasInsufficientKeySize
+ keyPairGen3.initialize(rsaSpec);
// BAD: Key size is less than 2048
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("RSA");
@@ -54,15 +54,15 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
keyPairGen4.initialize(2048); // Safe
- // test with spec?
- // // BAD: Key size is less than 2048
- // KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("DSA");
- // DSAGenParameterSpec dsaSpec = new DSAGenParameterSpec(1024, null);
- // keyPairGen5.initialize(dsaSpec); // $ hasInsufficientKeySize
+ // test with spec
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("DSA");
+ DSAGenParameterSpec dsaSpec = new DSAGenParameterSpec(1024, 0); // $ hasInsufficientKeySize
+ keyPairGen5.initialize(dsaSpec);
- // // BAD: Key size is less than 2048
- // KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("DSA");
- // keyPairGen6.initialize(new DSAGenParameterSpec(1024, null)); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("DSA");
+ keyPairGen6.initialize(new DSAGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
}
// DH (Asymmetric)
@@ -75,15 +75,15 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
keyPairGen17.initialize(2048); // Safe
- // test with spec?
- // // BAD: Key size is less than 2048
- // KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DH");
- // DHGenParameterSpec dhSpec = new DHGenParameterSpec(1024, null);
- // keyPairGen3.initialize(dhSpec); // $ hasInsufficientKeySize
+ // test with spec
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DH");
+ DHGenParameterSpec dhSpec = new DHGenParameterSpec(1024, 0); // $ hasInsufficientKeySize
+ keyPairGen3.initialize(dhSpec);
- // // BAD: Key size is less than 2048
- // KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DH");
- // keyPairGen4.initialize(new DHGenParameterSpec(1024, null)); // $ hasInsufficientKeySize
+ // BAD: Key size is less than 2048
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DH");
+ keyPairGen4.initialize(new DHGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
}
// EC (Asymmetric)
@@ -91,8 +91,8 @@ public class InsufficientKeySizeTest {
{
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
- keyPairGen5.initialize(ecSpec1); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
+ keyPairGen5.initialize(ecSpec1);
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
@@ -105,18 +105,18 @@ public class InsufficientKeySizeTest {
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2");
- keyPairGen8.initialize(ecSpec3); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2"); // $ hasInsufficientKeySize
+ keyPairGen8.initialize(ecSpec3);
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3");
- keyPairGen9.initialize(ecSpec4); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3"); // $ hasInsufficientKeySize
+ keyPairGen9.initialize(ecSpec4);
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
- keyPairGen10.initialize(ecSpec5); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1"); // $ hasInsufficientKeySize
+ keyPairGen10.initialize(ecSpec5);
// GOOD: Key size is no less than 256
KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
@@ -125,8 +125,8 @@ public class InsufficientKeySizeTest {
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen12 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2");
- keyPairGen12.initialize(ecSpec7); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2"); // $ hasInsufficientKeySize
+ keyPairGen12.initialize(ecSpec7);
// GOOD: Key size is no less than 256
KeyPairGenerator keyPairGen13 = KeyPairGenerator.getInstance("EC");
@@ -135,8 +135,8 @@ public class InsufficientKeySizeTest {
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen14 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1");
- keyPairGen14.initialize(ecSpec9); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1"); // $ hasInsufficientKeySize
+ keyPairGen14.initialize(ecSpec9);
// GOOD: Key size is no less than 256
KeyPairGenerator keyPairGen15 = KeyPairGenerator.getInstance("EC");
@@ -194,7 +194,7 @@ public class InsufficientKeySizeTest {
// Test variable passed to other method(s) - Asymmetric, EC
{
- ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // test ECGenParameterSpec variable
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize // test ECGenParameterSpec variable
KeyPairGenerator keyPairGen22 = KeyPairGenerator.getInstance("EC"); // test KeyPairGenerator variable
testAsymmetricEC(ecSpec, keyPairGen22);
@@ -237,18 +237,17 @@ public class InsufficientKeySizeTest {
public static void testAsymmetricEC(ECGenParameterSpec spec, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
// BAD: Key size is less than 256
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
- keyPairGen.initialize(spec); // $ hasInsufficientKeySize
+ keyPairGen.initialize(spec); // sink is now at above where `spec` variable is initialized
// BAD: Key size is less than 256
- ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1");
- kpg.initialize(ecSpec); // $ hasInsufficientKeySize
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
+ kpg.initialize(ecSpec);
}
// ToDo testing:
- // todo #1: add tests for keysize variable passed to specs
- // ? todo #2: add tests with DH and DSA specs? (or do those specs not make dev specify keysize?)
+ // ? todo #1: add tests for keysize variable passed to specs - not needed if spec is sink now
// ? todo #3: add test for retrieving a key from elsewhere?
- // todo #4: add barrier-guard tests (see FP from OpenIdentityPlatform/OpenAM)
+ // ? todo #4: add barrier-guard tests (see FP from OpenIdentityPlatform/OpenAM)
// ? todo #5: add tests for updated keysize variable?: e.g. keysize = 1024; keysize += 1024; so when it's used it is correctly 2048.
// ? todo #6: consider if some flow paths for keysize variables will be too hard to track how the keysize is updated (e.g. if calling some other method to get keysize, etc....)
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index 93ad76704c2..eef02e9352a 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -7,25 +7,11 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override string getARelevantTag() { result = "hasInsufficientKeySize" }
- // * Path-problem
- // override predicate hasActualResult(Location location, string element, string tag, string value) {
- // tag = "hasInsufficientKeySize" and
- // exists(DataFlow::PathNode source, DataFlow::PathNode sink |
- // exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- // exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- // exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
- // |
- // sink.getNode().getLocation() = location and
- // element = sink.getNode().toString() and
- // value = ""
- // )
- // }
- // * Not path-problem
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
exists(DataFlow::Node source, DataFlow::Node sink |
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
- exists(AsymmetricECCKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
+ exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
|
sink.getLocation() = location and
From b6a8c27d4858e2aee9c29d31d128fdcce6e8f0be Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 10 Oct 2022 16:22:42 -0400
Subject: [PATCH 033/465] delete experimental files
---
.../semmle/code/java/security/Encryption.qll | 5 +-
...BeforeAddingTaintFlowBackForAsymmetric.qll | 264 ------------------
...an-UpForDoubleTaintTracking-2022-10-10.qll | 239 ----------------
...ientKeySizeQuery_BeforeDataFlowRewrite.qll | 182 ------------
4 files changed, 1 insertion(+), 689 deletions(-)
delete mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll
delete mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll
delete mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index c19d51a2f4b..39e3f2d2110 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -249,10 +249,7 @@ string getASecureAlgorithmName() {
result =
[
"RSA", "SHA256", "SHA512", "CCM", "GCM", "AES(?)",
- "Blowfish", "ECIES" // ! Blowfish not actually secure based on https://rules.sonarsource.com/java/type/Vulnerability/RSPEC-4426 ??
- // ! hmm, other sources imply that it is secure...
- // ! also no DH here, etc.?
- // ! also is ECB matched with AES?
+ "Blowfish", "ECIES"
]
}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll
deleted file mode 100644
index 720ed112981..00000000000
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeAddingTaintFlowBackForAsymmetric.qll
+++ /dev/null
@@ -1,264 +0,0 @@
-import semmle.code.java.security.Encryption
-import semmle.code.java.dataflow.TaintTracking
-import semmle.code.java.dataflow.DataFlow
-
-//import DataFlow::PathGraph
-/**
- * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
- */
-class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
- AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(IntegerLiteral integer, VarAccess var |
- integer.getIntValue() < 2048 and
- source.asExpr() = integer
- or
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
- source.asExpr() = var.getVariable().getInitializer()
- )
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class ECGenParameterSpec extends RefType {
- ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-private class KeyGeneratorInitMethod extends Method {
- KeyGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyGenerator and
- this.hasName("init")
- }
-}
-
-/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-private class KeyPairGeneratorInitMethod extends Method {
- KeyPairGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyPairGenerator and
- this.hasName("initialize")
- }
-}
-
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-
-/** Taint configuration tracking flow from a key generator to a `init` method call. */
-private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaxCryptoKeyGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/**
- * Taint configuration tracking flow from a keypair generator to
- * an `initialize` method call.
- */
-private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaSecurityKeyPairGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/**
- * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-bindingset[type]
-private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- // flow needed to correctly determine algorithm type and
- // not match to ANY symmetric algorithm (although doesn't really matter since only have AES currently...)
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
- DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue() = type and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- cc.hasFlowPath(source, dest)
- ) and
- (
- // VarAccess case needed to handle FN of key-size stored in a variable
- // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
- // (e.g. not declared `final` in Java)
- exists(VarAccess var |
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- ma.getArgument(0) = var
- )
- or
- // exists(CompileTimeConstantExpr var |
- // //var.getUnderlyingExpr() instanceof IntegerLiteral and // can't include this...
- // var.getIntValue() < 128 and
- // ma.getArgument(0) = var
- // )
- // or
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
- ) and
- msg = "Key size should be at least 128 bits for " + type + " encryption."
-}
-
-/**
- * Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size.
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortAESKey(MethodAccess ma, string msg) {
- hasShortSymmetricKey(ma, msg, "AES")
-}
-
-/**
- * Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-bindingset[type]
-private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- //ma.getQualifier() instanceof JavaSecurityKeyPairGenerator and
- //ma.getQualifier().getBasicBlock() instanceof JavaSecurityKeyPairGenerator and
- // * USE BELOW
- ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
- // * USE ABOVE
- //ma.getQualifier().getBasicBlock().getNode(2) instanceof JavaSecurityKeyPairGenerator and
- // ma.getQualifier()
- // .getBasicBlock()
- // .getANode()
- // .(JavaSecurityKeyPairGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase() = type and
- //ma.getQualifier().getBasicBlock().getAPredecessor() instanceof JavaSecurityKeyPairGenerator and
- // * USE BELOW
- ma.getQualifier()
- .getBasicBlock()
- .getAPredecessor()
- .(JavaSecurityKeyPairGenerator)
- .getAlgoSpec()
- .(StringLiteral)
- .getValue()
- .toUpperCase() = type and
- // * USE ABOVE
- // flow needed to correctly determine algorithm type and
- // not match to ANY asymmetric algorithm
- // * REMOVE BELOW
- // exists(
- // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- // DataFlow::PathNode source, DataFlow::PathNode dest
- // |
- // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
- // source.getNode().asExpr() = jpg and
- // dest.getNode().asExpr() = ma.getQualifier() and
- // kc.hasFlowPath(source, dest)
- // ) and
- // * REMOVE ABOVE
- // VarAccess case needed to handle FN of key-size stored in a variable
- // Note: cannot use CompileTimeConstantExpr since will miss cases when variable is not a compile-time constant
- // (e.g. not declared `final` in Java)
- (
- exists(VarAccess var |
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 2048 and
- ma.getArgument(0) = var
- )
- or
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048
- // or
- // exists(
- // AsymmetricKeyTrackingConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
- // |
- // cfg.hasFlowPath(source, sink)
- // )
- ) and
- msg = "Key size should be at least 2048 bits for " + type + " encryption."
-}
-
-/**
- * Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "DSA") or
- hasShortAsymmetricKeyPair(ma, msg, "DH")
-}
-
-/**
- * Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "RSA")
-}
-
-/**
- * Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortECKeyPair(MethodAccess ma, string msg) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest) and
- DataFlow::localExprFlow(cie, ma.getArgument(0)) and
- ma.getArgument(0).getType() instanceof ECGenParameterSpec and
- getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
- ) and
- msg = "Key size should be at least 256 bits for EC encryption."
-}
-
-// ! refactor this so can use 'path-problem' select clause instead?
-predicate hasInsufficientKeySize(Expr e, string msg) {
- hasShortAESKey(e, msg) or
- hasShortDsaKeyPair(e, msg) or
- hasShortRsaKeyPair(e, msg) or
- hasShortECKeyPair(e, msg)
-}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll
deleted file mode 100644
index eec0e086f1b..00000000000
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeClean-UpForDoubleTaintTracking-2022-10-10.qll
+++ /dev/null
@@ -1,239 +0,0 @@
-import semmle.code.java.security.Encryption
-import semmle.code.java.dataflow.TaintTracking2
-import semmle.code.java.dataflow.TaintTracking
-import semmle.code.java.dataflow.DataFlow
-
-// TODO:
-// todo #0: find a better way to combine the two needed taint-tracking configs so can go back to having a path-graph...
-// todo #1: make representation of source that can be shared across the configs
-// todo #2: make representation of sink that can be shared across the configs
-// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
-// todo #4: refactor to be more like the Python version? (or not possible because of lack of DataFlow::Node for void method in Java?)
-// ******* DATAFLOW BELOW *************************************************************************
-/**
- * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
- */
-class AsymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
- AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(ClassInstanceExpr rsaGenParamSpec |
- rsaGenParamSpec.getConstructedType() instanceof RSAGenParameterSpec and // ! double-check if should just use getType() instead
- rsaGenParamSpec.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
- source.asExpr() = rsaGenParamSpec
- )
- or
- source.asExpr().(IntegerLiteral).getIntValue() < 2048
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, VarAccess va |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- //ma.getFile().getBaseName().matches("SignatureTest.java") and
- // va.getVariable()
- // .getAnAssignedValue()
- // .(JavaSecurityKeyPairGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase()
- // .matches(["RSA", "DSA", "DH"]) and
- // ma.getQualifier() = va and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kpgConfig.hasFlowPath(source, dest)
- ) and
- sink.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-// predicate hasInsufficientKeySize(string msg) { hasShortAsymmetricKeyPair(msg) }
-// predicate hasShortAsymmetricKeyPair(string msg) {
-// exists(AsymmetricKeyTrackingConfiguration config1, DataFlow::Node source, DataFlow::Node sink |
-// config1.hasFlow(source, sink)
-// ) and
-// msg = "Key size should be at least 2048 bits for " + "___" + " encryption."
-// }
-/**
- * Asymmetric (EC) key length data flow tracking configuration.
- */
-class AsymmetricECCKeyTrackingConfiguration extends TaintTracking2::Configuration {
- AsymmetricECCKeyTrackingConfiguration() { this = "AsymmetricECCKeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(ClassInstanceExpr ecGenParamSpec |
- getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
- source.asExpr() = ecGenParamSpec
- )
- or
- source.asExpr().(IntegerLiteral).getIntValue() < 256
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, VarAccess va |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- //ma.getArgument(0).getType() instanceof ECGenParameterSpec and // ! can generate EC with just the keysize and not the curve apparently... (based on netty/netty FP example)
- // va.getVariable()
- // .getAnAssignedValue()
- // .(JavaSecurityKeyPairGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase()
- // .matches(["EC%"]) and
- // ma.getQualifier() = va and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kpgConfig.hasFlowPath(source, dest)
- ) and
- sink.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-/**
- * Symmetric (AES) key length data flow tracking configuration.
- */
-class SymmetricKeyTrackingConfiguration extends TaintTracking2::Configuration {
- SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration2" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr().(IntegerLiteral).getIntValue() < 128
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, VarAccess va |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- // va.getVariable()
- // .getAnAssignedValue()
- // .(JavaxCryptoKeyGenerator)
- // .getAlgoSpec()
- // .(StringLiteral)
- // .getValue()
- // .toUpperCase()
- // .matches(["AES"]) and
- // ma.getQualifier() = va and
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("AES") and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kgConfig.hasFlowPath(source, dest)
- ) and
- sink.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-// ! below doesn't work for some reason...
-// predicate hasInsufficientKeySize2(DataFlow::PathNode source, DataFlow::PathNode sink) {
-// exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
-// or
-// exists(SymmetricKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink))
-// }
-// ******** Need the below for the above ********
-// ! move to Encryption.qll?
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class ECGenParameterSpec extends RefType {
- ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class RSAGenParameterSpec extends RefType {
- RSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
-}
-
-// ! move to Encryption.qll?
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-
-// ! move to Encryption.qll?
-/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-private class KeyGeneratorInitMethod extends Method {
- KeyGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyGenerator and
- this.hasName("init")
- }
-}
-
-// ! move to Encryption.qll?
-/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-private class KeyPairGeneratorInitMethod extends Method {
- KeyPairGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyPairGenerator and
- this.hasName("initialize")
- }
-}
-
-// ******* DATAFLOW ABOVE *************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ************************************************************************************************
-// ******* OLD/UNUSED OR EXPERIMENTAL CODE BELOW **************************************************
-class UnsafeSymmetricKeySize extends IntegerLiteral {
- UnsafeSymmetricKeySize() { this.getIntValue() < 128 }
-}
-
-class UnsafeAsymmetricKeySize extends IntegerLiteral {
- UnsafeAsymmetricKeySize() { this.getIntValue() < 2048 }
-}
-
-/** Taint configuration tracking flow from a key generator to a `init` method call. */
-private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaxCryptoKeyGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/**
- * Taint configuration tracking flow from a keypair generator to
- * an `initialize` method call.
- */
-private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaSecurityKeyPairGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll
deleted file mode 100644
index d6943adebc5..00000000000
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery_BeforeDataFlowRewrite.qll
+++ /dev/null
@@ -1,182 +0,0 @@
-import semmle.code.java.security.Encryption
-import semmle.code.java.dataflow.TaintTracking
-import semmle.code.java.dataflow.DataFlow3
-
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class ECGenParameterSpec extends RefType {
- ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-private class KeyGeneratorInitMethod extends Method {
- KeyGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyGenerator and
- this.hasName("init")
- }
-}
-
-/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-private class KeyPairGeneratorInitMethod extends Method {
- KeyPairGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyPairGenerator and
- this.hasName("initialize")
- }
-}
-
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-
-/** Taint configuration tracking flow from a key generator to a `init` method call. */
-private class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaxCryptoKeyGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/**
- * Taint configuration tracking flow from a keypair generator to
- * an `initialize` method call.
- */
-private class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
- KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaSecurityKeyPairGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/**
- * Holds if a symmetric `KeyGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-bindingset[type]
-private predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
- DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue() = type and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- //ma.getArgument(0) = var and // ! me
- //var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and // ! me
- cc.hasFlowPath(source, dest) //and
- //var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 // ! me
- ) and
- exists(VarAccess var |
- var.getVariable().getInitializer().getUnderlyingExpr() instanceof IntegerLiteral and
- var.getVariable().getInitializer().getUnderlyingExpr().toString().toInt() < 128 and
- //DataFlow3::localExprFlow(var, ma.getArgument(0)) and
- ma.getArgument(0) = var
- //ma.getArgument(0).(IntegerLiteral).getIntValue() < 128
- ) and
- msg = "Key size should be at least 128 bits for " + type + " encryption."
-}
-
-/**
- * Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size.
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortAESKey(MethodAccess ma, string msg) {
- hasShortSymmetricKey(ma, msg, "AES")
-}
-
-/**
- * Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm
- * `type` and initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-bindingset[type]
-private predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest)
- ) and
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
- msg = "Key size should be at least 2048 bits for " + type + " encryption."
-}
-
-/**
- * Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortDsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "DSA") or
- hasShortAsymmetricKeyPair(ma, msg, "DH")
-}
-
-/**
- * Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortRsaKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "RSA")
-}
-
-/**
- * Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size.
- *
- * `msg` provides a human-readable description of the problem.
- */
-private predicate hasShortECKeyPair(MethodAccess ma, string msg) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
- DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest) and
- DataFlow::localExprFlow(cie, ma.getArgument(0)) and
- ma.getArgument(0).getType() instanceof ECGenParameterSpec and
- getECKeySize(cie.getArgument(0).(StringLiteral).getValue()) < 256
- ) and
- msg = "Key size should be at least 256 bits for EC encryption."
-}
-
-// ! refactor this so can use 'path-problem' select clause instead?
-predicate hasInsufficientKeySize(Expr e, string msg) {
- hasShortAESKey(e, msg) or
- hasShortDsaKeyPair(e, msg) or
- hasShortRsaKeyPair(e, msg) or
- hasShortECKeyPair(e, msg)
-}
From e64825ff7afe9d15ed46864858979e3c2352a2b9 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 10 Oct 2022 23:01:55 -0400
Subject: [PATCH 034/465] fix code-scanning bot problems
---
.../security/InsufficientKeySizeQuery.qll | 39 ++++++++++---------
.../CWE/CWE-326/InsufficientKeySize.ql | 2 +-
.../CWE/CWE-326/InsufficientKeySize_OLD.java | 37 ------------------
.../CWE-326/InsufficientKeySizeTestOLD.txt | 11 ------
4 files changed, 22 insertions(+), 67 deletions(-)
delete mode 100644 java/ql/src/Security/CWE/CWE-326/InsufficientKeySize_OLD.java
delete mode 100644 java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTestOLD.txt
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index ed7408facf9..2891bbcac50 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,9 +1,11 @@
+/** Provides classes and predicates related to insufficient key sizes in Java. */
+
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow2
/**
- * Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ * An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
class AsymmetricNonECKeyTrackingConfiguration extends DataFlow2::Configuration {
AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
@@ -29,24 +31,24 @@ class AsymmetricNonECKeyTrackingConfiguration extends DataFlow2::Configuration {
or
// TODO: combine below three for less duplicated code
exists(ClassInstanceExpr rsaKeyGenParamSpec |
- rsaKeyGenParamSpec.getConstructedType() instanceof RSAKeyGenParameterSpec and
+ rsaKeyGenParamSpec.getConstructedType() instanceof RsaKeyGenParameterSpec and
sink.asExpr() = rsaKeyGenParamSpec.getArgument(0)
)
or
exists(ClassInstanceExpr dsaGenParamSpec |
- dsaGenParamSpec.getConstructedType() instanceof DSAGenParameterSpec and
+ dsaGenParamSpec.getConstructedType() instanceof DsaGenParameterSpec and
sink.asExpr() = dsaGenParamSpec.getArgument(0)
)
or
exists(ClassInstanceExpr dhGenParamSpec |
- dhGenParamSpec.getConstructedType() instanceof DHGenParameterSpec and
+ dhGenParamSpec.getConstructedType() instanceof DhGenParameterSpec and
sink.asExpr() = dhGenParamSpec.getArgument(0)
)
}
}
/**
- * Asymmetric (EC) key length data flow tracking configuration.
+ * An Asymmetric (EC) key length data flow tracking configuration.
*/
class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
@@ -72,7 +74,7 @@ class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
)
or
exists(ClassInstanceExpr ecGenParamSpec |
- ecGenParamSpec.getConstructedType() instanceof ECGenParameterSpec and
+ ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
//getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
sink.asExpr() = ecGenParamSpec.getArgument(0)
)
@@ -80,7 +82,7 @@ class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
}
/**
- * Symmetric (AES) key length data flow tracking configuration.
+ * A Symmetric (AES) key length data flow tracking configuration.
*/
class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
@@ -96,7 +98,7 @@ class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
DataFlow::PathNode source, DataFlow::PathNode dest
|
- jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("AES") and
+ jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
source.getNode().asExpr() = jcg and
dest.getNode().asExpr() = ma.getQualifier() and
kgConfig.hasFlowPath(source, dest)
@@ -108,7 +110,7 @@ class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
// ********************** Need the below models for the above configs **********************
// todo: move some/all of below to Encryption.qll or elsewhere?
-/** Data flow configuration tracking flow from a key generator to an `init` method call. */
+/** A data flow configuration tracking flow from a key generator to an `init` method call. */
private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
@@ -124,7 +126,7 @@ private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
}
}
-/** Data flow configuration tracking flow from a keypair generator to an `initialize` method call. */
+/** A data flow configuration tracking flow from a keypair generator to an `initialize` method call. */
private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration {
KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
@@ -141,23 +143,23 @@ private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration
}
/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class ECGenParameterSpec extends RefType {
- ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+private class EcGenParameterSpec extends RefType {
+ EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
-private class RSAKeyGenParameterSpec extends RefType {
- RSAKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+private class RsaKeyGenParameterSpec extends RefType {
+ RsaKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
}
/** The Java class `java.security.spec.DSAGenParameterSpec`. */
-private class DSAGenParameterSpec extends RefType {
- DSAGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
+private class DsaGenParameterSpec extends RefType {
+ DsaGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
}
/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
-private class DHGenParameterSpec extends RefType {
- DHGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
+private class DhGenParameterSpec extends RefType {
+ DhGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
}
/** The `init` method declared in `javax.crypto.KeyGenerator`. */
@@ -190,6 +192,7 @@ private int getECKeySize(string algorithm) {
}
// ******* DATAFLOW ABOVE *************************************************************************
// TODO:
+// todo #0: look into use of specs without keygens; should spec not be a sink in these cases?
// todo #1: make representation of source that can be shared across the configs
// todo #2: make representation of sink that can be shared across the configs
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index a5401dfb157..c1f99e67b9f 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -19,4 +19,4 @@ where
exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
-select sink, "This $@ is too small and creates a key $@.", source, "key size", sink, "here"
+select sink, "This $@ is too small.", source, "key size"
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize_OLD.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize_OLD.java
deleted file mode 100644
index ff30196abb5..00000000000
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize_OLD.java
+++ /dev/null
@@ -1,37 +0,0 @@
-public class InsufficientKeySize {
- public void CryptoMethod() {
- KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
- // BAD: Key size is less than 128
- keyGen1.init(64);
-
- KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
- // GOOD: Key size is no less than 128
- keyGen2.init(128);
-
- KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
- // BAD: Key size is less than 2048
- keyPairGen1.initialize(1024);
-
- KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
- // GOOD: Key size is no less than 2048
- keyPairGen2.initialize(2048);
-
- KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
- // BAD: Key size is less than 2048
- keyPairGen3.initialize(1024);
-
- KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
- // GOOD: Key size is no less than 2048
- keyPairGen4.initialize(2048);
-
- KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
- keyPairGen5.initialize(ecSpec1);
-
- KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 256
- ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
- keyPairGen6.initialize(ecSpec2);
- }
-}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTestOLD.txt b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTestOLD.txt
deleted file mode 100644
index 421335b84ff..00000000000
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTestOLD.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-| InsufficientKeySize.java:9:9:9:24 | init(...) | Key size should be at least 128 bits for AES encryption. |
-| InsufficientKeySize.java:17:9:17:36 | initialize(...) | Key size should be at least 2048 bits for RSA encryption. |
-| InsufficientKeySize.java:25:9:25:36 | initialize(...) | Key size should be at least 2048 bits for DSA encryption. |
-| InsufficientKeySize.java:34:9:34:39 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:38:9:38:67 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:48:9:48:39 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:53:9:53:39 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:58:9:58:40 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:68:9:68:40 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:78:9:78:40 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
-| InsufficientKeySize.java:87:9:87:37 | initialize(...) | Key size should be at least 2048 bits for DH encryption. |
From 26f4abf12b6f706ca3d98b6d844bec894e2715b1 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 11 Oct 2022 16:31:02 -0400
Subject: [PATCH 035/465] remove globalflow for key(pair)gen
---
.../security/InsufficientKeySizeQuery.qll | 124 +++++++++---------
.../CWE-326/InsufficientKeySizeTest.java | 4 +-
2 files changed, 64 insertions(+), 64 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 2891bbcac50..c6dba3959cc 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -15,17 +15,19 @@ class AsymmetricNonECKeyTrackingConfiguration extends DataFlow2::Configuration {
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
+ exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kpgConfig.hasFlowPath(source, dest)
- ) and
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+ // exists(
+ // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
+ // DataFlow::PathNode source, DataFlow::PathNode dest
+ // |
+ // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ // source.getNode().asExpr() = jpg and
+ // dest.getNode().asExpr() = ma.getQualifier() and
+ // kpgConfig.hasFlowPath(source, dest)
+ // ) and
sink.asExpr() = ma.getArgument(0)
)
or
@@ -59,17 +61,19 @@ class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
+ exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kpgConfig.hasFlowPath(source, dest)
- ) and
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+ // exists(
+ // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
+ // DataFlow::PathNode source, DataFlow::PathNode dest
+ // |
+ // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ // source.getNode().asExpr() = jpg and
+ // dest.getNode().asExpr() = ma.getQualifier() and
+ // kpgConfig.hasFlowPath(source, dest)
+ // ) and
sink.asExpr() = ma.getArgument(0)
)
or
@@ -92,17 +96,19 @@ class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
+ exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
ma.getMethod() instanceof KeyGeneratorInitMethod and
- exists(
- JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
- DataFlow::PathNode source, DataFlow::PathNode dest
- |
- jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
- source.getNode().asExpr() = jcg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kgConfig.hasFlowPath(source, dest)
- ) and
+ jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
+ DataFlow::localExprFlow(jcg, ma.getQualifier()) and
+ // exists(
+ // JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
+ // DataFlow::PathNode source, DataFlow::PathNode dest
+ // |
+ // jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
+ // source.getNode().asExpr() = jcg and
+ // dest.getNode().asExpr() = ma.getQualifier() and
+ // kgConfig.hasFlowPath(source, dest)
+ // ) and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -110,38 +116,32 @@ class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
// ********************** Need the below models for the above configs **********************
// todo: move some/all of below to Encryption.qll or elsewhere?
-/** A data flow configuration tracking flow from a key generator to an `init` method call. */
-private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
- KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaxCryptoKeyGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/** A data flow configuration tracking flow from a keypair generator to an `initialize` method call. */
-private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration {
- KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- source.asExpr() instanceof JavaSecurityKeyPairGenerator
- }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
- }
-}
-
+// /** A data flow configuration tracking flow from a key generator to an `init` method call. */
+// private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
+// KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
+// override predicate isSource(DataFlow::Node source) {
+// source.asExpr() instanceof JavaxCryptoKeyGenerator
+// }
+// override predicate isSink(DataFlow::Node sink) {
+// exists(MethodAccess ma |
+// ma.getMethod() instanceof KeyGeneratorInitMethod and
+// sink.asExpr() = ma.getQualifier()
+// )
+// }
+// }
+// /** A data flow configuration tracking flow from a keypair generator to an `initialize` method call. */
+// private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration {
+// KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
+// override predicate isSource(DataFlow::Node source) {
+// source.asExpr() instanceof JavaSecurityKeyPairGenerator
+// }
+// override predicate isSink(DataFlow::Node sink) {
+// exists(MethodAccess ma |
+// ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+// sink.asExpr() = ma.getQualifier()
+// )
+// }
+// }
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class EcGenParameterSpec extends RefType {
EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 9ba11f1c632..e6a61d7bb4c 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -208,7 +208,7 @@ public class InsufficientKeySizeTest {
keyGen.init(keySize); // $ hasInsufficientKeySize
// BAD: Key size is less than 2048
- kg.init(64); // $ hasInsufficientKeySize
+ kg.init(64); // $ MISSING: hasInsufficientKeySize
}
//! refactor this to use expected-value tag and combine with above method
@@ -224,7 +224,7 @@ public class InsufficientKeySizeTest {
keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
// BAD: Key size is less than 2048
- kpg.initialize(1024); // $ hasInsufficientKeySize
+ kpg.initialize(1024); // $ MISSING: hasInsufficientKeySize
}
//! refactor this to use expected-value tag and combine with above method
From 3e8748e639014f5edf27b12439bb3efdcd5364b9 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 11 Oct 2022 16:54:07 -0400
Subject: [PATCH 036/465] add path-graph back to query alerts
---
.../code/java/security/InsufficientKeySizeQuery.qll | 7 +++----
.../src/Security/CWE/CWE-326/InsufficientKeySize.ql | 13 +++++++------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index c6dba3959cc..ce2d3c3a927 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -2,12 +2,11 @@
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.DataFlow
-import semmle.code.java.dataflow.DataFlow2
/**
* An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
-class AsymmetricNonECKeyTrackingConfiguration extends DataFlow2::Configuration {
+class AsymmetricNonECKeyTrackingConfiguration extends DataFlow::Configuration {
AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -52,7 +51,7 @@ class AsymmetricNonECKeyTrackingConfiguration extends DataFlow2::Configuration {
/**
* An Asymmetric (EC) key length data flow tracking configuration.
*/
-class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
+class AsymmetricECKeyTrackingConfiguration extends DataFlow::Configuration {
AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
@@ -88,7 +87,7 @@ class AsymmetricECKeyTrackingConfiguration extends DataFlow2::Configuration {
/**
* A Symmetric (AES) key length data flow tracking configuration.
*/
-class SymmetricKeyTrackingConfiguration extends DataFlow2::Configuration {
+class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index c1f99e67b9f..27f4d7e2612 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -2,7 +2,7 @@
* @name Insufficient key size used with a cryptographic algorithm
* @description Using cryptographic algorithms with too small of a key size can
* allow an attacker to compromise security.
- * @kind problem
+ * @kind path-problem
* @problem.severity error
* @security-severity 7.5
* @precision high
@@ -13,10 +13,11 @@
import java
import semmle.code.java.security.InsufficientKeySizeQuery
+import DataFlow::PathGraph
-from DataFlow::Node source, DataFlow::Node sink
+from DataFlow::PathNode source, DataFlow::PathNode sink
where
- exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
-select sink, "This $@ is too small.", source, "key size"
+ exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
From 29de0c67482aa8e95296e5a9eb464003571787cc Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 11 Oct 2022 22:29:48 -0400
Subject: [PATCH 037/465] make one config for asymm with flow states; seems to
work...
---
.../security/InsufficientKeySizeQuery.qll | 78 +++++++++++++++++++
.../CWE/CWE-326/InsufficientKeySize.ql | 6 +-
.../CWE-326/InsufficientKeySizeTest.ql | 15 ++--
3 files changed, 91 insertions(+), 8 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index ce2d3c3a927..51c5bf702e0 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -2,6 +2,84 @@
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.TaintTracking
+
+//import semmle.code.java.dataflow.internal.DataFlowImplCommonPublic
+//import semmle.code.java.dataflow.FlowSources
+//import semmle.code.java.dataflow.internal.DataFlowNodes
+/**
+ * An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ */
+class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+ AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
+ //state instanceof DataFlow::FlowStateEmpty and
+ source.asExpr().(IntegerLiteral).getIntValue() < 2048 and state = "2048"
+ or
+ source.asExpr().(IntegerLiteral).getIntValue() < 256 and state = "256"
+ or
+ getECKeySize(source.asExpr().(StringLiteral).getValue()) < 256 and state = "256" // need this for the cases when the key size is embedded in the curve name.
+ }
+
+ override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
+ exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ (
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+ sink.asExpr() = ma.getArgument(0) and
+ //ma.getArgument(0).(LocalSourceNode).flowsTo(sink) and
+ //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 2048 and
+ state = "2048"
+ )
+ or
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+ sink.asExpr() = ma.getArgument(0) and
+ //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 256 and
+ state = "256"
+ )
+ or
+ // TODO: combine below three for less duplicated code
+ exists(ClassInstanceExpr rsaKeyGenParamSpec |
+ rsaKeyGenParamSpec.getConstructedType() instanceof RsaKeyGenParameterSpec and
+ sink.asExpr() = rsaKeyGenParamSpec.getArgument(0) and
+ state = "2048"
+ )
+ or
+ exists(ClassInstanceExpr dsaGenParamSpec |
+ dsaGenParamSpec.getConstructedType() instanceof DsaGenParameterSpec and
+ sink.asExpr() = dsaGenParamSpec.getArgument(0) and
+ state = "2048"
+ )
+ or
+ exists(ClassInstanceExpr dhGenParamSpec |
+ dhGenParamSpec.getConstructedType() instanceof DhGenParameterSpec and
+ sink.asExpr() = dhGenParamSpec.getArgument(0) and
+ state = "2048"
+ )
+ or
+ exists(ClassInstanceExpr ecGenParamSpec |
+ ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
+ sink.asExpr() = ecGenParamSpec.getArgument(0) and
+ state = "256"
+ )
+ }
+
+ override predicate isAdditionalFlowStep(
+ DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
+ DataFlow::FlowState state2
+ ) {
+ exists(IntegerLiteral intLiteral |
+ state1 = "" and
+ state2 = intLiteral.toString() and
+ node1.asExpr() = intLiteral and
+ node2.asExpr() = intLiteral
+ //intLiteral.toString().toInt() = 64 // test viability of this craziness
+ )
+ }
+}
/**
* An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 27f4d7e2612..979a6d4958b 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -17,7 +17,9 @@ import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink
where
- exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+ or
+ // exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ // exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index eef02e9352a..bd7c164273f 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -1,6 +1,7 @@
import java
import TestUtilities.InlineExpectationsTest
import semmle.code.java.security.InsufficientKeySizeQuery
+import DataFlow::PathGraph
class InsufficientKeySizeTest extends InlineExpectationsTest {
InsufficientKeySizeTest() { this = "InsufficientKeySize" }
@@ -9,13 +10,15 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
- exists(DataFlow::Node source, DataFlow::Node sink |
- exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlow(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlow(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlow(source, sink))
+ exists(DataFlow::PathNode source, DataFlow::PathNode sink |
+ exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+ or
+ // exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ // exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
|
- sink.getLocation() = location and
- element = sink.toString() and
+ sink.getNode().getLocation() = location and
+ element = sink.getNode().toString() and
value = ""
)
}
From 01c2a8cbba765d4fe1750d03fa89d95cc43b7d88 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 12 Oct 2022 08:51:22 -0400
Subject: [PATCH 038/465] add symm to the single config; still seems to work
---
.../security/InsufficientKeySizeQuery.qll | 22 +++++++++++++++----
.../CWE/CWE-326/InsufficientKeySize.ql | 11 +++++-----
.../CWE-326/InsufficientKeySizeTest.ql | 10 ++++-----
3 files changed, 28 insertions(+), 15 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 51c5bf702e0..d5739fa2310 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -8,13 +8,17 @@ import semmle.code.java.dataflow.TaintTracking
//import semmle.code.java.dataflow.FlowSources
//import semmle.code.java.dataflow.internal.DataFlowNodes
/**
- * An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ * A key length data flow tracking configuration.
*/
-class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
- AsymmetricKeyTrackingConfiguration() { this = "AsymmetricKeyTrackingConfiguration" }
+class KeyTrackingConfiguration extends DataFlow::Configuration {
+ KeyTrackingConfiguration() { this = "KeyTrackingConfiguration" }
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
//state instanceof DataFlow::FlowStateEmpty and
+ // SYMMETRIC
+ source.asExpr().(IntegerLiteral).getIntValue() < 128 and state = "128"
+ or
+ // ASYMMETRIC
source.asExpr().(IntegerLiteral).getIntValue() < 2048 and state = "2048"
or
source.asExpr().(IntegerLiteral).getIntValue() < 256 and state = "256"
@@ -23,6 +27,16 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
}
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
+ // SYMMETRIC
+ exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
+ DataFlow::localExprFlow(jcg, ma.getQualifier()) and
+ sink.asExpr() = ma.getArgument(0) and
+ state = "128"
+ )
+ or
+ // ASYMMETRIC
exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
(
@@ -67,6 +81,7 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
)
}
+ // ! FlowStates seem to work without even including a step like the below... hmmm
override predicate isAdditionalFlowStep(
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
DataFlow::FlowState state2
@@ -76,7 +91,6 @@ class AsymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
state2 = intLiteral.toString() and
node1.asExpr() = intLiteral and
node2.asExpr() = intLiteral
- //intLiteral.toString().toInt() = 64 // test viability of this craziness
)
}
}
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 979a6d4958b..040f17abfc0 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -16,10 +16,9 @@ import semmle.code.java.security.InsufficientKeySizeQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink
-where
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- or
- // exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- // exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+where exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+//or
+// exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+// exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+// exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index bd7c164273f..6d0d83f25fe 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -1,8 +1,8 @@
import java
import TestUtilities.InlineExpectationsTest
import semmle.code.java.security.InsufficientKeySizeQuery
-import DataFlow::PathGraph
+//import DataFlow::PathGraph // Note: importing this messes up tests - adds edges and nodes to actual file...
class InsufficientKeySizeTest extends InlineExpectationsTest {
InsufficientKeySizeTest() { this = "InsufficientKeySize" }
@@ -11,12 +11,12 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
exists(DataFlow::PathNode source, DataFlow::PathNode sink |
- exists(AsymmetricKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- or
+ exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+ |
+ //or
// exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
// exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
- |
+ //exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
sink.getNode().getLocation() = location and
element = sink.getNode().toString() and
value = ""
From 0fc4a33d431ef97059a9404eabb733a9a417bda1 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 12 Oct 2022 08:54:06 -0400
Subject: [PATCH 039/465] remove commented-out code
---
.../security/InsufficientKeySizeQuery.qll | 230 +++++++-----------
1 file changed, 85 insertions(+), 145 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index d5739fa2310..c141cd04529 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -4,97 +4,6 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
-//import semmle.code.java.dataflow.internal.DataFlowImplCommonPublic
-//import semmle.code.java.dataflow.FlowSources
-//import semmle.code.java.dataflow.internal.DataFlowNodes
-/**
- * A key length data flow tracking configuration.
- */
-class KeyTrackingConfiguration extends DataFlow::Configuration {
- KeyTrackingConfiguration() { this = "KeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
- //state instanceof DataFlow::FlowStateEmpty and
- // SYMMETRIC
- source.asExpr().(IntegerLiteral).getIntValue() < 128 and state = "128"
- or
- // ASYMMETRIC
- source.asExpr().(IntegerLiteral).getIntValue() < 2048 and state = "2048"
- or
- source.asExpr().(IntegerLiteral).getIntValue() < 256 and state = "256"
- or
- getECKeySize(source.asExpr().(StringLiteral).getValue()) < 256 and state = "256" // need this for the cases when the key size is embedded in the curve name.
- }
-
- override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
- // SYMMETRIC
- exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
- DataFlow::localExprFlow(jcg, ma.getQualifier()) and
- sink.asExpr() = ma.getArgument(0) and
- state = "128"
- )
- or
- // ASYMMETRIC
- exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- (
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- sink.asExpr() = ma.getArgument(0) and
- //ma.getArgument(0).(LocalSourceNode).flowsTo(sink) and
- //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 2048 and
- state = "2048"
- )
- or
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- sink.asExpr() = ma.getArgument(0) and
- //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 256 and
- state = "256"
- )
- or
- // TODO: combine below three for less duplicated code
- exists(ClassInstanceExpr rsaKeyGenParamSpec |
- rsaKeyGenParamSpec.getConstructedType() instanceof RsaKeyGenParameterSpec and
- sink.asExpr() = rsaKeyGenParamSpec.getArgument(0) and
- state = "2048"
- )
- or
- exists(ClassInstanceExpr dsaGenParamSpec |
- dsaGenParamSpec.getConstructedType() instanceof DsaGenParameterSpec and
- sink.asExpr() = dsaGenParamSpec.getArgument(0) and
- state = "2048"
- )
- or
- exists(ClassInstanceExpr dhGenParamSpec |
- dhGenParamSpec.getConstructedType() instanceof DhGenParameterSpec and
- sink.asExpr() = dhGenParamSpec.getArgument(0) and
- state = "2048"
- )
- or
- exists(ClassInstanceExpr ecGenParamSpec |
- ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
- sink.asExpr() = ecGenParamSpec.getArgument(0) and
- state = "256"
- )
- }
-
- // ! FlowStates seem to work without even including a step like the below... hmmm
- override predicate isAdditionalFlowStep(
- DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
- DataFlow::FlowState state2
- ) {
- exists(IntegerLiteral intLiteral |
- state1 = "" and
- state2 = intLiteral.toString() and
- node1.asExpr() = intLiteral and
- node2.asExpr() = intLiteral
- )
- }
-}
-
/**
* An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
*/
@@ -110,15 +19,6 @@ class AsymmetricNonECKeyTrackingConfiguration extends DataFlow::Configuration {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- // exists(
- // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
- // DataFlow::PathNode source, DataFlow::PathNode dest
- // |
- // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- // source.getNode().asExpr() = jpg and
- // dest.getNode().asExpr() = ma.getQualifier() and
- // kpgConfig.hasFlowPath(source, dest)
- // ) and
sink.asExpr() = ma.getArgument(0)
)
or
@@ -156,21 +56,11 @@ class AsymmetricECKeyTrackingConfiguration extends DataFlow::Configuration {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- // exists(
- // JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kpgConfig,
- // DataFlow::PathNode source, DataFlow::PathNode dest
- // |
- // jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- // source.getNode().asExpr() = jpg and
- // dest.getNode().asExpr() = ma.getQualifier() and
- // kpgConfig.hasFlowPath(source, dest)
- // ) and
sink.asExpr() = ma.getArgument(0)
)
or
exists(ClassInstanceExpr ecGenParamSpec |
ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
- //getECKeySize(ecGenParamSpec.getArgument(0).(StringLiteral).getValue()) < 256 and
sink.asExpr() = ecGenParamSpec.getArgument(0)
)
}
@@ -191,15 +81,6 @@ class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
ma.getMethod() instanceof KeyGeneratorInitMethod and
jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
DataFlow::localExprFlow(jcg, ma.getQualifier()) and
- // exists(
- // JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration kgConfig,
- // DataFlow::PathNode source, DataFlow::PathNode dest
- // |
- // jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
- // source.getNode().asExpr() = jcg and
- // dest.getNode().asExpr() = ma.getQualifier() and
- // kgConfig.hasFlowPath(source, dest)
- // ) and
sink.asExpr() = ma.getArgument(0)
)
}
@@ -207,32 +88,6 @@ class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
// ********************** Need the below models for the above configs **********************
// todo: move some/all of below to Encryption.qll or elsewhere?
-// /** A data flow configuration tracking flow from a key generator to an `init` method call. */
-// private class KeyGeneratorInitConfiguration extends DataFlow::Configuration {
-// KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
-// override predicate isSource(DataFlow::Node source) {
-// source.asExpr() instanceof JavaxCryptoKeyGenerator
-// }
-// override predicate isSink(DataFlow::Node sink) {
-// exists(MethodAccess ma |
-// ma.getMethod() instanceof KeyGeneratorInitMethod and
-// sink.asExpr() = ma.getQualifier()
-// )
-// }
-// }
-// /** A data flow configuration tracking flow from a keypair generator to an `initialize` method call. */
-// private class KeyPairGeneratorInitConfiguration extends DataFlow::Configuration {
-// KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
-// override predicate isSource(DataFlow::Node source) {
-// source.asExpr() instanceof JavaSecurityKeyPairGenerator
-// }
-// override predicate isSink(DataFlow::Node sink) {
-// exists(MethodAccess ma |
-// ma.getMethod() instanceof KeyPairGeneratorInitMethod and
-// sink.asExpr() = ma.getQualifier()
-// )
-// }
-// }
/** The Java class `java.security.spec.ECGenParameterSpec`. */
private class EcGenParameterSpec extends RefType {
EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
@@ -288,3 +143,88 @@ private int getECKeySize(string algorithm) {
// todo #2: make representation of sink that can be shared across the configs
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
// todo #4: refactor to be more like the Python (or C#) version? (or not possible because of lack of DataFlow::Node for void method in Java?)
+// ******* SINGLE CONFIG ATTEMPT BELOW *************************************************************************
+// /**
+// * A key length data flow tracking configuration.
+// */
+// class KeyTrackingConfiguration extends DataFlow::Configuration {
+// KeyTrackingConfiguration() { this = "KeyTrackingConfiguration" }
+// override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
+// //state instanceof DataFlow::FlowStateEmpty and
+// // SYMMETRIC
+// source.asExpr().(IntegerLiteral).getIntValue() < 128 and state = "128"
+// or
+// // ASYMMETRIC
+// source.asExpr().(IntegerLiteral).getIntValue() < 2048 and state = "2048"
+// or
+// source.asExpr().(IntegerLiteral).getIntValue() < 256 and state = "256"
+// or
+// getECKeySize(source.asExpr().(StringLiteral).getValue()) < 256 and state = "256" // need this for the cases when the key size is embedded in the curve name.
+// }
+// override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
+// // SYMMETRIC
+// exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
+// ma.getMethod() instanceof KeyGeneratorInitMethod and
+// jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
+// DataFlow::localExprFlow(jcg, ma.getQualifier()) and
+// sink.asExpr() = ma.getArgument(0) and
+// state = "128"
+// )
+// or
+// // ASYMMETRIC
+// exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
+// ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+// (
+// jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+// DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+// sink.asExpr() = ma.getArgument(0) and
+// //ma.getArgument(0).(LocalSourceNode).flowsTo(sink) and
+// //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 2048 and
+// state = "2048"
+// )
+// or
+// jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+// DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+// sink.asExpr() = ma.getArgument(0) and
+// //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 256 and
+// state = "256"
+// )
+// or
+// // TODO: combine below three for less duplicated code
+// exists(ClassInstanceExpr rsaKeyGenParamSpec |
+// rsaKeyGenParamSpec.getConstructedType() instanceof RsaKeyGenParameterSpec and
+// sink.asExpr() = rsaKeyGenParamSpec.getArgument(0) and
+// state = "2048"
+// )
+// or
+// exists(ClassInstanceExpr dsaGenParamSpec |
+// dsaGenParamSpec.getConstructedType() instanceof DsaGenParameterSpec and
+// sink.asExpr() = dsaGenParamSpec.getArgument(0) and
+// state = "2048"
+// )
+// or
+// exists(ClassInstanceExpr dhGenParamSpec |
+// dhGenParamSpec.getConstructedType() instanceof DhGenParameterSpec and
+// sink.asExpr() = dhGenParamSpec.getArgument(0) and
+// state = "2048"
+// )
+// or
+// exists(ClassInstanceExpr ecGenParamSpec |
+// ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
+// sink.asExpr() = ecGenParamSpec.getArgument(0) and
+// state = "256"
+// )
+// }
+// // ! FlowStates seem to work without even including a step like the below... hmmm
+// override predicate isAdditionalFlowStep(
+// DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
+// DataFlow::FlowState state2
+// ) {
+// exists(IntegerLiteral intLiteral |
+// state1 = "" and
+// state2 = intLiteral.toString() and
+// node1.asExpr() = intLiteral and
+// node2.asExpr() = intLiteral
+// )
+// }
+// }
From 37d85587e007472b976d2416c5a36a5485d20ecd Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 12 Oct 2022 15:39:57 -0400
Subject: [PATCH 040/465] refactor code into InsufficientKeySize.qll
---
.../semmle/code/java/security/Encryption.qll | 40 ++++++
.../java/security/InsufficientKeySize.qll | 100 +++++++++++++
.../security/InsufficientKeySizeQuery.qll | 133 ++----------------
.../CWE/CWE-326/InsufficientKeySize.ql | 11 +-
.../CWE-326/InsufficientKeySizeTest.ql | 10 +-
5 files changed, 165 insertions(+), 129 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 39e3f2d2110..a1535a438b5 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -83,11 +83,27 @@ class KeyGenerator extends RefType {
KeyGenerator() { this.hasQualifiedName("javax.crypto", "KeyGenerator") }
}
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyGenerator and
+ this.hasName("init")
+ }
+}
+
/** The Java class `java.security.KeyPairGenerator`. */
class KeyPairGenerator extends RefType {
KeyPairGenerator() { this.hasQualifiedName("java.security", "KeyPairGenerator") }
}
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ this.getDeclaringType() instanceof KeyPairGenerator and
+ this.hasName("initialize")
+ }
+}
+
/** The `verify` method of the class `javax.net.ssl.HostnameVerifier`. */
class HostnameVerifierVerify extends Method {
HostnameVerifierVerify() {
@@ -307,6 +323,12 @@ class JavaxCryptoSecretKey extends JavaxCryptoAlgoSpec {
}
}
+// TODO: consider extending JavaxCryptoAlgoSpec as above does; will need to override getAlgoSpec() method
+/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
+class DhGenParameterSpec extends RefType {
+ DhGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
+}
+
class JavaxCryptoKeyGenerator extends JavaxCryptoAlgoSpec {
JavaxCryptoKeyGenerator() {
exists(Method m | m.getAReference() = this |
@@ -367,6 +389,24 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
+// TODO: consider extending JavaSecurityAlgoSpec as above does; will need to override getAlgoSpec() method
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+class EcGenParameterSpec extends RefType {
+ EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+// TODO: consider extending JavaSecurityAlgoSpec as above does; will need to override getAlgoSpec() method
+/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
+class RsaKeyGenParameterSpec extends RefType {
+ RsaKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+}
+
+// TODO: consider extending JavaSecurityAlgoSpec as above does; will need to override getAlgoSpec() method
+/** The Java class `java.security.spec.DSAGenParameterSpec`. */
+class DsaGenParameterSpec extends RefType {
+ DsaGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
+}
+
/** A method call to the Java class `java.security.KeyPairGenerator`. */
class JavaSecurityKeyPairGenerator extends JavaxCryptoAlgoSpec {
JavaSecurityKeyPairGenerator() {
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
new file mode 100644
index 00000000000..4aa4bbe7a44
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -0,0 +1,100 @@
+/** Provides classes and predicates related to insufficient key sizes in Java. */
+
+private import semmle.code.java.security.Encryption
+private import semmle.code.java.dataflow.DataFlow
+
+/** A source for an insufficient key size. */
+abstract class InsufficientKeySizeSource extends DataFlow::Node { }
+
+/** A sink for an insufficient key size. */
+abstract class InsufficientKeySizeSink extends DataFlow::Node { }
+
+// TODO: Consider if below 3 sources should be private and if it's possible to only use InsufficientKeySizeSource in the configs
+// TODO: add QLDocs if keeping non-private
+class AsymmetricNonECSource extends InsufficientKeySizeSource {
+ AsymmetricNonECSource() { getNodeIntValue(this) < 2048 }
+}
+
+class AsymmetricECSource extends InsufficientKeySizeSource {
+ AsymmetricECSource() {
+ getNodeIntValue(this) < 256 or
+ getECKeySize(this.asExpr().(StringLiteral).getValue()) < 256 // need this for the cases when the key size is embedded in the curve name.
+ }
+}
+
+class SymmetricSource extends InsufficientKeySizeSource {
+ SymmetricSource() { getNodeIntValue(this) < 128 }
+}
+
+private int getNodeIntValue(DataFlow::Node node) {
+ result = node.asExpr().(IntegerLiteral).getIntValue()
+}
+
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+private int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
+class AsymmetricNonECSink extends InsufficientKeySizeSink {
+ AsymmetricNonECSink() {
+ exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+ this.asExpr() = ma.getArgument(0)
+ )
+ or
+ exists(ClassInstanceExpr genParamSpec |
+ genParamSpec.getConstructedType() instanceof AsymmetricNonECSpec and
+ this.asExpr() = genParamSpec.getArgument(0)
+ )
+ }
+}
+
+// TODO: move to Encryption.qll? or keep here since specific to this query?
+private class AsymmetricNonECSpec extends RefType {
+ AsymmetricNonECSpec() {
+ this instanceof RsaKeyGenParameterSpec or
+ this instanceof DsaGenParameterSpec or
+ this instanceof DhGenParameterSpec
+ }
+}
+
+class AsymmetricECSink extends InsufficientKeySizeSink {
+ AsymmetricECSink() {
+ exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ DataFlow::localExprFlow(jpg, ma.getQualifier()) and
+ this.asExpr() = ma.getArgument(0)
+ )
+ or
+ exists(ClassInstanceExpr ecGenParamSpec |
+ ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
+ this.asExpr() = ecGenParamSpec.getArgument(0)
+ )
+ }
+}
+
+class SymmetricSink extends InsufficientKeySizeSink {
+ SymmetricSink() {
+ exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
+ DataFlow::localExprFlow(jcg, ma.getQualifier()) and
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+// TODO:
+// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
+// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
+// todo #5:
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index c141cd04529..51800aa8df9 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,148 +1,43 @@
-/** Provides classes and predicates related to insufficient key sizes in Java. */
+/** Provides data flow configurations to be used in queries related to insufficient key sizes. */
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.security.InsufficientKeySize
/**
- * An Asymmetric (RSA, DSA, DH) key length data flow tracking configuration.
+ * A data flow configuration for tracking non-elliptic curve asymmetric algorithms
+ * (RSA, DSA, and DH) key sizes.
*/
class AsymmetricNonECKeyTrackingConfiguration extends DataFlow::Configuration {
AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
- override predicate isSource(DataFlow::Node source) {
- source.asExpr().(IntegerLiteral).getIntValue() < 2048
- }
+ override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricNonECSource }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- sink.asExpr() = ma.getArgument(0)
- )
- or
- // TODO: combine below three for less duplicated code
- exists(ClassInstanceExpr rsaKeyGenParamSpec |
- rsaKeyGenParamSpec.getConstructedType() instanceof RsaKeyGenParameterSpec and
- sink.asExpr() = rsaKeyGenParamSpec.getArgument(0)
- )
- or
- exists(ClassInstanceExpr dsaGenParamSpec |
- dsaGenParamSpec.getConstructedType() instanceof DsaGenParameterSpec and
- sink.asExpr() = dsaGenParamSpec.getArgument(0)
- )
- or
- exists(ClassInstanceExpr dhGenParamSpec |
- dhGenParamSpec.getConstructedType() instanceof DhGenParameterSpec and
- sink.asExpr() = dhGenParamSpec.getArgument(0)
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricNonECSink }
}
/**
- * An Asymmetric (EC) key length data flow tracking configuration.
+ * A data flow configuration for tracking elliptic curve (EC) asymmetric
+ * algorithm key sizes.
*/
class AsymmetricECKeyTrackingConfiguration extends DataFlow::Configuration {
AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
- override predicate isSource(DataFlow::Node source) {
- source.asExpr().(IntegerLiteral).getIntValue() < 256 or
- getECKeySize(source.asExpr().(StringLiteral).getValue()) < 256 // need this for the cases when the key size is embedded in the curve name.
- }
+ override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricECSource }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- sink.asExpr() = ma.getArgument(0)
- )
- or
- exists(ClassInstanceExpr ecGenParamSpec |
- ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
- sink.asExpr() = ecGenParamSpec.getArgument(0)
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricECSink }
}
-/**
- * A Symmetric (AES) key length data flow tracking configuration.
- */
+/** A data flow configuration for tracking symmetric algorithm (AES) key sizes. */
class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
- override predicate isSource(DataFlow::Node source) {
- source.asExpr().(IntegerLiteral).getIntValue() < 128
- }
+ override predicate isSource(DataFlow::Node source) { source instanceof SymmetricSource }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
- DataFlow::localExprFlow(jcg, ma.getQualifier()) and
- sink.asExpr() = ma.getArgument(0)
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sink instanceof SymmetricSink }
}
-
-// ********************** Need the below models for the above configs **********************
-// todo: move some/all of below to Encryption.qll or elsewhere?
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-private class EcGenParameterSpec extends RefType {
- EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
-private class RsaKeyGenParameterSpec extends RefType {
- RsaKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
-}
-
-/** The Java class `java.security.spec.DSAGenParameterSpec`. */
-private class DsaGenParameterSpec extends RefType {
- DsaGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
-}
-
-/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
-private class DhGenParameterSpec extends RefType {
- DhGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
-}
-
-/** The `init` method declared in `javax.crypto.KeyGenerator`. */
-private class KeyGeneratorInitMethod extends Method {
- KeyGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyGenerator and
- this.hasName("init")
- }
-}
-
-/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
-private class KeyPairGeneratorInitMethod extends Method {
- KeyPairGeneratorInitMethod() {
- this.getDeclaringType() instanceof KeyPairGenerator and
- this.hasName("initialize")
- }
-}
-
-/** Returns the key size in the EC algorithm string */
-bindingset[algorithm]
-private int getECKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-// ******* DATAFLOW ABOVE *************************************************************************
-// TODO:
-// todo #0: look into use of specs without keygens; should spec not be a sink in these cases?
-// todo #1: make representation of source that can be shared across the configs
-// todo #2: make representation of sink that can be shared across the configs
-// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
-// todo #4: refactor to be more like the Python (or C#) version? (or not possible because of lack of DataFlow::Node for void method in Java?)
+// ******* 3 DATAFLOW CONFIGS ABOVE *************************************************************************
// ******* SINGLE CONFIG ATTEMPT BELOW *************************************************************************
// /**
// * A key length data flow tracking configuration.
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 040f17abfc0..cb560aa1f34 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -16,9 +16,10 @@ import semmle.code.java.security.InsufficientKeySizeQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink
-where exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
-//or
-// exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
-// exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
-// exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+where
+ //exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
+ //or
+ exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index 6d0d83f25fe..869831242f9 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -11,12 +11,12 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
exists(DataFlow::PathNode source, DataFlow::PathNode sink |
- exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- |
+ //exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
//or
- // exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- // exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- //exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+ exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+ |
sink.getNode().getLocation() = location and
element = sink.getNode().toString() and
value = ""
From bfbb6db43612ca347170ac778ab994761a5ca3f6 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 12 Oct 2022 16:58:34 -0400
Subject: [PATCH 041/465] clean up code
---
.../java/security/InsufficientKeySize.qll | 82 ++++++++++++-------
.../CWE/CWE-326/InsufficientKeySize.ql | 6 +-
.../CWE-326/InsufficientKeySizeTest.ql | 6 +-
3 files changed, 59 insertions(+), 35 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 4aa4bbe7a44..8e45a2c2387 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -26,6 +26,7 @@ class SymmetricSource extends InsufficientKeySizeSource {
SymmetricSource() { getNodeIntValue(this) < 128 }
}
+// TODO: use `typeFlag` like with sinks below to include the size numbers in this predicate?
private int getNodeIntValue(DataFlow::Node node) {
result = node.asExpr().(IntegerLiteral).getIntValue()
}
@@ -43,19 +44,13 @@ private int getECKeySize(string algorithm) {
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
+// TODO: Consider if below 3 sinks should be private and if it's possible to only use InsufficientKeySizeSink in the configs
+// TODO: add QLDocs if keeping non-private
class AsymmetricNonECSink extends InsufficientKeySizeSink {
AsymmetricNonECSink() {
- exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- this.asExpr() = ma.getArgument(0)
- )
+ hasKeySizeInInitMethod(this, "asymmetric-non-ec")
or
- exists(ClassInstanceExpr genParamSpec |
- genParamSpec.getConstructedType() instanceof AsymmetricNonECSpec and
- this.asExpr() = genParamSpec.getArgument(0)
- )
+ hasKeySizeInSpec(this, "asymmetric-non-ec")
}
}
@@ -70,31 +65,60 @@ private class AsymmetricNonECSpec extends RefType {
class AsymmetricECSink extends InsufficientKeySizeSink {
AsymmetricECSink() {
- exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- DataFlow::localExprFlow(jpg, ma.getQualifier()) and
- this.asExpr() = ma.getArgument(0)
- )
+ hasKeySizeInInitMethod(this, "asymmetric-ec")
or
- exists(ClassInstanceExpr ecGenParamSpec |
- ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
- this.asExpr() = ecGenParamSpec.getArgument(0)
- )
+ hasKeySizeInSpec(this, "asymmetric-ec")
}
}
class SymmetricSink extends InsufficientKeySizeSink {
- SymmetricSink() {
- exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
- DataFlow::localExprFlow(jcg, ma.getQualifier()) and
- this.asExpr() = ma.getArgument(0)
- )
- }
+ SymmetricSink() { hasKeySizeInInitMethod(this, "symmetric") }
+}
+
+// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
+// TODO: can prbly re-work way using the typeFlag to be better and less repetitive
+private predicate hasKeySizeInInitMethod(DataFlow::Node node, string typeFlag) {
+ exists(MethodAccess ma, JavaxCryptoAlgoSpec jcaSpec |
+ (
+ ma.getMethod() instanceof KeyGeneratorInitMethod and typeFlag = "symmetric"
+ or
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and typeFlag.matches("asymmetric%")
+ ) and
+ (
+ jcaSpec instanceof JavaxCryptoKeyGenerator and typeFlag = "symmetric"
+ or
+ jcaSpec instanceof JavaSecurityKeyPairGenerator and typeFlag.matches("asymmetric%")
+ ) and
+ (
+ jcaSpec.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
+ typeFlag = "symmetric"
+ or
+ jcaSpec.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
+ typeFlag = "asymmetric-non-ec"
+ or
+ jcaSpec.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
+ typeFlag = "asymmetric-ec"
+ ) and
+ DataFlow::localExprFlow(jcaSpec, ma.getQualifier()) and
+ node.asExpr() = ma.getArgument(0)
+ )
+}
+
+// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
+// TODO: can prbly re-work way using the typeFlag to be better and less repetitive
+private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
+ exists(ClassInstanceExpr paramSpec |
+ (
+ paramSpec.getConstructedType() instanceof AsymmetricNonECSpec and
+ typeFlag = "asymmetric-non-ec"
+ or
+ paramSpec.getConstructedType() instanceof EcGenParameterSpec and
+ typeFlag = "asymmetric-ec"
+ ) and
+ node.asExpr() = paramSpec.getArgument(0)
+ )
}
// TODO:
// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
-// todo #5:
+// todo #4: use flowstate (or even just result predicate) to pass source int size to sink?
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index cb560aa1f34..f9d3d3baa0f 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -19,7 +19,7 @@ from DataFlow::PathNode source, DataFlow::PathNode sink
where
//exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
//or
- exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+ exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index 869831242f9..db925935d5c 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -13,9 +13,9 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
exists(DataFlow::PathNode source, DataFlow::PathNode sink |
//exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
//or
- exists(AsymmetricNonECKeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration config2 | config2.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration config3 | config3.hasFlowPath(source, sink))
+ exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+ exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+ exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
|
sink.getNode().getLocation() = location and
element = sink.getNode().toString() and
From bcb506b63795ba65d823d716a260802d3196f983 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 12 Oct 2022 17:04:51 -0400
Subject: [PATCH 042/465] add placeholder qldocs
---
.../lib/semmle/code/java/security/InsufficientKeySize.qll | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 8e45a2c2387..8f41252908d 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -11,10 +11,12 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node { }
// TODO: Consider if below 3 sources should be private and if it's possible to only use InsufficientKeySizeSource in the configs
// TODO: add QLDocs if keeping non-private
+/** A source for an insufficient key size (asymmetric-non-ec). */
class AsymmetricNonECSource extends InsufficientKeySizeSource {
AsymmetricNonECSource() { getNodeIntValue(this) < 2048 }
}
+/** A source for an insufficient key size (asymmetric-ec). */
class AsymmetricECSource extends InsufficientKeySizeSource {
AsymmetricECSource() {
getNodeIntValue(this) < 256 or
@@ -22,6 +24,7 @@ class AsymmetricECSource extends InsufficientKeySizeSource {
}
}
+/** A source for an insufficient key size (symmetric). */
class SymmetricSource extends InsufficientKeySizeSource {
SymmetricSource() { getNodeIntValue(this) < 128 }
}
@@ -46,6 +49,7 @@ private int getECKeySize(string algorithm) {
// TODO: Consider if below 3 sinks should be private and if it's possible to only use InsufficientKeySizeSink in the configs
// TODO: add QLDocs if keeping non-private
+/** A sink for an insufficient key size (asymmetric-non-ec). */
class AsymmetricNonECSink extends InsufficientKeySizeSink {
AsymmetricNonECSink() {
hasKeySizeInInitMethod(this, "asymmetric-non-ec")
@@ -63,6 +67,7 @@ private class AsymmetricNonECSpec extends RefType {
}
}
+/** A sink for an insufficient key size (asymmetric-ec). */
class AsymmetricECSink extends InsufficientKeySizeSink {
AsymmetricECSink() {
hasKeySizeInInitMethod(this, "asymmetric-ec")
@@ -71,6 +76,7 @@ class AsymmetricECSink extends InsufficientKeySizeSink {
}
}
+/** A sink for an insufficient key size (symmetric). */
class SymmetricSink extends InsufficientKeySizeSink {
SymmetricSink() { hasKeySizeInInitMethod(this, "symmetric") }
}
From e0f0d554cb11dce3a9522e9be926f879390276be Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 12 Oct 2022 22:18:07 -0400
Subject: [PATCH 043/465] condense code
---
.../code/java/security/InsufficientKeySize.qll | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 8f41252908d..53efe07b490 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -96,20 +96,22 @@ private predicate hasKeySizeInInitMethod(DataFlow::Node node, string typeFlag) {
jcaSpec instanceof JavaSecurityKeyPairGenerator and typeFlag.matches("asymmetric%")
) and
(
- jcaSpec.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
- typeFlag = "symmetric"
+ getAlgoName(jcaSpec) = "AES" and typeFlag = "symmetric"
or
- jcaSpec.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
- typeFlag = "asymmetric-non-ec"
+ getAlgoName(jcaSpec).matches(["RSA", "DSA", "DH"]) and typeFlag = "asymmetric-non-ec"
or
- jcaSpec.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
- typeFlag = "asymmetric-ec"
+ getAlgoName(jcaSpec).matches("EC%") and typeFlag = "asymmetric-ec"
) and
DataFlow::localExprFlow(jcaSpec, ma.getQualifier()) and
node.asExpr() = ma.getArgument(0)
)
}
+// TODO: this predicate is just a poc for more code condensing; redo this
+private string getAlgoName(JavaxCryptoAlgoSpec jca) {
+ result = jca.getAlgoSpec().(StringLiteral).getValue().toUpperCase()
+}
+
// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
// TODO: can prbly re-work way using the typeFlag to be better and less repetitive
private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
From 2daa3457d79906b53353ad8a5cc0c475e770f5bd Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 13 Oct 2022 17:57:56 -0400
Subject: [PATCH 044/465] combine three configs into one
---
.../java/security/InsufficientKeySize.qll | 58 ++++---
.../security/InsufficientKeySizeQuery.qll | 141 ++++--------------
.../CWE/CWE-326/InsufficientKeySize.ql | 11 +-
.../CWE-326/InsufficientKeySizeTest.ql | 10 +-
4 files changed, 79 insertions(+), 141 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 53efe07b490..dde014fe6de 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -3,30 +3,41 @@
private import semmle.code.java.security.Encryption
private import semmle.code.java.dataflow.DataFlow
+// TODO: only update key sizes (and key size strings in one place in the code)
/** A source for an insufficient key size. */
-abstract class InsufficientKeySizeSource extends DataFlow::Node { }
+abstract class InsufficientKeySizeSource extends DataFlow::Node {
+ /** Holds if this source has the specified `state`. */
+ predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
+}
/** A sink for an insufficient key size. */
-abstract class InsufficientKeySizeSink extends DataFlow::Node { }
-
-// TODO: Consider if below 3 sources should be private and if it's possible to only use InsufficientKeySizeSource in the configs
-// TODO: add QLDocs if keeping non-private
-/** A source for an insufficient key size (asymmetric-non-ec). */
-class AsymmetricNonECSource extends InsufficientKeySizeSource {
- AsymmetricNonECSource() { getNodeIntValue(this) < 2048 }
+abstract class InsufficientKeySizeSink extends DataFlow::Node {
+ /** Holds if this sink has the specified `state`. */
+ predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
}
-/** A source for an insufficient key size (asymmetric-ec). */
-class AsymmetricECSource extends InsufficientKeySizeSource {
+/** A source for an insufficient key size (asymmetric-non-ec: RSA, DSA, DH). */
+private class AsymmetricNonECSource extends InsufficientKeySizeSource {
+ AsymmetricNonECSource() { getNodeIntValue(this) < 2048 }
+
+ override predicate hasState(DataFlow::FlowState state) { state = "2048" }
+}
+
+/** A source for an insufficient key size (asymmetric-ec: EC%). */
+private class AsymmetricECSource extends InsufficientKeySizeSource {
AsymmetricECSource() {
getNodeIntValue(this) < 256 or
- getECKeySize(this.asExpr().(StringLiteral).getValue()) < 256 // need this for the cases when the key size is embedded in the curve name.
+ getECKeySize(this.asExpr().(StringLiteral).getValue()) < 256 // for cases when the key size is embedded in the curve name
}
+
+ override predicate hasState(DataFlow::FlowState state) { state = "256" }
}
-/** A source for an insufficient key size (symmetric). */
-class SymmetricSource extends InsufficientKeySizeSource {
+/** A source for an insufficient key size (symmetric: AES). */
+private class SymmetricSource extends InsufficientKeySizeSource {
SymmetricSource() { getNodeIntValue(this) < 128 }
+
+ override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
// TODO: use `typeFlag` like with sinks below to include the size numbers in this predicate?
@@ -34,6 +45,7 @@ private int getNodeIntValue(DataFlow::Node node) {
result = node.asExpr().(IntegerLiteral).getIntValue()
}
+// TODO: check if any other regex should be added based on some MRVA results.
/** Returns the key size in the EC algorithm string */
bindingset[algorithm]
private int getECKeySize(string algorithm) {
@@ -47,18 +59,17 @@ private int getECKeySize(string algorithm) {
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
-// TODO: Consider if below 3 sinks should be private and if it's possible to only use InsufficientKeySizeSink in the configs
-// TODO: add QLDocs if keeping non-private
/** A sink for an insufficient key size (asymmetric-non-ec). */
-class AsymmetricNonECSink extends InsufficientKeySizeSink {
+private class AsymmetricNonECSink extends InsufficientKeySizeSink {
AsymmetricNonECSink() {
hasKeySizeInInitMethod(this, "asymmetric-non-ec")
or
hasKeySizeInSpec(this, "asymmetric-non-ec")
}
+
+ override predicate hasState(DataFlow::FlowState state) { state = "2048" }
}
-// TODO: move to Encryption.qll? or keep here since specific to this query?
private class AsymmetricNonECSpec extends RefType {
AsymmetricNonECSpec() {
this instanceof RsaKeyGenParameterSpec or
@@ -68,17 +79,21 @@ private class AsymmetricNonECSpec extends RefType {
}
/** A sink for an insufficient key size (asymmetric-ec). */
-class AsymmetricECSink extends InsufficientKeySizeSink {
+private class AsymmetricECSink extends InsufficientKeySizeSink {
AsymmetricECSink() {
hasKeySizeInInitMethod(this, "asymmetric-ec")
or
hasKeySizeInSpec(this, "asymmetric-ec")
}
+
+ override predicate hasState(DataFlow::FlowState state) { state = "256" }
}
/** A sink for an insufficient key size (symmetric). */
-class SymmetricSink extends InsufficientKeySizeSink {
+private class SymmetricSink extends InsufficientKeySizeSink {
SymmetricSink() { hasKeySizeInInitMethod(this, "symmetric") }
+
+ override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
@@ -113,7 +128,7 @@ private string getAlgoName(JavaxCryptoAlgoSpec jca) {
}
// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
-// TODO: can prbly re-work way using the typeFlag to be better and less repetitive
+// TODO: can prbly re-work way using the typeFlag to be better and less repetitive...
private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
exists(ClassInstanceExpr paramSpec |
(
@@ -126,7 +141,8 @@ private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
node.asExpr() = paramSpec.getArgument(0)
)
}
+
+class SpecWithKeySize extends RefType { }
// TODO:
// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
-// todo #4: use flowstate (or even just result predicate) to pass source int size to sink?
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 51800aa8df9..08388c345d6 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -6,120 +6,43 @@ import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.InsufficientKeySize
/**
- * A data flow configuration for tracking non-elliptic curve asymmetric algorithms
+ * A data flow configuration for tracking non-elliptic curve asymmetric algorithm
* (RSA, DSA, and DH) key sizes.
*/
-class AsymmetricNonECKeyTrackingConfiguration extends DataFlow::Configuration {
- AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
+class KeySizeConfiguration extends DataFlow::Configuration {
+ KeySizeConfiguration() { this = "KeySizeConfiguration" }
- override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricNonECSource }
+ override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
+ source.(InsufficientKeySizeSource).hasState(state)
+ //source instanceof InsufficientKeySizeSource
+ }
- override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricNonECSink }
+ override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
+ sink.(InsufficientKeySizeSink).hasState(state)
+ //sink instanceof InsufficientKeySizeSink
+ }
}
-
-/**
- * A data flow configuration for tracking elliptic curve (EC) asymmetric
- * algorithm key sizes.
- */
-class AsymmetricECKeyTrackingConfiguration extends DataFlow::Configuration {
- AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricECSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricECSink }
-}
-
-/** A data flow configuration for tracking symmetric algorithm (AES) key sizes. */
-class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
- SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof SymmetricSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof SymmetricSink }
-}
-// ******* 3 DATAFLOW CONFIGS ABOVE *************************************************************************
-// ******* SINGLE CONFIG ATTEMPT BELOW *************************************************************************
// /**
-// * A key length data flow tracking configuration.
+// * A data flow configuration for tracking non-elliptic curve asymmetric algorithm
+// * (RSA, DSA, and DH) key sizes.
// */
-// class KeyTrackingConfiguration extends DataFlow::Configuration {
-// KeyTrackingConfiguration() { this = "KeyTrackingConfiguration" }
-// override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
-// //state instanceof DataFlow::FlowStateEmpty and
-// // SYMMETRIC
-// source.asExpr().(IntegerLiteral).getIntValue() < 128 and state = "128"
-// or
-// // ASYMMETRIC
-// source.asExpr().(IntegerLiteral).getIntValue() < 2048 and state = "2048"
-// or
-// source.asExpr().(IntegerLiteral).getIntValue() < 256 and state = "256"
-// or
-// getECKeySize(source.asExpr().(StringLiteral).getValue()) < 256 and state = "256" // need this for the cases when the key size is embedded in the curve name.
-// }
-// override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
-// // SYMMETRIC
-// exists(MethodAccess ma, JavaxCryptoKeyGenerator jcg |
-// ma.getMethod() instanceof KeyGeneratorInitMethod and
-// jcg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = "AES" and
-// DataFlow::localExprFlow(jcg, ma.getQualifier()) and
-// sink.asExpr() = ma.getArgument(0) and
-// state = "128"
-// )
-// or
-// // ASYMMETRIC
-// exists(MethodAccess ma, JavaSecurityKeyPairGenerator jpg |
-// ma.getMethod() instanceof KeyPairGeneratorInitMethod and
-// (
-// jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches(["RSA", "DSA", "DH"]) and
-// DataFlow::localExprFlow(jpg, ma.getQualifier()) and
-// sink.asExpr() = ma.getArgument(0) and
-// //ma.getArgument(0).(LocalSourceNode).flowsTo(sink) and
-// //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 2048 and
-// state = "2048"
-// )
-// or
-// jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase().matches("EC%") and
-// DataFlow::localExprFlow(jpg, ma.getQualifier()) and
-// sink.asExpr() = ma.getArgument(0) and
-// //ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() < 256 and
-// state = "256"
-// )
-// or
-// // TODO: combine below three for less duplicated code
-// exists(ClassInstanceExpr rsaKeyGenParamSpec |
-// rsaKeyGenParamSpec.getConstructedType() instanceof RsaKeyGenParameterSpec and
-// sink.asExpr() = rsaKeyGenParamSpec.getArgument(0) and
-// state = "2048"
-// )
-// or
-// exists(ClassInstanceExpr dsaGenParamSpec |
-// dsaGenParamSpec.getConstructedType() instanceof DsaGenParameterSpec and
-// sink.asExpr() = dsaGenParamSpec.getArgument(0) and
-// state = "2048"
-// )
-// or
-// exists(ClassInstanceExpr dhGenParamSpec |
-// dhGenParamSpec.getConstructedType() instanceof DhGenParameterSpec and
-// sink.asExpr() = dhGenParamSpec.getArgument(0) and
-// state = "2048"
-// )
-// or
-// exists(ClassInstanceExpr ecGenParamSpec |
-// ecGenParamSpec.getConstructedType() instanceof EcGenParameterSpec and
-// sink.asExpr() = ecGenParamSpec.getArgument(0) and
-// state = "256"
-// )
-// }
-// // ! FlowStates seem to work without even including a step like the below... hmmm
-// override predicate isAdditionalFlowStep(
-// DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
-// DataFlow::FlowState state2
-// ) {
-// exists(IntegerLiteral intLiteral |
-// state1 = "" and
-// state2 = intLiteral.toString() and
-// node1.asExpr() = intLiteral and
-// node2.asExpr() = intLiteral
-// )
-// }
+// class AsymmetricNonECKeyTrackingConfiguration extends DataFlow::Configuration {
+// AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
+// override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricNonECSource }
+// override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricNonECSink }
+// }
+// /**
+// * A data flow configuration for tracking elliptic curve (EC) asymmetric
+// * algorithm key sizes.
+// */
+// class AsymmetricECKeyTrackingConfiguration extends DataFlow::Configuration {
+// AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
+// override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricECSource }
+// override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricECSink }
+// }
+// /** A data flow configuration for tracking symmetric algorithm (AES) key sizes. */
+// class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
+// SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
+// override predicate isSource(DataFlow::Node source) { source instanceof SymmetricSource }
+// override predicate isSink(DataFlow::Node sink) { sink instanceof SymmetricSink }
// }
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index f9d3d3baa0f..8f0f54c7d9b 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -16,10 +16,9 @@ import semmle.code.java.security.InsufficientKeySizeQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink
-where
- //exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- //or
- exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
+where exists(KeySizeConfiguration config1 | config1.hasFlowPath(source, sink))
+//or
+// exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+// exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+// exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index db925935d5c..5dc7ecdf467 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -11,12 +11,12 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
exists(DataFlow::PathNode source, DataFlow::PathNode sink |
- //exists(KeyTrackingConfiguration config1 | config1.hasFlowPath(source, sink))
- //or
- exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
- exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
- exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
+ exists(KeySizeConfiguration config1 | config1.hasFlowPath(source, sink))
|
+ //or
+ // exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+ // exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
+ // exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
sink.getNode().getLocation() = location and
element = sink.getNode().toString() and
value = ""
From c61f23baae0f7fb427334ce7617407fb3523a31e Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 13 Oct 2022 23:24:06 -0400
Subject: [PATCH 045/465] experiment with more code condensing
---
.../java/security/InsufficientKeySize.qll | 26 ++++++++++++-------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index dde014fe6de..778bc111dd0 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -3,7 +3,7 @@
private import semmle.code.java.security.Encryption
private import semmle.code.java.dataflow.DataFlow
-// TODO: only update key sizes (and key size strings in one place in the code)
+// TODO: only update key sizes (and key size strings) in one place in the code
/** A source for an insufficient key size. */
abstract class InsufficientKeySizeSource extends DataFlow::Node {
/** Holds if this source has the specified `state`. */
@@ -64,7 +64,7 @@ private class AsymmetricNonECSink extends InsufficientKeySizeSink {
AsymmetricNonECSink() {
hasKeySizeInInitMethod(this, "asymmetric-non-ec")
or
- hasKeySizeInSpec(this, "asymmetric-non-ec")
+ hasKeySizeInSpec(this)
}
override predicate hasState(DataFlow::FlowState state) { state = "2048" }
@@ -83,7 +83,7 @@ private class AsymmetricECSink extends InsufficientKeySizeSink {
AsymmetricECSink() {
hasKeySizeInInitMethod(this, "asymmetric-ec")
or
- hasKeySizeInSpec(this, "asymmetric-ec")
+ hasKeySizeInSpec(this)
}
override predicate hasState(DataFlow::FlowState state) { state = "256" }
@@ -129,20 +129,28 @@ private string getAlgoName(JavaxCryptoAlgoSpec jca) {
// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
// TODO: can prbly re-work way using the typeFlag to be better and less repetitive...
-private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
+private predicate hasKeySizeInSpec(DataFlow::Node node) {
exists(ClassInstanceExpr paramSpec |
(
- paramSpec.getConstructedType() instanceof AsymmetricNonECSpec and
- typeFlag = "asymmetric-non-ec"
+ paramSpec.getConstructedType() instanceof AsymmetricNonECSpec //and
or
- paramSpec.getConstructedType() instanceof EcGenParameterSpec and
- typeFlag = "asymmetric-ec"
+ //typeFlag = "asymmetric-non-ec"
+ paramSpec.getConstructedType() instanceof EcGenParameterSpec //and
+ //typeFlag = "asymmetric-ec"
) and
node.asExpr() = paramSpec.getArgument(0)
)
}
-class SpecWithKeySize extends RefType { }
+// ! use below instead of/in above??
+class Spec extends ClassInstanceExpr {
+ Spec() {
+ this.getConstructedType() instanceof AsymmetricNonECSpec or
+ this.getConstructedType() instanceof EcGenParameterSpec
+ }
+
+ Argument getKeySizeArg() { result = this.getArgument(0) }
+}
// TODO:
// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
From 6eb58d832c32d240a4a3ad811b16fd999c571f21 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 14 Oct 2022 00:47:57 -0400
Subject: [PATCH 046/465] remove dependence on typeFlag
---
.../java/security/InsufficientKeySize.qll | 180 +++++++++++-------
1 file changed, 114 insertions(+), 66 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 778bc111dd0..3fccbe412b6 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -8,6 +8,7 @@ private import semmle.code.java.dataflow.DataFlow
abstract class InsufficientKeySizeSource extends DataFlow::Node {
/** Holds if this source has the specified `state`. */
predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
+ //int getIntValue() { result = this.asExpr().(IntegerLiteral).getIntValue() }
}
/** A sink for an insufficient key size. */
@@ -17,17 +18,17 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
}
/** A source for an insufficient key size (asymmetric-non-ec: RSA, DSA, DH). */
-private class AsymmetricNonECSource extends InsufficientKeySizeSource {
- AsymmetricNonECSource() { getNodeIntValue(this) < 2048 }
+private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
+ AsymmetricNonEcSource() { getNodeIntValue(this) < 2048 }
override predicate hasState(DataFlow::FlowState state) { state = "2048" }
}
/** A source for an insufficient key size (asymmetric-ec: EC%). */
-private class AsymmetricECSource extends InsufficientKeySizeSource {
- AsymmetricECSource() {
+private class AsymmetricEcSource extends InsufficientKeySizeSource {
+ AsymmetricEcSource() {
getNodeIntValue(this) < 256 or
- getECKeySize(this.asExpr().(StringLiteral).getValue()) < 256 // for cases when the key size is embedded in the curve name
+ getEcKeySize(this.asExpr().(StringLiteral).getValue()) < 256 // for cases when the key size is embedded in the curve name
}
override predicate hasState(DataFlow::FlowState state) { state = "256" }
@@ -40,7 +41,6 @@ private class SymmetricSource extends InsufficientKeySizeSource {
override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
-// TODO: use `typeFlag` like with sinks below to include the size numbers in this predicate?
private int getNodeIntValue(DataFlow::Node node) {
result = node.asExpr().(IntegerLiteral).getIntValue()
}
@@ -48,7 +48,7 @@ private int getNodeIntValue(DataFlow::Node node) {
// TODO: check if any other regex should be added based on some MRVA results.
/** Returns the key size in the EC algorithm string */
bindingset[algorithm]
-private int getECKeySize(string algorithm) {
+private int getEcKeySize(string algorithm) {
algorithm.matches("sec%") and // specification such as "secp256r1"
result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
or
@@ -60,30 +60,36 @@ private int getECKeySize(string algorithm) {
}
/** A sink for an insufficient key size (asymmetric-non-ec). */
-private class AsymmetricNonECSink extends InsufficientKeySizeSink {
- AsymmetricNonECSink() {
- hasKeySizeInInitMethod(this, "asymmetric-non-ec")
+private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
+ AsymmetricNonEcSink() {
+ // hasKeySizeInInitMethod(this, "asymmetric-non-ec")
+ // or
+ //hasKeySizeInSpec(this, "asymmetric-non-ec")
+ exists(AsymmInitMethodAccess ma, AsymmKeyGen kg |
+ kg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
+ DataFlow::localExprFlow(kg, ma.getQualifier()) and
+ this.asExpr() = ma.getKeySizeArg()
+ )
or
- hasKeySizeInSpec(this)
+ exists(AsymmetricNonEcSpec s | this.asExpr() = s.getKeySizeArg())
}
override predicate hasState(DataFlow::FlowState state) { state = "2048" }
}
-private class AsymmetricNonECSpec extends RefType {
- AsymmetricNonECSpec() {
- this instanceof RsaKeyGenParameterSpec or
- this instanceof DsaGenParameterSpec or
- this instanceof DhGenParameterSpec
- }
-}
-
/** A sink for an insufficient key size (asymmetric-ec). */
-private class AsymmetricECSink extends InsufficientKeySizeSink {
- AsymmetricECSink() {
- hasKeySizeInInitMethod(this, "asymmetric-ec")
+private class AsymmetricEcSink extends InsufficientKeySizeSink {
+ AsymmetricEcSink() {
+ // hasKeySizeInInitMethod(this, "asymmetric-ec")
+ // or
+ //hasKeySizeInSpec(this, "asymmetric-ec")
+ exists(AsymmInitMethodAccess ma, AsymmKeyGen kg |
+ kg.getAlgoName().matches("EC%") and
+ DataFlow::localExprFlow(kg, ma.getQualifier()) and
+ this.asExpr() = ma.getKeySizeArg()
+ )
or
- hasKeySizeInSpec(this)
+ exists(AsymmetricEcSpec s | this.asExpr() = s.getKeySizeArg())
}
override predicate hasState(DataFlow::FlowState state) { state = "256" }
@@ -91,65 +97,107 @@ private class AsymmetricECSink extends InsufficientKeySizeSink {
/** A sink for an insufficient key size (symmetric). */
private class SymmetricSink extends InsufficientKeySizeSink {
- SymmetricSink() { hasKeySizeInInitMethod(this, "symmetric") }
+ SymmetricSink() {
+ //hasKeySizeInInitMethod(this, "symmetric")
+ exists(SymmInitMethodAccess ma, SymmKeyGen kg |
+ kg.getAlgoName() = "AES" and
+ DataFlow::localExprFlow(kg, ma.getQualifier()) and
+ this.asExpr() = ma.getKeySizeArg()
+ )
+ }
override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
// TODO: can prbly re-work way using the typeFlag to be better and less repetitive
-private predicate hasKeySizeInInitMethod(DataFlow::Node node, string typeFlag) {
- exists(MethodAccess ma, JavaxCryptoAlgoSpec jcaSpec |
- (
- ma.getMethod() instanceof KeyGeneratorInitMethod and typeFlag = "symmetric"
- or
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and typeFlag.matches("asymmetric%")
- ) and
- (
- jcaSpec instanceof JavaxCryptoKeyGenerator and typeFlag = "symmetric"
- or
- jcaSpec instanceof JavaSecurityKeyPairGenerator and typeFlag.matches("asymmetric%")
- ) and
- (
- getAlgoName(jcaSpec) = "AES" and typeFlag = "symmetric"
- or
- getAlgoName(jcaSpec).matches(["RSA", "DSA", "DH"]) and typeFlag = "asymmetric-non-ec"
- or
- getAlgoName(jcaSpec).matches("EC%") and typeFlag = "asymmetric-ec"
- ) and
- DataFlow::localExprFlow(jcaSpec, ma.getQualifier()) and
- node.asExpr() = ma.getArgument(0)
- )
+// private predicate hasKeySizeInInitMethod(DataFlow::Node node, string typeFlag) {
+// exists(MethodAccess ma, JavaxCryptoAlgoSpec jcaSpec |
+// (
+// ma.getMethod() instanceof KeyGeneratorInitMethod and typeFlag = "symmetric"
+// or
+// ma.getMethod() instanceof KeyPairGeneratorInitMethod and typeFlag.matches("asymmetric%")
+// ) and
+// (
+// jcaSpec instanceof JavaxCryptoKeyGenerator and typeFlag = "symmetric"
+// or
+// jcaSpec instanceof JavaSecurityKeyPairGenerator and typeFlag.matches("asymmetric%")
+// ) and
+// (
+// getAlgoName(jcaSpec) = "AES" and typeFlag = "symmetric"
+// or
+// getAlgoName(jcaSpec).matches(["RSA", "DSA", "DH"]) and typeFlag = "asymmetric-non-ec"
+// or
+// getAlgoName(jcaSpec).matches("EC%") and typeFlag = "asymmetric-ec"
+// ) and
+// DataFlow::localExprFlow(jcaSpec, ma.getQualifier()) and
+// node.asExpr() = ma.getArgument(0)
+// )
+// }
+// // TODO: this predicate is just a poc for more code condensing; redo this
+// private string getAlgoName(JavaxCryptoAlgoSpec jca) {
+// result = jca.getAlgoSpec().(StringLiteral).getValue().toUpperCase()
+// }
+abstract class InitMethodAccess extends MethodAccess {
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
-// TODO: this predicate is just a poc for more code condensing; redo this
-private string getAlgoName(JavaxCryptoAlgoSpec jca) {
- result = jca.getAlgoSpec().(StringLiteral).getValue().toUpperCase()
+class AsymmInitMethodAccess extends InitMethodAccess {
+ AsymmInitMethodAccess() { this.getMethod() instanceof KeyPairGeneratorInitMethod }
+}
+
+class SymmInitMethodAccess extends InitMethodAccess {
+ SymmInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
+}
+
+abstract class KeyGen extends JavaxCryptoAlgoSpec {
+ string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
+}
+
+class AsymmKeyGen extends KeyGen {
+ AsymmKeyGen() { this instanceof JavaSecurityKeyPairGenerator }
+
+ // ! this override is repetitive...
+ override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
+}
+
+class SymmKeyGen extends KeyGen {
+ SymmKeyGen() { this instanceof JavaxCryptoKeyGenerator }
+
+ // ! this override is repetitive...
+ override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
// TODO: can prbly re-work way using the typeFlag to be better and less repetitive...
-private predicate hasKeySizeInSpec(DataFlow::Node node) {
- exists(ClassInstanceExpr paramSpec |
- (
- paramSpec.getConstructedType() instanceof AsymmetricNonECSpec //and
- or
- //typeFlag = "asymmetric-non-ec"
- paramSpec.getConstructedType() instanceof EcGenParameterSpec //and
- //typeFlag = "asymmetric-ec"
- ) and
- node.asExpr() = paramSpec.getArgument(0)
- )
+// private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
+// exists(ClassInstanceExpr paramSpec |
+// (
+// paramSpec.getConstructedType() instanceof AsymmetricNonEcSpec and
+// typeFlag = "asymmetric-non-ec"
+// or
+// paramSpec.getConstructedType() instanceof EcGenParameterSpec and
+// typeFlag = "asymmetric-ec"
+// ) and
+// node.asExpr() = paramSpec.getArgument(0)
+// )
+// }
+// ! use below instead of/in above?? (actually I don't think I need any of this, can just use AsymmetricNonEcSpec and EcGenParameterSpec directly???)
+// Algo spec
+abstract class AsymmetricAlgoSpec extends ClassInstanceExpr {
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
-// ! use below instead of/in above??
-class Spec extends ClassInstanceExpr {
- Spec() {
- this.getConstructedType() instanceof AsymmetricNonECSpec or
- this.getConstructedType() instanceof EcGenParameterSpec
+class AsymmetricNonEcSpec extends AsymmetricAlgoSpec {
+ AsymmetricNonEcSpec() {
+ this.getConstructedType() instanceof RsaKeyGenParameterSpec or
+ this.getConstructedType() instanceof DsaGenParameterSpec or
+ this.getConstructedType() instanceof DhGenParameterSpec
}
+}
- Argument getKeySizeArg() { result = this.getArgument(0) }
+class AsymmetricEcSpec extends AsymmetricAlgoSpec {
+ AsymmetricEcSpec() { this.getConstructedType() instanceof EcGenParameterSpec }
}
// TODO:
// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
From 47030df8ac33c41b6bc235d72aeb440c38a35cbf Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 14 Oct 2022 09:26:44 -0400
Subject: [PATCH 047/465] remove commented-out 3 configs
---
.../security/InsufficientKeySizeQuery.qll | 26 -------------------
1 file changed, 26 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 08388c345d6..6036071111e 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -14,35 +14,9 @@ class KeySizeConfiguration extends DataFlow::Configuration {
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
source.(InsufficientKeySizeSource).hasState(state)
- //source instanceof InsufficientKeySizeSource
}
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
sink.(InsufficientKeySizeSink).hasState(state)
- //sink instanceof InsufficientKeySizeSink
}
}
-// /**
-// * A data flow configuration for tracking non-elliptic curve asymmetric algorithm
-// * (RSA, DSA, and DH) key sizes.
-// */
-// class AsymmetricNonECKeyTrackingConfiguration extends DataFlow::Configuration {
-// AsymmetricNonECKeyTrackingConfiguration() { this = "AsymmetricNonECKeyTrackingConfiguration" }
-// override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricNonECSource }
-// override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricNonECSink }
-// }
-// /**
-// * A data flow configuration for tracking elliptic curve (EC) asymmetric
-// * algorithm key sizes.
-// */
-// class AsymmetricECKeyTrackingConfiguration extends DataFlow::Configuration {
-// AsymmetricECKeyTrackingConfiguration() { this = "AsymmetricECKeyTrackingConfiguration" }
-// override predicate isSource(DataFlow::Node source) { source instanceof AsymmetricECSource }
-// override predicate isSink(DataFlow::Node sink) { sink instanceof AsymmetricECSink }
-// }
-// /** A data flow configuration for tracking symmetric algorithm (AES) key sizes. */
-// class SymmetricKeyTrackingConfiguration extends DataFlow::Configuration {
-// SymmetricKeyTrackingConfiguration() { this = "SymmetricKeyTrackingConfiguration" }
-// override predicate isSource(DataFlow::Node source) { source instanceof SymmetricSource }
-// override predicate isSink(DataFlow::Node sink) { sink instanceof SymmetricSink }
-// }
From 0334470f33a2623773cc6118048791a52325a6af Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 14 Oct 2022 10:55:30 -0400
Subject: [PATCH 048/465] remove commented out predicates that relied on
typeFlag
---
.../java/security/InsufficientKeySize.qll | 47 +------------------
.../security/InsufficientKeySizeQuery.qll | 5 +-
2 files changed, 3 insertions(+), 49 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 3fccbe412b6..b34a9e0da21 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -3,12 +3,10 @@
private import semmle.code.java.security.Encryption
private import semmle.code.java.dataflow.DataFlow
-// TODO: only update key sizes (and key size strings) in one place in the code
/** A source for an insufficient key size. */
abstract class InsufficientKeySizeSource extends DataFlow::Node {
/** Holds if this source has the specified `state`. */
predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
- //int getIntValue() { result = this.asExpr().(IntegerLiteral).getIntValue() }
}
/** A sink for an insufficient key size. */
@@ -109,35 +107,6 @@ private class SymmetricSink extends InsufficientKeySizeSink {
override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
-// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
-// TODO: can prbly re-work way using the typeFlag to be better and less repetitive
-// private predicate hasKeySizeInInitMethod(DataFlow::Node node, string typeFlag) {
-// exists(MethodAccess ma, JavaxCryptoAlgoSpec jcaSpec |
-// (
-// ma.getMethod() instanceof KeyGeneratorInitMethod and typeFlag = "symmetric"
-// or
-// ma.getMethod() instanceof KeyPairGeneratorInitMethod and typeFlag.matches("asymmetric%")
-// ) and
-// (
-// jcaSpec instanceof JavaxCryptoKeyGenerator and typeFlag = "symmetric"
-// or
-// jcaSpec instanceof JavaSecurityKeyPairGenerator and typeFlag.matches("asymmetric%")
-// ) and
-// (
-// getAlgoName(jcaSpec) = "AES" and typeFlag = "symmetric"
-// or
-// getAlgoName(jcaSpec).matches(["RSA", "DSA", "DH"]) and typeFlag = "asymmetric-non-ec"
-// or
-// getAlgoName(jcaSpec).matches("EC%") and typeFlag = "asymmetric-ec"
-// ) and
-// DataFlow::localExprFlow(jcaSpec, ma.getQualifier()) and
-// node.asExpr() = ma.getArgument(0)
-// )
-// }
-// // TODO: this predicate is just a poc for more code condensing; redo this
-// private string getAlgoName(JavaxCryptoAlgoSpec jca) {
-// result = jca.getAlgoSpec().(StringLiteral).getValue().toUpperCase()
-// }
abstract class InitMethodAccess extends MethodAccess {
Argument getKeySizeArg() { result = this.getArgument(0) }
}
@@ -168,20 +137,6 @@ class SymmKeyGen extends KeyGen {
override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
-// TODO: rethink the predicate name; also think about whether this could/should be a class instead; or a predicate within the sink class so can do sink.predicate()...
-// TODO: can prbly re-work way using the typeFlag to be better and less repetitive...
-// private predicate hasKeySizeInSpec(DataFlow::Node node, string typeFlag) {
-// exists(ClassInstanceExpr paramSpec |
-// (
-// paramSpec.getConstructedType() instanceof AsymmetricNonEcSpec and
-// typeFlag = "asymmetric-non-ec"
-// or
-// paramSpec.getConstructedType() instanceof EcGenParameterSpec and
-// typeFlag = "asymmetric-ec"
-// ) and
-// node.asExpr() = paramSpec.getArgument(0)
-// )
-// }
// ! use below instead of/in above?? (actually I don't think I need any of this, can just use AsymmetricNonEcSpec and EcGenParameterSpec directly???)
// Algo spec
abstract class AsymmetricAlgoSpec extends ClassInstanceExpr {
@@ -202,3 +157,5 @@ class AsymmetricEcSpec extends AsymmetricAlgoSpec {
// TODO:
// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
+// todo: add barrier guard for !=0 conditional case
+// todo: only update key sizes (and key size strings) in one place in the code
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index 6036071111e..eb8e5441747 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -5,10 +5,7 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.InsufficientKeySize
-/**
- * A data flow configuration for tracking non-elliptic curve asymmetric algorithm
- * (RSA, DSA, and DH) key sizes.
- */
+/** A data flow configuration for tracking key sizes used in cryptographic algorithms. */
class KeySizeConfiguration extends DataFlow::Configuration {
KeySizeConfiguration() { this = "KeySizeConfiguration" }
From da218fdbf1adf1a96ff43c33bdd13a9831fdbd50 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 14 Oct 2022 13:03:34 -0400
Subject: [PATCH 049/465] clean up code
---
.../semmle/code/java/security/Encryption.qll | 14 ++-
.../java/security/InsufficientKeySize.qll | 85 ++++++++++---------
.../security/InsufficientKeySizeQuery.qll | 2 -
.../CWE/CWE-326/InsufficientKeySize.ql | 9 +-
4 files changed, 51 insertions(+), 59 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index a1535a438b5..adb2dc74691 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -83,6 +83,11 @@ class KeyGenerator extends RefType {
KeyGenerator() { this.hasQualifiedName("javax.crypto", "KeyGenerator") }
}
+/** The Java class `java.security.KeyPairGenerator`. */
+class KeyPairGenerator extends RefType {
+ KeyPairGenerator() { this.hasQualifiedName("java.security", "KeyPairGenerator") }
+}
+
/** The `init` method declared in `javax.crypto.KeyGenerator`. */
class KeyGeneratorInitMethod extends Method {
KeyGeneratorInitMethod() {
@@ -91,11 +96,6 @@ class KeyGeneratorInitMethod extends Method {
}
}
-/** The Java class `java.security.KeyPairGenerator`. */
-class KeyPairGenerator extends RefType {
- KeyPairGenerator() { this.hasQualifiedName("java.security", "KeyPairGenerator") }
-}
-
/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
class KeyPairGeneratorInitMethod extends Method {
KeyPairGeneratorInitMethod() {
@@ -323,7 +323,6 @@ class JavaxCryptoSecretKey extends JavaxCryptoAlgoSpec {
}
}
-// TODO: consider extending JavaxCryptoAlgoSpec as above does; will need to override getAlgoSpec() method
/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
class DhGenParameterSpec extends RefType {
DhGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
@@ -389,19 +388,16 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
-// TODO: consider extending JavaSecurityAlgoSpec as above does; will need to override getAlgoSpec() method
/** The Java class `java.security.spec.ECGenParameterSpec`. */
class EcGenParameterSpec extends RefType {
EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
}
-// TODO: consider extending JavaSecurityAlgoSpec as above does; will need to override getAlgoSpec() method
/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
class RsaKeyGenParameterSpec extends RefType {
RsaKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
}
-// TODO: consider extending JavaSecurityAlgoSpec as above does; will need to override getAlgoSpec() method
/** The Java class `java.security.spec.DSAGenParameterSpec`. */
class DsaGenParameterSpec extends RefType {
DsaGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index b34a9e0da21..16f960f1b6d 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -15,36 +15,40 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
}
-/** A source for an insufficient key size (asymmetric-non-ec: RSA, DSA, DH). */
+// *********************************** SOURCES ***********************************
+/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
AsymmetricNonEcSource() { getNodeIntValue(this) < 2048 }
override predicate hasState(DataFlow::FlowState state) { state = "2048" }
}
-/** A source for an insufficient key size (asymmetric-ec: EC%). */
+/** A source for an insufficient key size used in elliptic curve (EC) algorithms. */
private class AsymmetricEcSource extends InsufficientKeySizeSource {
AsymmetricEcSource() {
- getNodeIntValue(this) < 256 or
- getEcKeySize(this.asExpr().(StringLiteral).getValue()) < 256 // for cases when the key size is embedded in the curve name
+ getNodeIntValue(this) < 256
+ or
+ // the below is needed for cases when the key size is embedded in the curve name
+ getEcKeySize(this.asExpr().(StringLiteral).getValue()) < 256
}
override predicate hasState(DataFlow::FlowState state) { state = "256" }
}
-/** A source for an insufficient key size (symmetric: AES). */
+/** A source for an insufficient key size used in AES algorithms. */
private class SymmetricSource extends InsufficientKeySizeSource {
SymmetricSource() { getNodeIntValue(this) < 128 }
override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
+// ************************** SOURCES HELPER PREDICATES **************************
+/** Returns the integer value of a given Node. */
private int getNodeIntValue(DataFlow::Node node) {
result = node.asExpr().(IntegerLiteral).getIntValue()
}
-// TODO: check if any other regex should be added based on some MRVA results.
-/** Returns the key size in the EC algorithm string */
+/** Returns the key size from an EC algorithm curve name string */
bindingset[algorithm]
private int getEcKeySize(string algorithm) {
algorithm.matches("sec%") and // specification such as "secp256r1"
@@ -57,31 +61,26 @@ private int getEcKeySize(string algorithm) {
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
-/** A sink for an insufficient key size (asymmetric-non-ec). */
+// ************************************ SINKS ************************************
+/** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
AsymmetricNonEcSink() {
- // hasKeySizeInInitMethod(this, "asymmetric-non-ec")
- // or
- //hasKeySizeInSpec(this, "asymmetric-non-ec")
- exists(AsymmInitMethodAccess ma, AsymmKeyGen kg |
+ exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
kg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
DataFlow::localExprFlow(kg, ma.getQualifier()) and
this.asExpr() = ma.getKeySizeArg()
)
or
- exists(AsymmetricNonEcSpec s | this.asExpr() = s.getKeySizeArg())
+ exists(AsymmetricNonEcSpec spec | this.asExpr() = spec.getKeySizeArg())
}
override predicate hasState(DataFlow::FlowState state) { state = "2048" }
}
-/** A sink for an insufficient key size (asymmetric-ec). */
+/** A sink for an insufficient key size used in elliptic curve (EC) algorithms. */
private class AsymmetricEcSink extends InsufficientKeySizeSink {
AsymmetricEcSink() {
- // hasKeySizeInInitMethod(this, "asymmetric-ec")
- // or
- //hasKeySizeInSpec(this, "asymmetric-ec")
- exists(AsymmInitMethodAccess ma, AsymmKeyGen kg |
+ exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
kg.getAlgoName().matches("EC%") and
DataFlow::localExprFlow(kg, ma.getQualifier()) and
this.asExpr() = ma.getKeySizeArg()
@@ -93,11 +92,11 @@ private class AsymmetricEcSink extends InsufficientKeySizeSink {
override predicate hasState(DataFlow::FlowState state) { state = "256" }
}
-/** A sink for an insufficient key size (symmetric). */
+/** A sink for an insufficient key size used in AES algorithms. */
private class SymmetricSink extends InsufficientKeySizeSink {
SymmetricSink() {
//hasKeySizeInInitMethod(this, "symmetric")
- exists(SymmInitMethodAccess ma, SymmKeyGen kg |
+ exists(SymmetricInitMethodAccess ma, SymmetricKeyGenerator kg |
kg.getAlgoName() = "AES" and
DataFlow::localExprFlow(kg, ma.getQualifier()) and
this.asExpr() = ma.getKeySizeArg()
@@ -107,43 +106,49 @@ private class SymmetricSink extends InsufficientKeySizeSink {
override predicate hasState(DataFlow::FlowState state) { state = "128" }
}
-abstract class InitMethodAccess extends MethodAccess {
+// ********************** SINKS HELPER CLASSES & PREDICATES **********************
+/** A call to a method that initializes a key generator. */
+abstract class KeyGenInitMethodAccess extends MethodAccess {
+ /** Gets the `keysize` argument of this call. */
Argument getKeySizeArg() { result = this.getArgument(0) }
}
-class AsymmInitMethodAccess extends InitMethodAccess {
- AsymmInitMethodAccess() { this.getMethod() instanceof KeyPairGeneratorInitMethod }
+/** A call to the `initialize` method declared in `java.security.KeyPairGenerator`. */
+private class AsymmetricInitMethodAccess extends KeyGenInitMethodAccess {
+ AsymmetricInitMethodAccess() { this.getMethod() instanceof KeyPairGeneratorInitMethod }
}
-class SymmInitMethodAccess extends InitMethodAccess {
- SymmInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
+/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
+private class SymmetricInitMethodAccess extends KeyGenInitMethodAccess {
+ SymmetricInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
}
-abstract class KeyGen extends JavaxCryptoAlgoSpec {
+/** An instance of a key generator. */
+abstract class KeyGeneratorObject extends JavaxCryptoAlgoSpec {
string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
}
-class AsymmKeyGen extends KeyGen {
- AsymmKeyGen() { this instanceof JavaSecurityKeyPairGenerator }
+/** An instance of a `java.security.KeyPairGenerator`. */
+private class AsymmetricKeyGenerator extends KeyGeneratorObject {
+ AsymmetricKeyGenerator() { this instanceof JavaSecurityKeyPairGenerator }
- // ! this override is repetitive...
override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
-class SymmKeyGen extends KeyGen {
- SymmKeyGen() { this instanceof JavaxCryptoKeyGenerator }
+/** An instance of a `javax.crypto.KeyGenerator`. */
+private class SymmetricKeyGenerator extends KeyGeneratorObject {
+ SymmetricKeyGenerator() { this instanceof JavaxCryptoKeyGenerator }
- // ! this override is repetitive...
override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
-// ! use below instead of/in above?? (actually I don't think I need any of this, can just use AsymmetricNonEcSpec and EcGenParameterSpec directly???)
-// Algo spec
-abstract class AsymmetricAlgoSpec extends ClassInstanceExpr {
+/** An instance of an algorithm specification. */
+abstract class AlgoSpec extends ClassInstanceExpr {
Argument getKeySizeArg() { result = this.getArgument(0) }
}
-class AsymmetricNonEcSpec extends AsymmetricAlgoSpec {
+/** An instance of an RSA, DSA, or DH algorithm specification. */
+private class AsymmetricNonEcSpec extends AlgoSpec {
AsymmetricNonEcSpec() {
this.getConstructedType() instanceof RsaKeyGenParameterSpec or
this.getConstructedType() instanceof DsaGenParameterSpec or
@@ -151,11 +156,7 @@ class AsymmetricNonEcSpec extends AsymmetricAlgoSpec {
}
}
-class AsymmetricEcSpec extends AsymmetricAlgoSpec {
+/** An instance of an elliptic curve (EC) algorithm specification. */
+private class AsymmetricEcSpec extends AlgoSpec {
AsymmetricEcSpec() { this.getConstructedType() instanceof EcGenParameterSpec }
}
-// TODO:
-// todo #0: look into use of specs without keygen objects; should spec not be a sink in these cases?
-// todo #3: make list of algo names more easily reusable (either as constant-type variable at top of file, or model as own class to share, etc.)
-// todo: add barrier guard for !=0 conditional case
-// todo: only update key sizes (and key size strings) in one place in the code
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
index eb8e5441747..eb416d9647b 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySizeQuery.qll
@@ -1,8 +1,6 @@
/** Provides data flow configurations to be used in queries related to insufficient key sizes. */
-import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.DataFlow
-import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.InsufficientKeySize
/** A data flow configuration for tracking key sizes used in cryptographic algorithms. */
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 8f0f54c7d9b..b82104db79a 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -16,9 +16,6 @@ import semmle.code.java.security.InsufficientKeySizeQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink
-where exists(KeySizeConfiguration config1 | config1.hasFlowPath(source, sink))
-//or
-// exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
-// exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
-// exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
-select sink.getNode(), source, sink, "This $@ is too small.", source.getNode(), "key size"
+where exists(KeySizeConfiguration cfg | cfg.hasFlowPath(source, sink))
+select sink.getNode(), source, sink, "This $@ is less than the recommended key size.",
+ source.getNode(), "key size"
From 2714c7fdcf327cb26c5cae65a35431e00bb4f3bf Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 14 Oct 2022 16:45:13 -0400
Subject: [PATCH 050/465] update tests
---
.../CWE-326/InsufficientKeySizeTest.java | 263 +++++++-----------
.../CWE-326/InsufficientKeySizeTest.ql | 7 +-
2 files changed, 109 insertions(+), 161 deletions(-)
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index e6a61d7bb4c..3e6a1649ceb 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -10,244 +10,197 @@ import javax.crypto.spec.DHGenParameterSpec;
public class InsufficientKeySizeTest {
public void keySizeTesting() throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // Test basic key generation for all algos
-
- // AES (Symmetric)
+ /* AES (Symmetric): minimum recommended key size is 128 */
{
- // BAD: Key size is less than 128
+ /* Test with keysize as int */
KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
keyGen1.init(64); // $ hasInsufficientKeySize
- // GOOD: Key size is no less than 128
KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
- keyGen2.init(128); // Safe
+ keyGen2.init(128); // Safe: Key size is no less than 128
+
+ /* Test with local variable as keysize */
+ final int size1 = 64; // compile-time constant
+ int size2 = 64; // not a compile-time constant
+
+ KeyGenerator keyGen3 = KeyGenerator.getInstance("AES");
+ keyGen3.init(size1); // $ hasInsufficientKeySize
+
+ KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
+ keyGen4.init(size2); // $ hasInsufficientKeySize
+
+ /* Test variables passed to another method */
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // MISSING: test KeyGenerator variable as argument
+ testSymmetricVariable(size2, keyGen); // test with variable as key size
+ testSymmetricInt(64); // test with int literal as key size
}
- // RSA (Asymmetric)
+ // RSA (Asymmetric): minimum recommended key size is 2048
{
- // BAD: Key size is less than 2048
+ /* Test with keysize as int */
KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
keyPairGen1.initialize(1024); // $ hasInsufficientKeySize
- // GOOD: Key size is no less than 2048
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
- keyPairGen2.initialize(2048); // Safe
+ keyPairGen2.initialize(2048); // Safe: Key size is no less than 2048
- // test with spec
- // BAD: Key size is less than 2048
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("RSA");
RSAKeyGenParameterSpec rsaSpec = new RSAKeyGenParameterSpec(1024, null); // $ hasInsufficientKeySize
keyPairGen3.initialize(rsaSpec);
- // BAD: Key size is less than 2048
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("RSA");
keyPairGen4.initialize(new RSAKeyGenParameterSpec(1024, null)); // $ hasInsufficientKeySize
+
+ /* Test with local variable as keysize */
+ final int size1 = 1024; // compile-time constant
+ int size2 = 1024; // not a compile-time constant
+
+ KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen5.initialize(size1); // $ hasInsufficientKeySize
+
+ KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen6.initialize(size2); // $ hasInsufficientKeySize
+
+ /* Test variables passed to another method */
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); // MISSING: test KeyGenerator variable as argument
+ testAsymmetricNonEcVariable(size2, keyPairGen); // test with variable as key size
+ testAsymmetricNonEcInt(1024); // test with int literal as key size
}
- // DSA (Asymmetric)
+ // DSA (Asymmetric): minimum recommended key size is 2048
{
- // BAD: Key size is less than 2048
+ /* Test with keysize as int */
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("DSA");
+ keyPairGen1.initialize(1024); // $ hasInsufficientKeySize
+
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DSA");
+ keyPairGen2.initialize(2048); // Safe: Key size is no less than 2048
+
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
- keyPairGen3.initialize(1024); // $ hasInsufficientKeySize
-
- // GOOD: Key size is no less than 2048
- KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
- keyPairGen4.initialize(2048); // Safe
-
- // test with spec
- // BAD: Key size is less than 2048
- KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("DSA");
DSAGenParameterSpec dsaSpec = new DSAGenParameterSpec(1024, 0); // $ hasInsufficientKeySize
- keyPairGen5.initialize(dsaSpec);
+ keyPairGen3.initialize(dsaSpec);
- // BAD: Key size is less than 2048
- KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("DSA");
- keyPairGen6.initialize(new DSAGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
+ keyPairGen4.initialize(new DSAGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
}
- // DH (Asymmetric)
+ // DH (Asymmetric): minimum recommended key size is 2048
{
- // BAD: Key size is less than 2048
- KeyPairGenerator keyPairGen16 = KeyPairGenerator.getInstance("dh");
- keyPairGen16.initialize(1024); // $ hasInsufficientKeySize
+ /* Test with keysize as int */
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("dh");
+ keyPairGen1.initialize(1024); // $ hasInsufficientKeySize
- // GOOD: Key size is no less than 2048
- KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
- keyPairGen17.initialize(2048); // Safe
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DH");
+ keyPairGen2.initialize(2048); // Safe: Key size is no less than 2048
- // test with spec
- // BAD: Key size is less than 2048
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DH");
DHGenParameterSpec dhSpec = new DHGenParameterSpec(1024, 0); // $ hasInsufficientKeySize
keyPairGen3.initialize(dhSpec);
- // BAD: Key size is less than 2048
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DH");
keyPairGen4.initialize(new DHGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
}
- // EC (Asymmetric)
- // ! Check if I can re-use the same KeyPairGenerator instance with all of the below?
+ // EC (Asymmetric): minimum recommended key size is 256
{
- // BAD: Key size is less than 256
- KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
+ /* Test with keysize as int */
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("EC");
+ keyPairGen1.initialize(128); // $ hasInsufficientKeySize
+
+ /* Test with keysize as curve name in spec */
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
- keyPairGen5.initialize(ecSpec1);
+ keyPairGen2.initialize(ecSpec1);
- // BAD: Key size is less than 256
- KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
- keyPairGen6.initialize(new ECGenParameterSpec("secp112r1")); // $ hasInsufficientKeySize
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("EC");
+ keyPairGen3.initialize(new ECGenParameterSpec("secp112r1")); // $ hasInsufficientKeySize
- // GOOD: Key size is no less than 256
- KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
- keyPairGen7.initialize(ecSpec2); // Safe
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1"); // Safe: Key size is no less than 256
+ keyPairGen4.initialize(ecSpec2);
- // BAD: Key size is less than 256
- KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
+ KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2"); // $ hasInsufficientKeySize
- keyPairGen8.initialize(ecSpec3);
+ keyPairGen5.initialize(ecSpec3);
- // BAD: Key size is less than 256
- KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
+ KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3"); // $ hasInsufficientKeySize
- keyPairGen9.initialize(ecSpec4);
+ keyPairGen6.initialize(ecSpec4);
- // BAD: Key size is less than 256
- KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
+ KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1"); // $ hasInsufficientKeySize
- keyPairGen10.initialize(ecSpec5);
+ keyPairGen7.initialize(ecSpec5);
- // GOOD: Key size is no less than 256
- KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1");
- keyPairGen11.initialize(ecSpec6); // Safe
+ KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1"); // Safe: Key size is no less than 256
+ keyPairGen8.initialize(ecSpec6);
- // BAD: Key size is less than 256
- KeyPairGenerator keyPairGen12 = KeyPairGenerator.getInstance("EC");
+ KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2"); // $ hasInsufficientKeySize
- keyPairGen12.initialize(ecSpec7);
+ keyPairGen9.initialize(ecSpec7);
- // GOOD: Key size is no less than 256
- KeyPairGenerator keyPairGen13 = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecSpec8 = new ECGenParameterSpec("prime256v1");
- keyPairGen13.initialize(ecSpec8); // Safe
+ KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec8 = new ECGenParameterSpec("prime256v1"); // Safe: Key size is no less than 256
+ keyPairGen10.initialize(ecSpec8);
- // BAD: Key size is less than 256
KeyPairGenerator keyPairGen14 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1"); // $ hasInsufficientKeySize
keyPairGen14.initialize(ecSpec9);
- // GOOD: Key size is no less than 256
KeyPairGenerator keyPairGen15 = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec10 = new ECGenParameterSpec("c2tnb431r1");
- keyPairGen15.initialize(ecSpec10); // Safe
+ keyPairGen15.initialize(ecSpec10); // Safe: Key size is no less than 256
+
+ /* Test variables passed to another method */
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); // MISSING: test KeyGenerator variable as argument
+ testAsymmetricEC(ecSpec, keyPairGen); // test spec as an argument
}
-
- // ! FN Testing Additions:
-
- // Test local variable usage - Symmetric
- {
- final int size1 = 64; // compile-time constant
- int size2 = 64; // NOT a compile-time constant
-
- // BAD: Key size is less than 128
- KeyGenerator keyGen3 = KeyGenerator.getInstance("AES");
- keyGen3.init(size1); // $ hasInsufficientKeySize
-
- // BAD: Key size is less than 128
- KeyGenerator keyGen4 = KeyGenerator.getInstance("AES");
- keyGen4.init(size2); // $ hasInsufficientKeySize
- }
-
- // Test local variable usage - Asymmetric, Not EC
- {
- final int size1 = 1024; // compile-time constant
- int size2 = 1024; // NOT a compile-time constant
-
- // BAD: Key size is less than 2048
- KeyPairGenerator keyPairGen18 = KeyPairGenerator.getInstance("RSA");
- keyPairGen18.initialize(size1); // $ hasInsufficientKeySize
-
- // BAD: Key size is less than 2048
- KeyPairGenerator keyPairGen19 = KeyPairGenerator.getInstance("RSA");
- keyPairGen19.initialize(size2); // $ hasInsufficientKeySize
- }
-
-
- // Test variable passed to other method(s) - Symmetric
- {
- int size = 64; // test integer variable
- KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // test KeyGenerator variable
- testSymmetric(size, keyGen); // test with variable as key size
- testSymmetric2(64); // test with int literal as key size
- }
-
-
- // Test variables passed to other method(s) - Asymmetric, Not EC
- {
- int size = 1024; // test integer variable
- KeyPairGenerator keyPairGen21 = KeyPairGenerator.getInstance("RSA"); // test KeyPairGenerator variable
- testAsymmetricNonEC(size, keyPairGen21); // test with variable as key size
- testAsymmetricNonEC2(1024); // test with int literal as key size
- }
-
- // Test variable passed to other method(s) - Asymmetric, EC
- {
- ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize // test ECGenParameterSpec variable
- KeyPairGenerator keyPairGen22 = KeyPairGenerator.getInstance("EC"); // test KeyPairGenerator variable
- testAsymmetricEC(ecSpec, keyPairGen22);
-
- }
-
}
- public static void testSymmetric(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // BAD: Key size is less than 2048
+ public static void testSymmetricVariable(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(keySize); // $ hasInsufficientKeySize
-
- // BAD: Key size is less than 2048
kg.init(64); // $ MISSING: hasInsufficientKeySize
}
- //! refactor this to use expected-value tag and combine with above method
- public static void testSymmetric2(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // BAD: Key size is less than 2048
+ public static void testSymmetricInt(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(keySize); // $ hasInsufficientKeySize
}
- public static void testAsymmetricNonEC(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // BAD: Key size is less than 2048
+ public static void testAsymmetricNonEcVariable(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
-
- // BAD: Key size is less than 2048
kpg.initialize(1024); // $ MISSING: hasInsufficientKeySize
}
- //! refactor this to use expected-value tag and combine with above method
- public static void testAsymmetricNonEC2(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // BAD: Key size is less than 2048
+ public static void testAsymmetricNonEcInt(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
}
- public static void testAsymmetricEC(ECGenParameterSpec spec, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // BAD: Key size is less than 256
+ public static void testAsymmetricEcVariable(ECGenParameterSpec spec, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
- keyPairGen.initialize(spec); // sink is now at above where `spec` variable is initialized
+ keyPairGen.initialize(spec); // sink is above where `spec` variable is initialized
- // BAD: Key size is less than 256
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
- kpg.initialize(ecSpec);
+ kpg.initialize(ecSpec); // MISSING: test KeyGenerator variable as argument
}
- // ToDo testing:
- // ? todo #1: add tests for keysize variable passed to specs - not needed if spec is sink now
- // ? todo #3: add test for retrieving a key from elsewhere?
- // ? todo #4: add barrier-guard tests (see FP from OpenIdentityPlatform/OpenAM)
- // ? todo #5: add tests for updated keysize variable?: e.g. keysize = 1024; keysize += 1024; so when it's used it is correctly 2048.
- // ? todo #6: consider if some flow paths for keysize variables will be too hard to track how the keysize is updated (e.g. if calling some other method to get keysize, etc....)
+ public static void testAsymmetricEcInt(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
+ keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
+ }
+
+ // public static void testVariable(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ // keyGen.init(keySize); // $ hasInsufficientKeySize
+
+ // // BAD: Key size is less than 2048
+ // kg.init(64); // $ MISSING: hasInsufficientKeySize
+ // }
+
+ // public static void testInt(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ // }
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
index 5dc7ecdf467..59b8a39ed0d 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.ql
@@ -2,7 +2,6 @@ import java
import TestUtilities.InlineExpectationsTest
import semmle.code.java.security.InsufficientKeySizeQuery
-//import DataFlow::PathGraph // Note: importing this messes up tests - adds edges and nodes to actual file...
class InsufficientKeySizeTest extends InlineExpectationsTest {
InsufficientKeySizeTest() { this = "InsufficientKeySize" }
@@ -11,12 +10,8 @@ class InsufficientKeySizeTest extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasInsufficientKeySize" and
exists(DataFlow::PathNode source, DataFlow::PathNode sink |
- exists(KeySizeConfiguration config1 | config1.hasFlowPath(source, sink))
+ exists(KeySizeConfiguration cfg | cfg.hasFlowPath(source, sink))
|
- //or
- // exists(AsymmetricNonECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
- // exists(AsymmetricECKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink)) or
- // exists(SymmetricKeyTrackingConfiguration cfg | cfg.hasFlowPath(source, sink))
sink.getNode().getLocation() = location and
element = sink.getNode().toString() and
value = ""
From 5f39888a2dc9512e9f542c890abee67122b9f781 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 17 Oct 2022 16:28:06 -0400
Subject: [PATCH 051/465] minor code restructure
---
.../semmle/code/java/security/Encryption.qll | 59 +++++++++++--------
.../java/security/InsufficientKeySize.qll | 2 +-
2 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index adb2dc74691..464f8fda0a8 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -323,20 +323,21 @@ class JavaxCryptoSecretKey extends JavaxCryptoAlgoSpec {
}
}
-/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
-class DhGenParameterSpec extends RefType {
- DhGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
-}
-
class JavaxCryptoKeyGenerator extends JavaxCryptoAlgoSpec {
JavaxCryptoKeyGenerator() {
+ exists(Constructor c | c.getAReference() = this | c.getDeclaringType() instanceof KeyGenerator)
+ or
exists(Method m | m.getAReference() = this |
m.getDeclaringType() instanceof KeyGenerator and
m.getName() = "getInstance"
)
}
- override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
+ override Expr getAlgoSpec() {
+ exists(Call c | c = this |
+ if c.getNumArgument() = 3 then result = c.getArgument(2) else result = c.getArgument(0)
+ )
+ }
}
class JavaxCryptoKeyAgreement extends JavaxCryptoAlgoSpec {
@@ -388,29 +389,41 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
-/** The Java class `java.security.spec.ECGenParameterSpec`. */
-class EcGenParameterSpec extends RefType {
- EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
-}
-
-/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
-class RsaKeyGenParameterSpec extends RefType {
- RsaKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
-}
-
-/** The Java class `java.security.spec.DSAGenParameterSpec`. */
-class DsaGenParameterSpec extends RefType {
- DsaGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
-}
-
/** A method call to the Java class `java.security.KeyPairGenerator`. */
-class JavaSecurityKeyPairGenerator extends JavaxCryptoAlgoSpec {
+class JavaSecurityKeyPairGenerator extends JavaSecurityAlgoSpec {
JavaSecurityKeyPairGenerator() {
+ exists(Constructor c | c.getAReference() = this |
+ c.getDeclaringType() instanceof KeyPairGenerator
+ )
+ or
exists(Method m | m.getAReference() = this |
m.getDeclaringType() instanceof KeyPairGenerator and
m.getName() = "getInstance"
)
}
- override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
+ override Expr getAlgoSpec() { result = this.(Call).getArgument(0) }
+}
+
+/** The Java interface `java.security.spec.AlgorithmParameterSpec` */
+abstract class AlgorithmParameterSpec extends RefType { }
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+class EcGenParameterSpec extends AlgorithmParameterSpec {
+ EcGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The Java class `java.security.spec.RSAKeyGenParameterSpec`. */
+class RsaKeyGenParameterSpec extends AlgorithmParameterSpec {
+ RsaKeyGenParameterSpec() { this.hasQualifiedName("java.security.spec", "RSAKeyGenParameterSpec") }
+}
+
+/** The Java class `java.security.spec.DSAGenParameterSpec`. */
+class DsaGenParameterSpec extends AlgorithmParameterSpec {
+ DsaGenParameterSpec() { this.hasQualifiedName("java.security.spec", "DSAGenParameterSpec") }
+}
+
+/** The Java class `javax.crypto.spec.DHGenParameterSpec`. */
+class DhGenParameterSpec extends AlgorithmParameterSpec {
+ DhGenParameterSpec() { this.hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") }
}
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 16f960f1b6d..be12385f5cf 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -124,7 +124,7 @@ private class SymmetricInitMethodAccess extends KeyGenInitMethodAccess {
}
/** An instance of a key generator. */
-abstract class KeyGeneratorObject extends JavaxCryptoAlgoSpec {
+abstract class KeyGeneratorObject extends CryptoAlgoSpec {
string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
}
From 55bda34a45f50e1b35dc060511f6cdb9b7c0653c Mon Sep 17 00:00:00 2001
From: Arthur Baars
Date: Tue, 18 Oct 2022 15:07:35 +0200
Subject: [PATCH 052/465] Ruby: drop beta notice
---
docs/codeql/codeql-language-guides/codeql-for-ruby.rst | 1 -
docs/codeql/query-help/codeql-cwe-coverage.rst | 1 -
docs/codeql/query-help/index.rst | 2 --
docs/codeql/reusables/ruby-beta-note.rst | 4 ----
4 files changed, 8 deletions(-)
delete mode 100644 docs/codeql/reusables/ruby-beta-note.rst
diff --git a/docs/codeql/codeql-language-guides/codeql-for-ruby.rst b/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
index bfb29a012ef..b19f8abe230 100644
--- a/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
+++ b/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
@@ -15,4 +15,3 @@ Experiment and learn how to write effective and efficient queries for CodeQL dat
- :doc:`CodeQL library for Ruby `: When you're analyzing a Ruby program, you can make use of the large collection of classes in the CodeQL library for Ruby.
-.. include:: ../reusables/ruby-beta-note.rst
diff --git a/docs/codeql/query-help/codeql-cwe-coverage.rst b/docs/codeql/query-help/codeql-cwe-coverage.rst
index 30e7b569184..c0b36646df8 100644
--- a/docs/codeql/query-help/codeql-cwe-coverage.rst
+++ b/docs/codeql/query-help/codeql-cwe-coverage.rst
@@ -35,4 +35,3 @@ Note that the CWE coverage includes both "`supported queries `."
-.. include:: ../reusables/ruby-beta-note.rst
-
.. toctree::
:hidden:
:titlesonly:
diff --git a/docs/codeql/reusables/ruby-beta-note.rst b/docs/codeql/reusables/ruby-beta-note.rst
deleted file mode 100644
index 761381777c0..00000000000
--- a/docs/codeql/reusables/ruby-beta-note.rst
+++ /dev/null
@@ -1,4 +0,0 @@
- .. pull-quote:: Note
-
- CodeQL analysis for Ruby is currently in beta. During the beta, analysis of Ruby code,
- and the accompanying documentation, will not be as comprehensive as for other languages.
From 383b8a84e95b2017b45cd030546ee8bf4ee7ba91 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 18 Oct 2022 21:55:11 -0400
Subject: [PATCH 053/465] update select statement to be closer to cpp's
---
java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index b82104db79a..23efca5483c 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -1,9 +1,9 @@
/**
- * @name Insufficient key size used with a cryptographic algorithm
- * @description Using cryptographic algorithms with too small of a key size can
+ * @name Use of a cryptographic algorithm with insufficient key size
+ * @description Using cryptographic algorithms with too small a key size can
* allow an attacker to compromise security.
* @kind path-problem
- * @problem.severity error
+ * @problem.severity warning
* @security-severity 7.5
* @precision high
* @id java/insufficient-key-size
@@ -17,5 +17,6 @@ import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink
where exists(KeySizeConfiguration cfg | cfg.hasFlowPath(source, sink))
-select sink.getNode(), source, sink, "This $@ is less than the recommended key size.",
+select sink.getNode(), source, sink,
+ "This $@ is less than the recommended key size of " + source.getState() + " bits.",
source.getNode(), "key size"
From ff557a287f552a918e9410d3517dca63ab450d05 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 18 Oct 2022 23:08:54 -0400
Subject: [PATCH 054/465] add min key size predicates
---
.../java/security/InsufficientKeySize.qll | 40 +++++++++++++------
1 file changed, 28 insertions(+), 12 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index be12385f5cf..8dfd2987eb0 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -18,32 +18,45 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
// *********************************** SOURCES ***********************************
/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
- AsymmetricNonEcSource() { getNodeIntValue(this) < 2048 }
+ AsymmetricNonEcSource() { getNodeIntValue(this) < getMinAsymNonEcKeySize() }
- override predicate hasState(DataFlow::FlowState state) { state = "2048" }
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymNonEcKeySize().toString()
+ }
}
/** A source for an insufficient key size used in elliptic curve (EC) algorithms. */
private class AsymmetricEcSource extends InsufficientKeySizeSource {
AsymmetricEcSource() {
- getNodeIntValue(this) < 256
+ getNodeIntValue(this) < getMinAsymEcKeySize()
or
// the below is needed for cases when the key size is embedded in the curve name
- getEcKeySize(this.asExpr().(StringLiteral).getValue()) < 256
+ getEcKeySize(this.asExpr().(StringLiteral).getValue()) < getMinAsymEcKeySize()
}
- override predicate hasState(DataFlow::FlowState state) { state = "256" }
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymEcKeySize().toString()
+ }
}
/** A source for an insufficient key size used in AES algorithms. */
private class SymmetricSource extends InsufficientKeySizeSource {
- SymmetricSource() { getNodeIntValue(this) < 128 }
+ SymmetricSource() { getNodeIntValue(this) < getMinSymKeySize() }
- override predicate hasState(DataFlow::FlowState state) { state = "128" }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
// ************************** SOURCES HELPER PREDICATES **************************
-/** Returns the integer value of a given Node. */
+/** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
+private int getMinAsymNonEcKeySize() { result = 2048 }
+
+/** Returns the minimum recommended key size for elliptic curve (EC) algorithms. */
+private int getMinAsymEcKeySize() { result = 256 }
+
+/** Returns the minimum recommended key size for AES algorithms. */
+private int getMinSymKeySize() { result = 128 }
+
+/** Returns the integer value of a given DataFlow::Node. */
private int getNodeIntValue(DataFlow::Node node) {
result = node.asExpr().(IntegerLiteral).getIntValue()
}
@@ -74,7 +87,9 @@ private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
exists(AsymmetricNonEcSpec spec | this.asExpr() = spec.getKeySizeArg())
}
- override predicate hasState(DataFlow::FlowState state) { state = "2048" }
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymNonEcKeySize().toString()
+ }
}
/** A sink for an insufficient key size used in elliptic curve (EC) algorithms. */
@@ -89,13 +104,14 @@ private class AsymmetricEcSink extends InsufficientKeySizeSink {
exists(AsymmetricEcSpec s | this.asExpr() = s.getKeySizeArg())
}
- override predicate hasState(DataFlow::FlowState state) { state = "256" }
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymEcKeySize().toString()
+ }
}
/** A sink for an insufficient key size used in AES algorithms. */
private class SymmetricSink extends InsufficientKeySizeSink {
SymmetricSink() {
- //hasKeySizeInInitMethod(this, "symmetric")
exists(SymmetricInitMethodAccess ma, SymmetricKeyGenerator kg |
kg.getAlgoName() = "AES" and
DataFlow::localExprFlow(kg, ma.getQualifier()) and
@@ -103,7 +119,7 @@ private class SymmetricSink extends InsufficientKeySizeSink {
)
}
- override predicate hasState(DataFlow::FlowState state) { state = "128" }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
// ********************** SINKS HELPER CLASSES & PREDICATES **********************
From dc8b62baa0c68de6f66004c81e2b32c20adcd220 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 19 Oct 2022 00:11:59 -0400
Subject: [PATCH 055/465] add support for AlgorithmParameterGenerator
---
.../semmle/code/java/security/Encryption.qll | 37 ++++++++++++++++++-
.../java/security/InsufficientKeySize.qll | 14 +++++--
2 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 464f8fda0a8..7fb3b16542f 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -389,7 +389,7 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
-/** A method call to the Java class `java.security.KeyPairGenerator`. */
+/** An instance of a `java.security.KeyPairGenerator`. */
class JavaSecurityKeyPairGenerator extends JavaSecurityAlgoSpec {
JavaSecurityKeyPairGenerator() {
exists(Constructor c | c.getAReference() = this |
@@ -405,6 +405,41 @@ class JavaSecurityKeyPairGenerator extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(Call).getArgument(0) }
}
+/** The Java class `java.security.AlgorithmParameterGenerator`. */
+class AlgorithmParameterGenerator extends RefType {
+ AlgorithmParameterGenerator() {
+ this.hasQualifiedName("java.security", "AlgorithmParameterGenerator")
+ }
+}
+
+/** The `init` method declared in `java.security.AlgorithmParameterGenerator`. */
+class AlgoParamGeneratorInitMethod extends Method {
+ AlgoParamGeneratorInitMethod() {
+ this.getDeclaringType() instanceof AlgorithmParameterGenerator and
+ this.hasName("init")
+ }
+}
+
+/** An instance of a `java.security.AlgorithmParameterGenerator`. */
+class JavaSecurityAlgoParamGenerator extends JavaSecurityAlgoSpec {
+ JavaSecurityAlgoParamGenerator() {
+ exists(Constructor c | c.getAReference() = this |
+ c.getDeclaringType() instanceof AlgorithmParameterGenerator
+ )
+ or
+ exists(Method m | m.getAReference() = this |
+ m.getDeclaringType() instanceof AlgorithmParameterGenerator and
+ m.getName() = "getInstance"
+ )
+ }
+
+ override Expr getAlgoSpec() {
+ exists(Call c | c = this |
+ if c.getNumArgument() = 3 then result = c.getArgument(2) else result = c.getArgument(0)
+ )
+ }
+}
+
/** The Java interface `java.security.spec.AlgorithmParameterSpec` */
abstract class AlgorithmParameterSpec extends RefType { }
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 8dfd2987eb0..73203c3e6f2 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -131,7 +131,10 @@ abstract class KeyGenInitMethodAccess extends MethodAccess {
/** A call to the `initialize` method declared in `java.security.KeyPairGenerator`. */
private class AsymmetricInitMethodAccess extends KeyGenInitMethodAccess {
- AsymmetricInitMethodAccess() { this.getMethod() instanceof KeyPairGeneratorInitMethod }
+ AsymmetricInitMethodAccess() {
+ this.getMethod() instanceof KeyPairGeneratorInitMethod or
+ this.getMethod() instanceof AlgoParamGeneratorInitMethod
+ }
}
/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
@@ -146,16 +149,19 @@ abstract class KeyGeneratorObject extends CryptoAlgoSpec {
/** An instance of a `java.security.KeyPairGenerator`. */
private class AsymmetricKeyGenerator extends KeyGeneratorObject {
- AsymmetricKeyGenerator() { this instanceof JavaSecurityKeyPairGenerator }
+ AsymmetricKeyGenerator() {
+ this instanceof JavaSecurityKeyPairGenerator or
+ this instanceof JavaSecurityAlgoParamGenerator
+ }
- override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
+ override Expr getAlgoSpec() { result = this.getAlgoSpec() }
}
/** An instance of a `javax.crypto.KeyGenerator`. */
private class SymmetricKeyGenerator extends KeyGeneratorObject {
SymmetricKeyGenerator() { this instanceof JavaxCryptoKeyGenerator }
- override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
+ override Expr getAlgoSpec() { result = this.getAlgoSpec() }
}
/** An instance of an algorithm specification. */
From 4df0fbcce1ca04bd9e8de33031ff9da34f2bb6f5 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 19 Oct 2022 01:17:57 -0400
Subject: [PATCH 056/465] update tests
---
.../semmle/code/java/security/Encryption.qll | 24 ++-------
.../CWE-326/InsufficientKeySizeTest.java | 50 +++++++++++--------
2 files changed, 33 insertions(+), 41 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 7fb3b16542f..208343da0cc 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -325,19 +325,13 @@ class JavaxCryptoSecretKey extends JavaxCryptoAlgoSpec {
class JavaxCryptoKeyGenerator extends JavaxCryptoAlgoSpec {
JavaxCryptoKeyGenerator() {
- exists(Constructor c | c.getAReference() = this | c.getDeclaringType() instanceof KeyGenerator)
- or
exists(Method m | m.getAReference() = this |
m.getDeclaringType() instanceof KeyGenerator and
m.getName() = "getInstance"
)
}
- override Expr getAlgoSpec() {
- exists(Call c | c = this |
- if c.getNumArgument() = 3 then result = c.getArgument(2) else result = c.getArgument(0)
- )
- }
+ override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
class JavaxCryptoKeyAgreement extends JavaxCryptoAlgoSpec {
@@ -392,17 +386,13 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
/** An instance of a `java.security.KeyPairGenerator`. */
class JavaSecurityKeyPairGenerator extends JavaSecurityAlgoSpec {
JavaSecurityKeyPairGenerator() {
- exists(Constructor c | c.getAReference() = this |
- c.getDeclaringType() instanceof KeyPairGenerator
- )
- or
exists(Method m | m.getAReference() = this |
m.getDeclaringType() instanceof KeyPairGenerator and
m.getName() = "getInstance"
)
}
- override Expr getAlgoSpec() { result = this.(Call).getArgument(0) }
+ override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
/** The Java class `java.security.AlgorithmParameterGenerator`. */
@@ -423,21 +413,13 @@ class AlgoParamGeneratorInitMethod extends Method {
/** An instance of a `java.security.AlgorithmParameterGenerator`. */
class JavaSecurityAlgoParamGenerator extends JavaSecurityAlgoSpec {
JavaSecurityAlgoParamGenerator() {
- exists(Constructor c | c.getAReference() = this |
- c.getDeclaringType() instanceof AlgorithmParameterGenerator
- )
- or
exists(Method m | m.getAReference() = this |
m.getDeclaringType() instanceof AlgorithmParameterGenerator and
m.getName() = "getInstance"
)
}
- override Expr getAlgoSpec() {
- exists(Call c | c = this |
- if c.getNumArgument() = 3 then result = c.getArgument(2) else result = c.getArgument(0)
- )
- }
+ override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
/** The Java interface `java.security.spec.AlgorithmParameterSpec` */
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 3e6a1649ceb..e60544eb6dc 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -1,5 +1,6 @@
import javax.crypto.KeyGenerator;
import java.security.KeyPairGenerator;
+import java.security.AlgorithmParameterGenerator;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
@@ -30,8 +31,8 @@ public class InsufficientKeySizeTest {
keyGen4.init(size2); // $ hasInsufficientKeySize
/* Test variables passed to another method */
- KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // MISSING: test KeyGenerator variable as argument
- testSymmetricVariable(size2, keyGen); // test with variable as key size
+ KeyGenerator keyGen5 = KeyGenerator.getInstance("AES"); // MISSING: test KeyGenerator variable as argument
+ testSymmetricVariable(size2, keyGen5); // test with variable as key size
testSymmetricInt(64); // test with int literal as key size
}
@@ -62,9 +63,13 @@ public class InsufficientKeySizeTest {
keyPairGen6.initialize(size2); // $ hasInsufficientKeySize
/* Test variables passed to another method */
- KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); // MISSING: test KeyGenerator variable as argument
- testAsymmetricNonEcVariable(size2, keyPairGen); // test with variable as key size
+ KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("RSA"); // MISSING: test KeyGenerator variable as argument
+ testAsymmetricNonEcVariable(size2, keyPairGen7); // test with variable as key size
testAsymmetricNonEcInt(1024); // test with int literal as key size
+
+ /* Test getting key size as return value of another method */
+ KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("RSA");
+ keyPairGen8.initialize(getRSAKeySize()); // $ hasInsufficientKeySize
}
// DSA (Asymmetric): minimum recommended key size is 2048
@@ -82,6 +87,10 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
keyPairGen4.initialize(new DSAGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
+
+ /* Test `AlgorithmParameterGenerator` */
+ AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DSA");
+ paramGen.init(1024); // $ hasInsufficientKeySize
}
// DH (Asymmetric): minimum recommended key size is 2048
@@ -99,6 +108,10 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DH");
keyPairGen4.initialize(new DHGenParameterSpec(1024, 0)); // $ hasInsufficientKeySize
+
+ /* Test `AlgorithmParameterGenerator` */
+ AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH");
+ paramGen.init(1024); // $ hasInsufficientKeySize
}
// EC (Asymmetric): minimum recommended key size is 256
@@ -153,8 +166,11 @@ public class InsufficientKeySizeTest {
/* Test variables passed to another method */
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
+ testAsymmetricEcSpecVariable(ecSpec); // test spec as an argument
+ int size = 128;
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); // MISSING: test KeyGenerator variable as argument
- testAsymmetricEC(ecSpec, keyPairGen); // test spec as an argument
+ testAsymmetricEcIntVariable(size, keyPairGen); // test with variable as key size
+ testAsymmetricEcIntLiteral(128); // test with int literal as key size
}
}
@@ -180,27 +196,21 @@ public class InsufficientKeySizeTest {
keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
}
- public static void testAsymmetricEcVariable(ECGenParameterSpec spec, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ public static void testAsymmetricEcSpecVariable(ECGenParameterSpec spec) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
keyPairGen.initialize(spec); // sink is above where `spec` variable is initialized
-
- ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // $ hasInsufficientKeySize
- kpg.initialize(ecSpec); // MISSING: test KeyGenerator variable as argument
}
- public static void testAsymmetricEcInt(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ public static void testAsymmetricEcIntVariable(int keySize, KeyPairGenerator kpg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
+ keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
+ kpg.initialize(128); // $ MISSING: hasInsufficientKeySize
+ }
+
+ public static void testAsymmetricEcIntLiteral(int keySize) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
keyPairGen.initialize(keySize); // $ hasInsufficientKeySize
}
- // public static void testVariable(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // KeyGenerator keyGen = KeyGenerator.getInstance("AES");
- // keyGen.init(keySize); // $ hasInsufficientKeySize
-
- // // BAD: Key size is less than 2048
- // kg.init(64); // $ MISSING: hasInsufficientKeySize
- // }
-
- // public static void testInt(int keySize, KeyGenerator kg) throws java.security.NoSuchAlgorithmException, java.security.InvalidAlgorithmParameterException {
- // }
+ public int getRSAKeySize(){ return 1024; }
}
From 961e5c72a334e45ffd60151b626247bc9fc158d9 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 19 Oct 2022 08:44:35 -0400
Subject: [PATCH 057/465] minor updates
---
.../code/java/security/InsufficientKeySize.qll | 16 +++++++++++-----
.../CWE-326/InsufficientKeySizeTest.java | 1 -
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 73203c3e6f2..e4bb8e0a646 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -124,12 +124,15 @@ private class SymmetricSink extends InsufficientKeySizeSink {
// ********************** SINKS HELPER CLASSES & PREDICATES **********************
/** A call to a method that initializes a key generator. */
-abstract class KeyGenInitMethodAccess extends MethodAccess {
+abstract private class KeyGenInitMethodAccess extends MethodAccess {
/** Gets the `keysize` argument of this call. */
Argument getKeySizeArg() { result = this.getArgument(0) }
}
-/** A call to the `initialize` method declared in `java.security.KeyPairGenerator`. */
+/**
+ * A call to the `initialize` method declared in `java.security.KeyPairGenerator`
+ * or to the `init` method declared in `java.security.AlgorithmParameterGenerator`.
+ */
private class AsymmetricInitMethodAccess extends KeyGenInitMethodAccess {
AsymmetricInitMethodAccess() {
this.getMethod() instanceof KeyPairGeneratorInitMethod or
@@ -143,11 +146,14 @@ private class SymmetricInitMethodAccess extends KeyGenInitMethodAccess {
}
/** An instance of a key generator. */
-abstract class KeyGeneratorObject extends CryptoAlgoSpec {
+abstract private class KeyGeneratorObject extends CryptoAlgoSpec {
string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
}
-/** An instance of a `java.security.KeyPairGenerator`. */
+/**
+ * An instance of a `java.security.KeyPairGenerator`
+ * or of a `java.security.AlgorithmParameterGenerator`.
+ */
private class AsymmetricKeyGenerator extends KeyGeneratorObject {
AsymmetricKeyGenerator() {
this instanceof JavaSecurityKeyPairGenerator or
@@ -165,7 +171,7 @@ private class SymmetricKeyGenerator extends KeyGeneratorObject {
}
/** An instance of an algorithm specification. */
-abstract class AlgoSpec extends ClassInstanceExpr {
+abstract private class AlgoSpec extends ClassInstanceExpr {
Argument getKeySizeArg() { result = this.getArgument(0) }
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index e60544eb6dc..746239a846f 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -1,7 +1,6 @@
import javax.crypto.KeyGenerator;
import java.security.KeyPairGenerator;
import java.security.AlgorithmParameterGenerator;
-
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.DSAGenParameterSpec;
From 429bd5fbd86130f189e9957d3dad4c297d3c3732 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Wed, 19 Oct 2022 15:30:39 +0200
Subject: [PATCH 058/465] Add flow summaries for startActivities
Uses SyntheticCallables and SyntheticGlobals to pair each startActivities call to getIntent calls in the components targeted by the intent(s).
---
.../semmle/code/java/dataflow/FlowSummary.qll | 8 ++
.../code/java/frameworks/android/Intent.qll | 123 +++++++++++++++++-
.../intent/TestStartActivityToGetIntent.java | 21 ++-
3 files changed, 148 insertions(+), 4 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
index ed9b0de165d..6b790e487c1 100644
--- a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
@@ -26,6 +26,9 @@ module SummaryComponent {
/** Gets a summary component for `Element`. */
SummaryComponent element() { result = content(any(CollectionContent c)) }
+ /** Gets a summary component for `ArrayElement`. */
+ SummaryComponent arrayElement() { result = content(any(ArrayContent c)) }
+
/** Gets a summary component for `MapValue`. */
SummaryComponent mapValue() { result = content(any(MapValueContent c)) }
@@ -52,6 +55,11 @@ module SummaryComponentStack {
result = push(SummaryComponent::element(), object)
}
+ /** Gets a stack representing `ArrayElement` of `object`. */
+ SummaryComponentStack arrayElementOf(SummaryComponentStack object) {
+ result = push(SummaryComponent::arrayElement(), object)
+ }
+
/** Gets a stack representing `MapValue` of `object`. */
SummaryComponentStack mapValueOf(SummaryComponentStack object) {
result = push(SummaryComponent::mapValue(), object)
diff --git a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
index be38b83e5a7..d4c67d10761 100644
--- a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
@@ -1,7 +1,10 @@
import java
+private import semmle.code.java.frameworks.android.Android
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSteps
+private import semmle.code.java.dataflow.FlowSummary
+private import semmle.code.java.dataflow.internal.BaseSSA as BaseSSA
/** The class `android.content.Intent`. */
class TypeIntent extends Class {
@@ -242,19 +245,52 @@ private class StartComponentMethodAccess extends MethodAccess {
/** Gets the intent argument of this call. */
Argument getIntentArg() {
- result.getType() instanceof TypeIntent and
+ (
+ result.getType() instanceof TypeIntent or
+ result.getType().(Array).getElementType() instanceof TypeIntent
+ ) and
result = this.getAnArgument()
}
/** Holds if this targets a component of type `targetType`. */
- predicate targetsComponentType(RefType targetType) {
+ predicate targetsComponentType(AndroidComponent targetType) {
exists(NewIntent newIntent |
- DataFlow::localExprFlow(newIntent, this.getIntentArg()) and
+ reaches(newIntent, this.getIntentArg()) and
newIntent.getClassArg().getType().(ParameterizedType).getATypeArgument() = targetType
)
}
}
+/**
+ * Holds if `src` reaches the intent argument `arg` of `StartComponentMethodAccess`
+ * through intra-procedural steps.
+ */
+private predicate reaches(Expr src, Argument arg) {
+ any(StartComponentMethodAccess ma).getIntentArg() = arg and
+ src = arg
+ or
+ exists(Expr mid, BaseSSA::BaseSsaVariable ssa, BaseSSA::BaseSsaUpdate upd |
+ reaches(mid, arg) and
+ mid = ssa.getAUse() and
+ upd = ssa.getAnUltimateLocalDefinition() and
+ src = upd.getDefiningExpr().(VariableAssign).getSource()
+ )
+ or
+ exists(CastingExpr e | e.getExpr() = src | reaches(e, arg))
+ or
+ exists(ChooseExpr e | e.getAResultExpr() = src | reaches(e, arg))
+ or
+ exists(AssignExpr e | e.getSource() = src | reaches(e, arg))
+ or
+ exists(ArrayCreationExpr e | e.getInit().getAnInit() = src | reaches(e, arg))
+ or
+ exists(StmtExpr e | e.getResultExpr() = src | reaches(e, arg))
+ or
+ exists(NotNullExpr e | e.getExpr() = src | reaches(e, arg))
+ or
+ exists(WhenExpr e | e.getBranch(_).getAResult() = src | reaches(e, arg))
+}
+
/**
* A value-preserving step from the intent argument of a `startActivity` call to
* a `getIntent` call in the activity the intent targeted in its constructor.
@@ -271,6 +307,87 @@ private class StartActivityIntentStep extends AdditionalValueStep {
}
}
+/**
+ * Holds if `targetType` is targeted by an existing `StartComponentMethodAccess` call
+ * and it's identified by `id`.
+ */
+private predicate isTargetableType(AndroidComponent targetType, string id) {
+ exists(StartComponentMethodAccess ma | ma.targetsComponentType(targetType)) and
+ targetType.getQualifiedName() = id
+}
+
+private class StartActivitiesSyntheticCallable extends SyntheticCallable {
+ AndroidComponent targetType;
+
+ StartActivitiesSyntheticCallable() {
+ exists(string id |
+ this = "android.content.Activity.startActivities()+" + id and
+ isTargetableType(targetType, id)
+ )
+ }
+
+ override StartComponentMethodAccess getACall() {
+ result.getMethod().hasName("startActivities") and
+ result.targetsComponentType(targetType)
+ }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ exists(ActivityIntentSyntheticGlobal glob | glob.getTargetType() = targetType |
+ input = SummaryComponentStack::arrayElementOf(SummaryComponentStack::argument(0)) and
+ output = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(glob)) and
+ preservesValue = true
+ )
+ }
+}
+
+private class GetIntentSyntheticCallable extends SyntheticCallable {
+ AndroidComponent targetType;
+
+ GetIntentSyntheticCallable() {
+ exists(string id |
+ this = "android.content.Activity.getIntent()+" + id and
+ isTargetableType(targetType, id)
+ )
+ }
+
+ override Call getACall() {
+ result.getCallee() instanceof AndroidGetIntentMethod and
+ result.getEnclosingCallable().getDeclaringType() = targetType
+ }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ exists(ActivityIntentSyntheticGlobal glob | glob.getTargetType() = targetType |
+ input = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(glob)) and
+ output = SummaryComponentStack::return() and
+ preservesValue = true
+ )
+ }
+}
+
+private class ActivityIntentSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
+ AndroidComponent targetType;
+
+ ActivityIntentSyntheticGlobal() {
+ exists(string id |
+ this = "ActivityIntentSyntheticGlobal+" + id and
+ isTargetableType(targetType, id)
+ )
+ }
+
+ AndroidComponent getTargetType() { result = targetType }
+}
+
+private class RequiredComponentStackForStartActivities extends RequiredSummaryComponentStack {
+ override predicate required(SummaryComponent head, SummaryComponentStack tail) {
+ head = SummaryComponent::element() and
+ tail = SummaryComponentStack::argument(0)
+ }
+}
+
/**
* A value-preserving step from the intent argument of a `sendBroadcast` call to
* the intent parameter in the `onReceive` method of the receiver the
diff --git a/java/ql/test/library-tests/frameworks/android/intent/TestStartActivityToGetIntent.java b/java/ql/test/library-tests/frameworks/android/intent/TestStartActivityToGetIntent.java
index 35884a23a58..09e785e5b6c 100644
--- a/java/ql/test/library-tests/frameworks/android/intent/TestStartActivityToGetIntent.java
+++ b/java/ql/test/library-tests/frameworks/android/intent/TestStartActivityToGetIntent.java
@@ -24,6 +24,12 @@ public class TestStartActivityToGetIntent {
Intent[] intents = new Intent[] {intent};
ctx.startActivities(intents);
}
+ {
+ Intent intent = new Intent(null, AnotherActivity.class);
+ intent.putExtra("data", (String) source("ctx-start-acts-2"));
+ Intent[] intents = new Intent[] {intent};
+ ctx.startActivities(intents);
+ }
{
Intent intent = new Intent(null, SomeActivity.class);
intent.putExtra("data", (String) source("act-start"));
@@ -35,6 +41,12 @@ public class TestStartActivityToGetIntent {
Intent[] intents = new Intent[] {intent};
act.startActivities(intents);
}
+ {
+ Intent intent = new Intent(null, Object.class);
+ intent.putExtra("data", (String) source("start-activities-should-not-reach"));
+ Intent[] intents = new Intent[] {intent};
+ act.startActivities(intents);
+ }
{
Intent intent = new Intent(null, SomeActivity.class);
intent.putExtra("data", (String) source("start-for-result"));
@@ -79,9 +91,16 @@ public class TestStartActivityToGetIntent {
static class SomeActivity extends Activity {
public void test() {
- sink(getIntent().getStringExtra("data")); // $ hasValueFlow=ctx-start hasValueFlow=act-start hasValueFlow=start-for-result hasValueFlow=start-if-needed hasValueFlow=start-matching hasValueFlow=start-from-child hasValueFlow=start-from-frag hasValueFlow=4-arg MISSING: hasValueFlow=ctx-start-acts hasValueFlow=act-start-acts
+ // @formatter:off
+ sink(getIntent().getStringExtra("data")); // $ hasValueFlow=ctx-start hasValueFlow=act-start hasValueFlow=start-for-result hasValueFlow=start-if-needed hasValueFlow=start-matching hasValueFlow=start-from-child hasValueFlow=start-from-frag hasValueFlow=4-arg hasValueFlow=ctx-start-acts hasValueFlow=act-start-acts
+ // @formatter:on
}
+ }
+ static class AnotherActivity extends Activity {
+ public void test() {
+ sink(getIntent().getStringExtra("data")); // $ hasValueFlow=ctx-start-acts-2
+ }
}
static class SafeActivity extends Activity {
From 25241276b0bf75ba598f013f87788f54f80aa368 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Wed, 19 Oct 2022 16:29:36 +0200
Subject: [PATCH 059/465] Add change note
---
.../2022-10-19-android-startactivities-summaries.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 java/ql/lib/change-notes/2022-10-19-android-startactivities-summaries.md
diff --git a/java/ql/lib/change-notes/2022-10-19-android-startactivities-summaries.md b/java/ql/lib/change-notes/2022-10-19-android-startactivities-summaries.md
new file mode 100644
index 00000000000..4716fb2ac41
--- /dev/null
+++ b/java/ql/lib/change-notes/2022-10-19-android-startactivities-summaries.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* Added data flow summaries for tainted Android intents sent to activities via `Activity.startActivities`.
\ No newline at end of file
From 0678b06a9bbd4f3ce8650030b941b5d0f7ef03f3 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Wed, 19 Oct 2022 16:58:39 +0200
Subject: [PATCH 060/465] Apply review suggestions
---
java/ql/lib/semmle/code/java/frameworks/android/Intent.qll | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
index d4c67d10761..e37e7f350b8 100644
--- a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
@@ -4,7 +4,7 @@ private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.FlowSummary
-private import semmle.code.java.dataflow.internal.BaseSSA as BaseSSA
+private import semmle.code.java.dataflow.internal.BaseSSA as BaseSsa
/** The class `android.content.Intent`. */
class TypeIntent extends Class {
@@ -269,7 +269,7 @@ private predicate reaches(Expr src, Argument arg) {
any(StartComponentMethodAccess ma).getIntentArg() = arg and
src = arg
or
- exists(Expr mid, BaseSSA::BaseSsaVariable ssa, BaseSSA::BaseSsaUpdate upd |
+ exists(Expr mid, BaseSsa::BaseSsaVariable ssa, BaseSsa::BaseSsaUpdate upd |
reaches(mid, arg) and
mid = ssa.getAUse() and
upd = ssa.getAnUltimateLocalDefinition() and
@@ -383,7 +383,7 @@ private class ActivityIntentSyntheticGlobal extends SummaryComponent::SyntheticG
private class RequiredComponentStackForStartActivities extends RequiredSummaryComponentStack {
override predicate required(SummaryComponent head, SummaryComponentStack tail) {
- head = SummaryComponent::element() and
+ head = SummaryComponent::arrayElement() and
tail = SummaryComponentStack::argument(0)
}
}
From e5982f19fa8c2321d9bfc746333fdb0f716fdead Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 19 Oct 2022 11:05:40 -0400
Subject: [PATCH 061/465] minor updates
---
.../java/security/InsufficientKeySize.qll | 11 +-
.../CWE/CWE-326/InsufficientKeySize.qhelp | 31 +---
.../CWE/CWE-326/InsufficientKeySizeBad.java | 19 +-
.../CWE/CWE-326/InsufficientKeySizeGood.java | 16 --
.../CWE-326/InsufficientKeySizeTest.java | 3 +
.../security/CWE-326/SignatureTest.java | 167 +-----------------
6 files changed, 23 insertions(+), 224 deletions(-)
delete mode 100644 java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index e4bb8e0a646..41852e1aeda 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -61,7 +61,7 @@ private int getNodeIntValue(DataFlow::Node node) {
result = node.asExpr().(IntegerLiteral).getIntValue()
}
-/** Returns the key size from an EC algorithm curve name string */
+/** Returns the key size from an EC algorithm's curve name string */
bindingset[algorithm]
private int getEcKeySize(string algorithm) {
algorithm.matches("sec%") and // specification such as "secp256r1"
@@ -145,8 +145,9 @@ private class SymmetricInitMethodAccess extends KeyGenInitMethodAccess {
SymmetricInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
}
-/** An instance of a key generator. */
-abstract private class KeyGeneratorObject extends CryptoAlgoSpec {
+/** An instance of a generator that specifies an encryption algorithm. */
+abstract private class AlgoGeneratorObject extends CryptoAlgoSpec {
+ /** Returns an uppercase string representing the algorithm name specified by this generator object. */
string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
}
@@ -154,7 +155,7 @@ abstract private class KeyGeneratorObject extends CryptoAlgoSpec {
* An instance of a `java.security.KeyPairGenerator`
* or of a `java.security.AlgorithmParameterGenerator`.
*/
-private class AsymmetricKeyGenerator extends KeyGeneratorObject {
+private class AsymmetricKeyGenerator extends AlgoGeneratorObject {
AsymmetricKeyGenerator() {
this instanceof JavaSecurityKeyPairGenerator or
this instanceof JavaSecurityAlgoParamGenerator
@@ -164,7 +165,7 @@ private class AsymmetricKeyGenerator extends KeyGeneratorObject {
}
/** An instance of a `javax.crypto.KeyGenerator`. */
-private class SymmetricKeyGenerator extends KeyGeneratorObject {
+private class SymmetricKeyGenerator extends AlgoGeneratorObject {
SymmetricKeyGenerator() { this instanceof JavaxCryptoKeyGenerator }
override Expr getAlgoSpec() { result = this.getAlgoSpec() }
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
index 47ef1124624..3a0b74bebd4 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.qhelp
@@ -6,20 +6,19 @@
Modern encryption relies on the computational infeasibility of breaking a cipher and decoding its
message without the key. As computational power increases, the ability to break ciphers grows, and key
- sizes need to become larger as a result. Encryption algorithms that use too small of a key size are
+ sizes need to become larger as a result. Cryptographic algorithms that use too small of a key size are
vulnerable to brute force attacks, which can reveal sensitive data.
- Use a key of the recommended size or larger. The key size should be at least 2048 bits for RSA or
- DSA encryption, 256 bits for elliptic curve (EC) encryption, and 128 bits for symmetric encryption,
- such as AES.
+ Use a key of the recommended size or larger. The key size should be at least 128 bits for AES encryption,
+ 256 bits for elliptic-curve cryptography (ECC), and 2048 bits for RSA, DSA, or DH encryption.
- The following code uses encryption with insufficient key sizes.
+ The following code uses cryptographic algorithms with insufficient key sizes.
@@ -29,12 +28,6 @@
larger for each algorithm.
-
-
@@ -45,22 +38,6 @@
Wikipedia: Strong cryptography.
-
OWASP:
Cryptographic Storage Cheat Sheet.
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java
index 641543ca964..8393143e86c 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeBad.java
@@ -1,16 +1,15 @@
KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
- // BAD: Key size is less than 2048
- keyPairGen1.initialize(1024);
+ keyPairGen1.initialize(1024); // BAD: Key size is less than 2048
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DSA");
- // BAD: Key size is less than 2048
- keyPairGen2.initialize(1024);
+ keyPairGen2.initialize(1024); // BAD: Key size is less than 2048
- KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 256
- ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
- keyPairGen3.initialize(ecSpec1);
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DH");
+ keyPairGen3.initialize(1024); // BAD: Key size is less than 2048
+
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp112r1"); // BAD: Key size is less than 256
+ keyPairGen4.initialize(ecSpec);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
- // BAD: Key size is less than 128
- keyGen.init(64);
+ keyGen.init(64); // BAD: Key size is less than 128
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java
deleted file mode 100644
index 051f7dd2597..00000000000
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySizeGood.java
+++ /dev/null
@@ -1,16 +0,0 @@
- KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
- // GOOD: Key size is no less than 2048
- keyPairGen1.initialize(2048);
-
- KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DSA");
- // GOOD: Key size is no less than 2048
- keyPairGen2.initialize(2048);
-
- KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 256
- ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
- keyPairGen3.initialize(ecSpec);
-
- KeyGenerator keyGen = KeyGenerator.getInstance("AES");
- // GOOD: Key size is no less than 128
- keyGen.init(128);
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index 746239a846f..e356f35d998 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -44,6 +44,7 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
keyPairGen2.initialize(2048); // Safe: Key size is no less than 2048
+ /* Test spec */
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("RSA");
RSAKeyGenParameterSpec rsaSpec = new RSAKeyGenParameterSpec(1024, null); // $ hasInsufficientKeySize
keyPairGen3.initialize(rsaSpec);
@@ -80,6 +81,7 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DSA");
keyPairGen2.initialize(2048); // Safe: Key size is no less than 2048
+ /* Test spec */
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
DSAGenParameterSpec dsaSpec = new DSAGenParameterSpec(1024, 0); // $ hasInsufficientKeySize
keyPairGen3.initialize(dsaSpec);
@@ -101,6 +103,7 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("DH");
keyPairGen2.initialize(2048); // Safe: Key size is no less than 2048
+ /* Test spec */
KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DH");
DHGenParameterSpec dhSpec = new DHGenParameterSpec(1024, 0); // $ hasInsufficientKeySize
keyPairGen3.initialize(dhSpec);
diff --git a/java/ql/test/query-tests/security/CWE-326/SignatureTest.java b/java/ql/test/query-tests/security/CWE-326/SignatureTest.java
index 016a62a41f8..d7942581590 100644
--- a/java/ql/test/query-tests/security/CWE-326/SignatureTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/SignatureTest.java
@@ -1,4 +1,4 @@
-//package org.bouncycastle.jce.provider.test;
+/* Adds tests to check for FPs related to RSA/DSA versus EC */
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -6,191 +6,26 @@ import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
-// import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
-// import org.bouncycastle.jce.provider.BouncyCastleProvider;
-// import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
-// import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
-// import org.bouncycastle.util.encoders.Hex;
-// import org.bouncycastle.util.test.SimpleTest;
-
public class SignatureTest
- //extends SimpleTest
{
- // private static final byte[] DATA = Hex.decode("00000000deadbeefbeefdeadffffffff00000000");
-
- private void checkSig(KeyPair kp, String name)
- throws Exception
- {
- // Signature sig = Signature.getInstance(name, "BC");
-
- // sig.initSign(kp.getPrivate());
- // sig.update(DATA);
-
- // byte[] signature1 = sig.sign();
-
- // sig.update(DATA);
-
- // byte[] signature2 = sig.sign();
-
- // sig.initVerify(kp.getPublic());
-
- // sig.update(DATA);
- // if (!sig.verify(signature1))
- // {
- // fail("did not verify: " + name);
- // }
-
- // // After verify, should be reusable as if we are after initVerify
- // sig.update(DATA);
- // if (!sig.verify(signature1))
- // {
- // fail("second verify failed: " + name);
- // }
-
- // sig.update(DATA);
- // if (!sig.verify(signature2))
- // {
- // fail("second verify failed (2): " + name);
- // }
- }
public void performTest()
throws Exception
{
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
-
kpGen.initialize(2048); // Safe
-
KeyPair kp = kpGen.generateKeyPair();
- checkSig(kp, "SHA1withRSA");
- checkSig(kp, "SHA224withRSA");
- checkSig(kp, "SHA256withRSA");
- checkSig(kp, "SHA384withRSA");
- checkSig(kp, "SHA512withRSA");
-
- checkSig(kp, "SHA3-224withRSA");
- checkSig(kp, "SHA3-256withRSA");
- checkSig(kp, "SHA3-384withRSA");
- checkSig(kp, "SHA3-512withRSA");
-
- checkSig(kp, "MD2withRSA");
- checkSig(kp, "MD4withRSA");
- checkSig(kp, "MD5withRSA");
- checkSig(kp, "RIPEMD160withRSA");
- checkSig(kp, "RIPEMD128withRSA");
- checkSig(kp, "RIPEMD256withRSA");
-
- checkSig(kp, "SHA1withRSAandMGF1");
- checkSig(kp, "SHA1withRSAandMGF1");
- checkSig(kp, "SHA224withRSAandMGF1");
- checkSig(kp, "SHA256withRSAandMGF1");
- checkSig(kp, "SHA384withRSAandMGF1");
- checkSig(kp, "SHA512withRSAandMGF1");
-
- checkSig(kp, "SHA1withRSAandSHAKE128");
- checkSig(kp, "SHA1withRSAandSHAKE128");
- checkSig(kp, "SHA224withRSAandSHAKE128");
- checkSig(kp, "SHA256withRSAandSHAKE128");
- checkSig(kp, "SHA384withRSAandSHAKE128");
- checkSig(kp, "SHA512withRSAandSHAKE128");
-
- checkSig(kp, "SHA1withRSAandSHAKE256");
- checkSig(kp, "SHA1withRSAandSHAKE256");
- checkSig(kp, "SHA224withRSAandSHAKE256");
- checkSig(kp, "SHA256withRSAandSHAKE256");
- checkSig(kp, "SHA384withRSAandSHAKE256");
- checkSig(kp, "SHA512withRSAandSHAKE256");
-
- checkSig(kp, "SHAKE128withRSAPSS");
- checkSig(kp, "SHAKE256withRSAPSS");
-
- checkSig(kp, "SHA1withRSA/ISO9796-2");
- checkSig(kp, "MD5withRSA/ISO9796-2");
- checkSig(kp, "RIPEMD160withRSA/ISO9796-2");
-
-// checkSig(kp, "SHA1withRSA/ISO9796-2PSS");
-// checkSig(kp, "MD5withRSA/ISO9796-2PSS");
-// checkSig(kp, "RIPEMD160withRSA/ISO9796-2PSS");
-
- checkSig(kp, "RIPEMD128withRSA/X9.31");
- checkSig(kp, "RIPEMD160withRSA/X9.31");
- checkSig(kp, "SHA1withRSA/X9.31");
- checkSig(kp, "SHA224withRSA/X9.31");
- checkSig(kp, "SHA256withRSA/X9.31");
- checkSig(kp, "SHA384withRSA/X9.31");
- checkSig(kp, "SHA512withRSA/X9.31");
- checkSig(kp, "WhirlpoolwithRSA/X9.31");
-
kpGen = KeyPairGenerator.getInstance("DSA", "BC");
-
kpGen.initialize(2048); // Safe
-
kp = kpGen.generateKeyPair();
- checkSig(kp, "SHA1withDSA");
- checkSig(kp, "SHA224withDSA");
- checkSig(kp, "SHA256withDSA");
- checkSig(kp, "SHA384withDSA");
- checkSig(kp, "SHA512withDSA");
- checkSig(kp, "NONEwithDSA");
-
kpGen = KeyPairGenerator.getInstance("EC", "BC");
-
kpGen.initialize(256); // Safe
-
kp = kpGen.generateKeyPair();
- checkSig(kp, "SHA1withECDSA");
- checkSig(kp, "SHA224withECDSA");
- checkSig(kp, "SHA256withECDSA");
- checkSig(kp, "SHA384withECDSA");
- checkSig(kp, "SHA512withECDSA");
- checkSig(kp, "RIPEMD160withECDSA");
- checkSig(kp, "SHAKE128withECDSA");
- checkSig(kp, "SHAKE256withECDSA");
-
kpGen = KeyPairGenerator.getInstance("EC", "BC");
-
kpGen.initialize(521); // Safe
-
kp = kpGen.generateKeyPair();
-
- checkSig(kp, "SHA1withECNR");
- checkSig(kp, "SHA224withECNR");
- checkSig(kp, "SHA256withECNR");
- checkSig(kp, "SHA384withECNR");
- checkSig(kp, "SHA512withECNR");
-
- // kpGen = KeyPairGenerator.getInstance("ECGOST3410", "BC");
-
- // kpGen.initialize(new ECNamedCurveGenParameterSpec("GostR3410-2001-CryptoPro-A"), new SecureRandom());
-
- // kp = kpGen.generateKeyPair();
-
- // checkSig(kp, "GOST3411withECGOST3410");
-
- // kpGen = KeyPairGenerator.getInstance("GOST3410", "BC");
-
- // GOST3410ParameterSpec gost3410P = new GOST3410ParameterSpec(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_A.getId());
-
- // kpGen.initialize(gost3410P);
-
- // kp = kpGen.generateKeyPair();
-
- // checkSig(kp, "GOST3411withGOST3410");
}
-
- public String getName()
- {
- return "SigNameTest";
- }
-
- // public static void main(
- // String[] args)
- // {
- // //Security.addProvider(new BouncyCastleProvider());
-
- // //runTest(new SignatureTest());
- // }
}
From b7f360647eca7790999e7bf04842974d9602acd5 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 19 Oct 2022 11:37:42 -0400
Subject: [PATCH 062/465] rename change note
---
...sufficient-key-size.md => 2022-10-19-insufficient-key-size.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename java/ql/src/change-notes/{2022-10-03-insufficient-key-size.md => 2022-10-19-insufficient-key-size.md} (100%)
diff --git a/java/ql/src/change-notes/2022-10-03-insufficient-key-size.md b/java/ql/src/change-notes/2022-10-19-insufficient-key-size.md
similarity index 100%
rename from java/ql/src/change-notes/2022-10-03-insufficient-key-size.md
rename to java/ql/src/change-notes/2022-10-19-insufficient-key-size.md
From 345e4e0e8f54453082bcb95b712e483526657965 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 20 Oct 2022 23:52:31 -0400
Subject: [PATCH 063/465] remove unnecessary 'exists'
---
java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
index 23efca5483c..2cf3d9115b3 100644
--- a/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -15,8 +15,8 @@ import java
import semmle.code.java.security.InsufficientKeySizeQuery
import DataFlow::PathGraph
-from DataFlow::PathNode source, DataFlow::PathNode sink
-where exists(KeySizeConfiguration cfg | cfg.hasFlowPath(source, sink))
+from DataFlow::PathNode source, DataFlow::PathNode sink, KeySizeConfiguration cfg
+where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"This $@ is less than the recommended key size of " + source.getState() + " bits.",
source.getNode(), "key size"
From aab1e1f5b441a508a5da1a321c846ee15623bce4 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 12 Oct 2022 11:48:31 +0200
Subject: [PATCH 064/465] Ruby: add some helpers at the AST level
---
ruby/ql/lib/codeql/ruby/ast/Module.qll | 34 ++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll
index 5b0309ddb21..e13506d2eb9 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Module.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -63,6 +63,33 @@ class Module extends TModule {
loc.getStartColumn()
)
}
+
+ /** Gets a constant or `self` access that refers to this module. */
+ private Expr getAnImmediateReferenceBase() {
+ resolveConstantReadAccess(result) = this
+ or
+ result.(SelfVariableAccess).getVariable() = this.getADeclaration().getModuleSelfVariable()
+ }
+
+ /** Gets a singleton class that augments this module object. */
+ SingletonClass getASingletonClass() { result.getValue() = this.getAnImmediateReferenceBase() }
+
+ /**
+ * Gets a singleton method on this module, either declared as a singleton method
+ * or an instance method on a singleton class.
+ */
+ MethodBase getASingletonMethod() {
+ result.(SingletonMethod).getObject() = this.getAnImmediateReferenceBase()
+ or
+ result = this.getASingletonClass().getAMethod().(Method)
+ }
+
+ /** Gets a constant or `self` access that refers to this module. */
+ Expr getAnImmediateReference() {
+ result = this.getAnImmediateReferenceBase()
+ or
+ result.(SelfVariableAccess).getVariable().getDeclaringScope() = this.getASingletonMethod()
+ }
}
/**
@@ -141,6 +168,13 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
/** Gets the representation of the run-time value of this module or class. */
Module getModule() { none() }
+
+ /**
+ * Gets the `self` variable in the module-level scope.
+ *
+ * Does not include the `self` variable from any of the methods in the module.
+ */
+ SelfVariable getModuleSelfVariable() { result.getDeclaringScope() = this }
}
/**
From 65add15416651e375ab71c7cc75a540124a371c7 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 12 Oct 2022 11:48:46 +0200
Subject: [PATCH 065/465] Ruby: add getALocalUse()
This is the inverse of getALocalSource()
---
.../lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 7b56f2e6a93..25e23d8e0c4 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -174,6 +174,13 @@ class LocalSourceNode extends Node {
*/
pragma[inline]
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
+
+ /**
+ * Gets a node to which data may flow from this node in zero or
+ * more local data-flow steps.
+ */
+ pragma[inline]
+ Node getALocalUse() { hasLocalSource(result, this) }
}
/**
From ac4cac889f42109efceb8210582761b1d6667c96 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 12 Oct 2022 11:02:34 +0200
Subject: [PATCH 066/465] Ruby: add DataFlow::ModuleNode
sdf
---
.../ruby/dataflow/internal/DataFlowPublic.qll | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 25e23d8e0c4..6d63397ad23 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -566,3 +566,46 @@ abstract deprecated class BarrierGuard extends CfgNodes::ExprCfgNode {
result.asExpr() = this.getAMaybeGuardedCapturedDef().getARead()
}
}
+
+/**
+ * A representation of a run-time module or class.
+ *
+ * This is equivalent to the type `Ast::Module` but provides data-flow specific methods.
+ */
+class ModuleNode instanceof Module {
+ /** Gets a declaration of this module, if any. */
+ final ModuleBase getADeclaration() { result = super.getADeclaration() }
+
+ /** Gets the super class of this module, if any. */
+ final ModuleNode getSuperClass() { result = super.getSuperClass() }
+
+ /** Gets an immediate sub class of this module, if any. */
+ final ModuleNode getASubClass() { result = super.getASubClass() }
+
+ /** Gets a `prepend`ed module. */
+ final ModuleNode getAPrependedModule() { result = super.getAPrependedModule() }
+
+ /** Gets an `include`d module. */
+ final ModuleNode getAnIncludedModule() { result = super.getAnIncludedModule() }
+
+ /** Holds if this module is a class. */
+ predicate isClass() { super.isClass() }
+
+ /** Gets a textual representation of this module. */
+ final string toString() { result = super.toString() }
+
+ /**
+ * Gets the qualified name of this module, if any.
+ *
+ * Only modules that can be resolved will have a qualified name.
+ */
+ final string getQualifiedName() { result = super.getQualifiedName() }
+
+ /** Gets the location of this module. */
+ final Location getLocation() { result = super.getLocation() }
+
+ /** Gets a constant or `self` variable that refers to this module. */
+ LocalSourceNode getAnImmediateReference() {
+ result.asExpr().getExpr() = super.getAnImmediateReference()
+ }
+}
From 4c8e0a7648a6cb2ffbce812a65ae92887442d98e Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 15:05:05 -0400
Subject: [PATCH 067/465] update qldoc of JavaSecurityKeyPairGenerator and
JavaSecurityAlgoParamGenerator
---
java/ql/lib/semmle/code/java/security/Encryption.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 208343da0cc..12c1a9a1c58 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -383,7 +383,7 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
-/** An instance of a `java.security.KeyPairGenerator`. */
+/** A call to the `getInstance` method declared in `java.security.KeyPairGenerator`. */
class JavaSecurityKeyPairGenerator extends JavaSecurityAlgoSpec {
JavaSecurityKeyPairGenerator() {
exists(Method m | m.getAReference() = this |
@@ -410,7 +410,7 @@ class AlgoParamGeneratorInitMethod extends Method {
}
}
-/** An instance of a `java.security.AlgorithmParameterGenerator`. */
+/** A call to the `getInstance` method declared in `java.security.AlgorithmParameterGenerator`. */
class JavaSecurityAlgoParamGenerator extends JavaSecurityAlgoSpec {
JavaSecurityAlgoParamGenerator() {
exists(Method m | m.getAReference() = this |
From 2ee23f004e0ee0c7c962e799dc4f548866b4eb64 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 15:22:33 -0400
Subject: [PATCH 068/465] update qldoc for AlgorithmParameterSpec
---
java/ql/lib/semmle/code/java/security/Encryption.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/lib/semmle/code/java/security/Encryption.qll b/java/ql/lib/semmle/code/java/security/Encryption.qll
index 12c1a9a1c58..042018d3e34 100644
--- a/java/ql/lib/semmle/code/java/security/Encryption.qll
+++ b/java/ql/lib/semmle/code/java/security/Encryption.qll
@@ -422,7 +422,7 @@ class JavaSecurityAlgoParamGenerator extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
}
-/** The Java interface `java.security.spec.AlgorithmParameterSpec` */
+/** An implementation of the `java.security.spec.AlgorithmParameterSpec` interface. */
abstract class AlgorithmParameterSpec extends RefType { }
/** The Java class `java.security.spec.ECGenParameterSpec`. */
From eb69b98dffd95802ef451ff3e93fb4ce9bc50a20 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 15:28:31 -0400
Subject: [PATCH 069/465] remove separators
---
java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll | 4 ----
1 file changed, 4 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 41852e1aeda..823b95c31af 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -15,7 +15,6 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
}
-// *********************************** SOURCES ***********************************
/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
AsymmetricNonEcSource() { getNodeIntValue(this) < getMinAsymNonEcKeySize() }
@@ -46,7 +45,6 @@ private class SymmetricSource extends InsufficientKeySizeSource {
override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
-// ************************** SOURCES HELPER PREDICATES **************************
/** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
private int getMinAsymNonEcKeySize() { result = 2048 }
@@ -74,7 +72,6 @@ private int getEcKeySize(string algorithm) {
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
-// ************************************ SINKS ************************************
/** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
AsymmetricNonEcSink() {
@@ -122,7 +119,6 @@ private class SymmetricSink extends InsufficientKeySizeSink {
override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
-// ********************** SINKS HELPER CLASSES & PREDICATES **********************
/** A call to a method that initializes a key generator. */
abstract private class KeyGenInitMethodAccess extends MethodAccess {
/** Gets the `keysize` argument of this call. */
From 8bc0a648637a91bd3c9a0e09516fb95eeba8a18c Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 15:42:36 -0400
Subject: [PATCH 070/465] remove KeyGenInitMethodAccess class
---
.../code/java/security/InsufficientKeySize.qll | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 823b95c31af..e3e3c53dab0 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -119,26 +119,26 @@ private class SymmetricSink extends InsufficientKeySizeSink {
override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
-/** A call to a method that initializes a key generator. */
-abstract private class KeyGenInitMethodAccess extends MethodAccess {
- /** Gets the `keysize` argument of this call. */
- Argument getKeySizeArg() { result = this.getArgument(0) }
-}
-
/**
* A call to the `initialize` method declared in `java.security.KeyPairGenerator`
* or to the `init` method declared in `java.security.AlgorithmParameterGenerator`.
*/
-private class AsymmetricInitMethodAccess extends KeyGenInitMethodAccess {
+private class AsymmetricInitMethodAccess extends MethodAccess {
AsymmetricInitMethodAccess() {
this.getMethod() instanceof KeyPairGeneratorInitMethod or
this.getMethod() instanceof AlgoParamGeneratorInitMethod
}
+
+ /** Gets the `keysize` argument of this call. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
-private class SymmetricInitMethodAccess extends KeyGenInitMethodAccess {
+private class SymmetricInitMethodAccess extends MethodAccess {
SymmetricInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
+
+ /** Gets the `keysize` argument of this call. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
/** An instance of a generator that specifies an encryption algorithm. */
From 09829d7f7ae1070dcce6dae992de7b17997e5862 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 15:49:41 -0400
Subject: [PATCH 071/465] simplify instanceof usage
---
java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index e3e3c53dab0..222e4c73f07 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -161,9 +161,7 @@ private class AsymmetricKeyGenerator extends AlgoGeneratorObject {
}
/** An instance of a `javax.crypto.KeyGenerator`. */
-private class SymmetricKeyGenerator extends AlgoGeneratorObject {
- SymmetricKeyGenerator() { this instanceof JavaxCryptoKeyGenerator }
-
+private class SymmetricKeyGenerator extends AlgoGeneratorObject instanceof JavaxCryptoKeyGenerator {
override Expr getAlgoSpec() { result = this.getAlgoSpec() }
}
From d569f93e7846d3b21caae6f71463971dfb8236ca Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 16:05:57 -0400
Subject: [PATCH 072/465] update getAlgoSpec
---
.../semmle/code/java/security/InsufficientKeySize.qll | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 222e4c73f07..3e304ad14ce 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -157,12 +157,18 @@ private class AsymmetricKeyGenerator extends AlgoGeneratorObject {
this instanceof JavaSecurityAlgoParamGenerator
}
- override Expr getAlgoSpec() { result = this.getAlgoSpec() }
+ override Expr getAlgoSpec() {
+ result =
+ [
+ this.(JavaSecurityKeyPairGenerator).getAlgoSpec(),
+ this.(JavaSecurityAlgoParamGenerator).getAlgoSpec()
+ ]
+ }
}
/** An instance of a `javax.crypto.KeyGenerator`. */
private class SymmetricKeyGenerator extends AlgoGeneratorObject instanceof JavaxCryptoKeyGenerator {
- override Expr getAlgoSpec() { result = this.getAlgoSpec() }
+ override Expr getAlgoSpec() { result = JavaxCryptoKeyGenerator.super.getAlgoSpec() }
}
/** An instance of an algorithm specification. */
From c742a09defcab8ef7bc4fee078ff11451a9777ed Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 16:15:18 -0400
Subject: [PATCH 073/465] remove AlgoSpec class
---
.../code/java/security/InsufficientKeySize.qll | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 3e304ad14ce..c7ed75a84f3 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -171,21 +171,22 @@ private class SymmetricKeyGenerator extends AlgoGeneratorObject instanceof Javax
override Expr getAlgoSpec() { result = JavaxCryptoKeyGenerator.super.getAlgoSpec() }
}
-/** An instance of an algorithm specification. */
-abstract private class AlgoSpec extends ClassInstanceExpr {
- Argument getKeySizeArg() { result = this.getArgument(0) }
-}
-
/** An instance of an RSA, DSA, or DH algorithm specification. */
-private class AsymmetricNonEcSpec extends AlgoSpec {
+private class AsymmetricNonEcSpec extends ClassInstanceExpr {
AsymmetricNonEcSpec() {
this.getConstructedType() instanceof RsaKeyGenParameterSpec or
this.getConstructedType() instanceof DsaGenParameterSpec or
this.getConstructedType() instanceof DhGenParameterSpec
}
+
+ /** Gets the `keysize` argument of this instance. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
/** An instance of an elliptic curve (EC) algorithm specification. */
-private class AsymmetricEcSpec extends AlgoSpec {
+private class AsymmetricEcSpec extends ClassInstanceExpr {
AsymmetricEcSpec() { this.getConstructedType() instanceof EcGenParameterSpec }
+
+ /** Gets the `keysize` argument of this instance. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
From 1a1245343deb643219b8914c590e78a57d1fdf36 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 17:09:24 -0400
Subject: [PATCH 074/465] remove getNodeIntValue
---
.../code/java/security/InsufficientKeySize.qll | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index c7ed75a84f3..b7bebf7da11 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -17,7 +17,9 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
- AsymmetricNonEcSource() { getNodeIntValue(this) < getMinAsymNonEcKeySize() }
+ AsymmetricNonEcSource() {
+ this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymNonEcKeySize()
+ }
override predicate hasState(DataFlow::FlowState state) {
state = getMinAsymNonEcKeySize().toString()
@@ -27,7 +29,7 @@ private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
/** A source for an insufficient key size used in elliptic curve (EC) algorithms. */
private class AsymmetricEcSource extends InsufficientKeySizeSource {
AsymmetricEcSource() {
- getNodeIntValue(this) < getMinAsymEcKeySize()
+ this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymEcKeySize()
or
// the below is needed for cases when the key size is embedded in the curve name
getEcKeySize(this.asExpr().(StringLiteral).getValue()) < getMinAsymEcKeySize()
@@ -40,7 +42,7 @@ private class AsymmetricEcSource extends InsufficientKeySizeSource {
/** A source for an insufficient key size used in AES algorithms. */
private class SymmetricSource extends InsufficientKeySizeSource {
- SymmetricSource() { getNodeIntValue(this) < getMinSymKeySize() }
+ SymmetricSource() { this.asExpr().(IntegerLiteral).getIntValue() < getMinSymKeySize() }
override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
@@ -54,11 +56,6 @@ private int getMinAsymEcKeySize() { result = 256 }
/** Returns the minimum recommended key size for AES algorithms. */
private int getMinSymKeySize() { result = 128 }
-/** Returns the integer value of a given DataFlow::Node. */
-private int getNodeIntValue(DataFlow::Node node) {
- result = node.asExpr().(IntegerLiteral).getIntValue()
-}
-
/** Returns the key size from an EC algorithm's curve name string */
bindingset[algorithm]
private int getEcKeySize(string algorithm) {
From 1e80fa118c08df3a13469354d154be8be7bccba9 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 25 Oct 2022 18:26:00 -0400
Subject: [PATCH 075/465] add modules
---
.../java/security/InsufficientKeySize.qll | 310 +++++++++---------
1 file changed, 159 insertions(+), 151 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index b7bebf7da11..46124cd4128 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -15,175 +15,183 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
}
-/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
-private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
- AsymmetricNonEcSource() {
- this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymNonEcKeySize()
+private module Asymmetric {
+ private module NonEllipticCurve {
+ /** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
+ private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
+ AsymmetricNonEcSource() {
+ this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymNonEcKeySize()
+ }
+
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymNonEcKeySize().toString()
+ }
+ }
+
+ /** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
+ private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
+ AsymmetricNonEcSink() {
+ exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
+ kg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
+ DataFlow::localExprFlow(kg, ma.getQualifier()) and
+ this.asExpr() = ma.getKeySizeArg()
+ )
+ or
+ exists(AsymmetricNonEcSpec spec | this.asExpr() = spec.getKeySizeArg())
+ }
+
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymNonEcKeySize().toString()
+ }
+ }
+
+ /** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
+ private int getMinAsymNonEcKeySize() { result = 2048 }
+
+ /** An instance of an RSA, DSA, or DH algorithm specification. */
+ private class AsymmetricNonEcSpec extends ClassInstanceExpr {
+ AsymmetricNonEcSpec() {
+ this.getConstructedType() instanceof RsaKeyGenParameterSpec or
+ this.getConstructedType() instanceof DsaGenParameterSpec or
+ this.getConstructedType() instanceof DhGenParameterSpec
+ }
+
+ /** Gets the `keysize` argument of this instance. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
+ }
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymNonEcKeySize().toString()
+ private module EllipticCurve {
+ /** A source for an insufficient key size used in elliptic curve (EC) algorithms. */
+ private class AsymmetricEcSource extends InsufficientKeySizeSource {
+ AsymmetricEcSource() {
+ this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymEcKeySize()
+ or
+ // the below is needed for cases when the key size is embedded in the curve name
+ getEcKeySize(this.asExpr().(StringLiteral).getValue()) < getMinAsymEcKeySize()
+ }
+
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymEcKeySize().toString()
+ }
+ }
+
+ /** A sink for an insufficient key size used in elliptic curve (EC) algorithms. */
+ private class AsymmetricEcSink extends InsufficientKeySizeSink {
+ AsymmetricEcSink() {
+ exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
+ kg.getAlgoName().matches("EC%") and
+ DataFlow::localExprFlow(kg, ma.getQualifier()) and
+ this.asExpr() = ma.getKeySizeArg()
+ )
+ or
+ exists(AsymmetricEcSpec s | this.asExpr() = s.getKeySizeArg())
+ }
+
+ override predicate hasState(DataFlow::FlowState state) {
+ state = getMinAsymEcKeySize().toString()
+ }
+ }
+
+ /** Returns the minimum recommended key size for elliptic curve (EC) algorithms. */
+ private int getMinAsymEcKeySize() { result = 256 }
+
+ /** Returns the key size from an EC algorithm's curve name string */
+ bindingset[algorithm]
+ private int getEcKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ }
+
+ /** An instance of an elliptic curve (EC) algorithm specification. */
+ private class AsymmetricEcSpec extends ClassInstanceExpr {
+ AsymmetricEcSpec() { this.getConstructedType() instanceof EcGenParameterSpec }
+
+ /** Gets the `keysize` argument of this instance. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
+ }
+ }
+
+ /**
+ * A call to the `initialize` method declared in `java.security.KeyPairGenerator`
+ * or to the `init` method declared in `java.security.AlgorithmParameterGenerator`.
+ */
+ private class AsymmetricInitMethodAccess extends MethodAccess {
+ AsymmetricInitMethodAccess() {
+ this.getMethod() instanceof KeyPairGeneratorInitMethod or
+ this.getMethod() instanceof AlgoParamGeneratorInitMethod
+ }
+
+ /** Gets the `keysize` argument of this call. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
+ }
+
+ /**
+ * An instance of a `java.security.KeyPairGenerator`
+ * or of a `java.security.AlgorithmParameterGenerator`.
+ */
+ private class AsymmetricKeyGenerator extends AlgoGeneratorObject {
+ AsymmetricKeyGenerator() {
+ this instanceof JavaSecurityKeyPairGenerator or
+ this instanceof JavaSecurityAlgoParamGenerator
+ }
+
+ override Expr getAlgoSpec() {
+ result =
+ [
+ this.(JavaSecurityKeyPairGenerator).getAlgoSpec(),
+ this.(JavaSecurityAlgoParamGenerator).getAlgoSpec()
+ ]
+ }
}
}
-/** A source for an insufficient key size used in elliptic curve (EC) algorithms. */
-private class AsymmetricEcSource extends InsufficientKeySizeSource {
- AsymmetricEcSource() {
- this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymEcKeySize()
- or
- // the below is needed for cases when the key size is embedded in the curve name
- getEcKeySize(this.asExpr().(StringLiteral).getValue()) < getMinAsymEcKeySize()
+private module Symmetric {
+ /** A source for an insufficient key size used in AES algorithms. */
+ private class SymmetricSource extends InsufficientKeySizeSource {
+ SymmetricSource() { this.asExpr().(IntegerLiteral).getIntValue() < getMinSymKeySize() }
+
+ override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymEcKeySize().toString()
- }
-}
+ /** A sink for an insufficient key size used in AES algorithms. */
+ private class SymmetricSink extends InsufficientKeySizeSink {
+ SymmetricSink() {
+ exists(SymmetricInitMethodAccess ma, SymmetricKeyGenerator kg |
+ kg.getAlgoName() = "AES" and
+ DataFlow::localExprFlow(kg, ma.getQualifier()) and
+ this.asExpr() = ma.getKeySizeArg()
+ )
+ }
-/** A source for an insufficient key size used in AES algorithms. */
-private class SymmetricSource extends InsufficientKeySizeSource {
- SymmetricSource() { this.asExpr().(IntegerLiteral).getIntValue() < getMinSymKeySize() }
-
- override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
-}
-
-/** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
-private int getMinAsymNonEcKeySize() { result = 2048 }
-
-/** Returns the minimum recommended key size for elliptic curve (EC) algorithms. */
-private int getMinAsymEcKeySize() { result = 256 }
-
-/** Returns the minimum recommended key size for AES algorithms. */
-private int getMinSymKeySize() { result = 128 }
-
-/** Returns the key size from an EC algorithm's curve name string */
-bindingset[algorithm]
-private int getEcKeySize(string algorithm) {
- algorithm.matches("sec%") and // specification such as "secp256r1"
- result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
- or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
- or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
- result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
-}
-
-/** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
-private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
- AsymmetricNonEcSink() {
- exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
- kg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
- DataFlow::localExprFlow(kg, ma.getQualifier()) and
- this.asExpr() = ma.getKeySizeArg()
- )
- or
- exists(AsymmetricNonEcSpec spec | this.asExpr() = spec.getKeySizeArg())
+ override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymNonEcKeySize().toString()
- }
-}
+ /** Returns the minimum recommended key size for AES algorithms. */
+ private int getMinSymKeySize() { result = 128 }
-/** A sink for an insufficient key size used in elliptic curve (EC) algorithms. */
-private class AsymmetricEcSink extends InsufficientKeySizeSink {
- AsymmetricEcSink() {
- exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
- kg.getAlgoName().matches("EC%") and
- DataFlow::localExprFlow(kg, ma.getQualifier()) and
- this.asExpr() = ma.getKeySizeArg()
- )
- or
- exists(AsymmetricEcSpec s | this.asExpr() = s.getKeySizeArg())
+ /** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
+ private class SymmetricInitMethodAccess extends MethodAccess {
+ SymmetricInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
+
+ /** Gets the `keysize` argument of this call. */
+ Argument getKeySizeArg() { result = this.getArgument(0) }
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymEcKeySize().toString()
+ /** An instance of a `javax.crypto.KeyGenerator`. */
+ private class SymmetricKeyGenerator extends AlgoGeneratorObject instanceof JavaxCryptoKeyGenerator {
+ override Expr getAlgoSpec() { result = JavaxCryptoKeyGenerator.super.getAlgoSpec() }
}
}
-/** A sink for an insufficient key size used in AES algorithms. */
-private class SymmetricSink extends InsufficientKeySizeSink {
- SymmetricSink() {
- exists(SymmetricInitMethodAccess ma, SymmetricKeyGenerator kg |
- kg.getAlgoName() = "AES" and
- DataFlow::localExprFlow(kg, ma.getQualifier()) and
- this.asExpr() = ma.getKeySizeArg()
- )
- }
-
- override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
-}
-
-/**
- * A call to the `initialize` method declared in `java.security.KeyPairGenerator`
- * or to the `init` method declared in `java.security.AlgorithmParameterGenerator`.
- */
-private class AsymmetricInitMethodAccess extends MethodAccess {
- AsymmetricInitMethodAccess() {
- this.getMethod() instanceof KeyPairGeneratorInitMethod or
- this.getMethod() instanceof AlgoParamGeneratorInitMethod
- }
-
- /** Gets the `keysize` argument of this call. */
- Argument getKeySizeArg() { result = this.getArgument(0) }
-}
-
-/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
-private class SymmetricInitMethodAccess extends MethodAccess {
- SymmetricInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
-
- /** Gets the `keysize` argument of this call. */
- Argument getKeySizeArg() { result = this.getArgument(0) }
-}
-
/** An instance of a generator that specifies an encryption algorithm. */
abstract private class AlgoGeneratorObject extends CryptoAlgoSpec {
/** Returns an uppercase string representing the algorithm name specified by this generator object. */
string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
}
-
-/**
- * An instance of a `java.security.KeyPairGenerator`
- * or of a `java.security.AlgorithmParameterGenerator`.
- */
-private class AsymmetricKeyGenerator extends AlgoGeneratorObject {
- AsymmetricKeyGenerator() {
- this instanceof JavaSecurityKeyPairGenerator or
- this instanceof JavaSecurityAlgoParamGenerator
- }
-
- override Expr getAlgoSpec() {
- result =
- [
- this.(JavaSecurityKeyPairGenerator).getAlgoSpec(),
- this.(JavaSecurityAlgoParamGenerator).getAlgoSpec()
- ]
- }
-}
-
-/** An instance of a `javax.crypto.KeyGenerator`. */
-private class SymmetricKeyGenerator extends AlgoGeneratorObject instanceof JavaxCryptoKeyGenerator {
- override Expr getAlgoSpec() { result = JavaxCryptoKeyGenerator.super.getAlgoSpec() }
-}
-
-/** An instance of an RSA, DSA, or DH algorithm specification. */
-private class AsymmetricNonEcSpec extends ClassInstanceExpr {
- AsymmetricNonEcSpec() {
- this.getConstructedType() instanceof RsaKeyGenParameterSpec or
- this.getConstructedType() instanceof DsaGenParameterSpec or
- this.getConstructedType() instanceof DhGenParameterSpec
- }
-
- /** Gets the `keysize` argument of this instance. */
- Argument getKeySizeArg() { result = this.getArgument(0) }
-}
-
-/** An instance of an elliptic curve (EC) algorithm specification. */
-private class AsymmetricEcSpec extends ClassInstanceExpr {
- AsymmetricEcSpec() { this.getConstructedType() instanceof EcGenParameterSpec }
-
- /** Gets the `keysize` argument of this instance. */
- Argument getKeySizeArg() { result = this.getArgument(0) }
-}
From 420c35d4a2bbf5ee628a351e71292c1ae1486ec1 Mon Sep 17 00:00:00 2001
From: Karim Ali
Date: Wed, 26 Oct 2022 15:32:59 +0200
Subject: [PATCH 076/465] add a query that detects the use of constant salts
---
.../Security/CWE-760/ConstantSalt.qhelp | 21 ++++++
.../queries/Security/CWE-760/ConstantSalt.ql | 63 +++++++++++++++++
.../Security/CWE-760/ConstantSalt.swift | 22 ++++++
.../Security/CWE-760/ConstantSalt.expected | 17 +++++
.../Security/CWE-760/ConstantSalt.qlref | 1 +
.../query-tests/Security/CWE-760/test.swift | 70 +++++++++++++++++++
6 files changed, 194 insertions(+)
create mode 100644 swift/ql/src/queries/Security/CWE-760/ConstantSalt.qhelp
create mode 100644 swift/ql/src/queries/Security/CWE-760/ConstantSalt.ql
create mode 100644 swift/ql/src/queries/Security/CWE-760/ConstantSalt.swift
create mode 100644 swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.expected
create mode 100644 swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.qlref
create mode 100644 swift/ql/test/query-tests/Security/CWE-760/test.swift
diff --git a/swift/ql/src/queries/Security/CWE-760/ConstantSalt.qhelp b/swift/ql/src/queries/Security/CWE-760/ConstantSalt.qhelp
new file mode 100644
index 00000000000..ca8d65d9982
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-760/ConstantSalt.qhelp
@@ -0,0 +1,21 @@
+
+
+
+ Constant salts should not be used for password hashing. Data hashed using constant salts are vulnerable to dictionary attacks, enabling attackers to recover the original input.
+
+
+
+ Use randomly generated salts to securely hash input data.
+
+
+
+ The following example shows a few cases of hashing input data. In the 'BAD' cases, the salt is constant, making the generated hashes vulnerable to dictionary attakcs. In the 'GOOD' cases, the salt is randomly generated, which protects the hashed data against recovery.
+
+
+
+
+ What are Salted Passwords and Password Hashing?
+
+
diff --git a/swift/ql/src/queries/Security/CWE-760/ConstantSalt.ql b/swift/ql/src/queries/Security/CWE-760/ConstantSalt.ql
new file mode 100644
index 00000000000..32bc1325147
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-760/ConstantSalt.ql
@@ -0,0 +1,63 @@
+/**
+ * @name Constant salt
+ * @description Using constant salts for password hashing is not secure, because potential attackers can pre-compute the hash value via dictionary attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @id swift/constant-salt
+ * @tags security
+ * external/cwe/cwe-760
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.dataflow.TaintTracking
+import codeql.swift.dataflow.FlowSteps
+import DataFlow::PathGraph
+
+/**
+ * A constant salt is created through either a byte array or string literals.
+ */
+class ConstantSaltSource extends Expr {
+ ConstantSaltSource() {
+ this = any(ArrayExpr arr | arr.getType().getName() = "Array") or
+ this instanceof StringLiteralExpr
+ }
+}
+
+/**
+ * A class for all ways to use a constant salt.
+ */
+class ConstantSaltSink extends Expr {
+ ConstantSaltSink() {
+ // `salt` arg in `init` is a sink
+ exists(ClassOrStructDecl c, AbstractFunctionDecl f, CallExpr call, int arg |
+ c.getFullName() = ["HKDF", "PBKDF1", "PBKDF2", "Scrypt"] and
+ c.getAMember() = f and
+ f.getName().matches("%init(%salt:%") and
+ call.getStaticTarget() = f and
+ f.getParam(pragma[only_bind_into](arg)).getName() = "salt" and
+ call.getArgument(pragma[only_bind_into](arg)).getExpr() = this
+ )
+ }
+}
+
+/**
+ * A taint configuration from the source of constants salts to expressions that use
+ * them to initialize password-based enecryption keys.
+ */
+class ConstantSaltConfig extends TaintTracking::Configuration {
+ ConstantSaltConfig() { this = "ConstantSaltConfig" }
+
+ override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof ConstantSaltSource }
+
+ override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof ConstantSaltSink }
+}
+
+// The query itself
+from ConstantSaltConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
+where config.hasFlowPath(sourceNode, sinkNode)
+select sinkNode.getNode(), sourceNode, sinkNode,
+ "The value '" + sourceNode.getNode().toString() +
+ "' is used as a constant salt, which is insecure for hashing passwords."
diff --git a/swift/ql/src/queries/Security/CWE-760/ConstantSalt.swift b/swift/ql/src/queries/Security/CWE-760/ConstantSalt.swift
new file mode 100644
index 00000000000..a3bc11c826b
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-760/ConstantSalt.swift
@@ -0,0 +1,22 @@
+
+func encrypt(padding : Padding) {
+ // ...
+
+ // BAD: Using constant salts for hashing
+ let salt: Array = [0x2a, 0x3a, 0x80, 0x05]
+ let randomArray = (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
+ _ = try HKDF(password: randomArray, salt: salt, info: randomArray, keyLength: 0, variant: Variant.sha2)
+ _ = try PKCS5.PBKDF1(password: randomArray, salt: salt, iterations: 120120, keyLength: 0)
+ _ = try PKCS5.PBKDF2(password: randomArray, salt: salt, iterations: 120120, keyLength: 0)
+ _ = try Scrypt(password: randomArray, salt: salt, dkLen: 64, N: 16384, r: 8, p: 1)
+
+ // GOOD: Using randomly generated salts for hashing
+ let salt = (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
+ let randomArray = (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
+ _ = try HKDF(password: randomArray, salt: salt, info: randomArray, keyLength: 0, variant: Variant.sha2)
+ _ = try PKCS5.PBKDF1(password: randomArray, salt: salt, iterations: 120120, keyLength: 0)
+ _ = try PKCS5.PBKDF2(password: randomArray, salt: salt, iterations: 120120, keyLength: 0)
+ _ = try Scrypt(password: randomArray, salt: salt, dkLen: 64, N: 16384, r: 8, p: 1)
+
+ // ...
+}
diff --git a/swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.expected b/swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.expected
new file mode 100644
index 00000000000..6c02fd2c9b4
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.expected
@@ -0,0 +1,17 @@
+edges
+| test.swift:43:35:43:130 | [...] : | test.swift:51:49:51:49 | constantSalt |
+| test.swift:43:35:43:130 | [...] : | test.swift:56:59:56:59 | constantSalt |
+| test.swift:43:35:43:130 | [...] : | test.swift:62:59:62:59 | constantSalt |
+| test.swift:43:35:43:130 | [...] : | test.swift:67:53:67:53 | constantSalt |
+nodes
+| test.swift:43:35:43:130 | [...] : | semmle.label | [...] : |
+| test.swift:51:49:51:49 | constantSalt | semmle.label | constantSalt |
+| test.swift:56:59:56:59 | constantSalt | semmle.label | constantSalt |
+| test.swift:62:59:62:59 | constantSalt | semmle.label | constantSalt |
+| test.swift:67:53:67:53 | constantSalt | semmle.label | constantSalt |
+subpaths
+#select
+| test.swift:51:49:51:49 | constantSalt | test.swift:43:35:43:130 | [...] : | test.swift:51:49:51:49 | constantSalt | The value '[...]' is used as a constant salt, which is insecure for hashing passwords. |
+| test.swift:56:59:56:59 | constantSalt | test.swift:43:35:43:130 | [...] : | test.swift:56:59:56:59 | constantSalt | The value '[...]' is used as a constant salt, which is insecure for hashing passwords. |
+| test.swift:62:59:62:59 | constantSalt | test.swift:43:35:43:130 | [...] : | test.swift:62:59:62:59 | constantSalt | The value '[...]' is used as a constant salt, which is insecure for hashing passwords. |
+| test.swift:67:53:67:53 | constantSalt | test.swift:43:35:43:130 | [...] : | test.swift:67:53:67:53 | constantSalt | The value '[...]' is used as a constant salt, which is insecure for hashing passwords. |
\ No newline at end of file
diff --git a/swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.qlref b/swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.qlref
new file mode 100644
index 00000000000..04aadc2161f
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-760/ConstantSalt.qlref
@@ -0,0 +1 @@
+queries/Security/CWE-760/ConstantSalt.ql
diff --git a/swift/ql/test/query-tests/Security/CWE-760/test.swift b/swift/ql/test/query-tests/Security/CWE-760/test.swift
new file mode 100644
index 00000000000..05f5396d244
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-760/test.swift
@@ -0,0 +1,70 @@
+
+// --- stubs ---
+
+// These stubs roughly follows the same structure as classes from CryptoSwift
+enum PKCS5 { }
+
+enum Variant { case md5, sha1, sha2, sha3 }
+
+extension PKCS5 {
+ struct PBKDF1 {
+ init(password: Array, salt: Array, variant: Variant = .sha1, iterations: Int = 4096, keyLength: Int? = nil) { }
+ }
+
+ struct PBKDF2 {
+ init(password: Array, salt: Array, iterations: Int = 4096, keyLength: Int? = nil, variant: Variant = .sha2) { }
+ }
+}
+
+struct HKDF {
+ init(password: Array, salt: Array? = nil, info: Array? = nil, keyLength: Int? = nil, variant: Variant = .sha2) { }
+}
+
+final class Scrypt {
+ init(password: Array, salt: Array, dkLen: Int, N: Int, r: Int, p: Int) { }
+}
+
+// Helper functions
+func getConstantString() -> String {
+ "this string is constant"
+}
+
+func getConstantArray() -> Array {
+ [UInt8](getConstantString().utf8)
+}
+
+func getRandomArray() -> Array {
+ (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
+}
+
+// --- tests ---
+
+func test() {
+ let constantSalt: Array = [0x2a, 0x3a, 0x80, 0x05, 0xaf, 0x46, 0x58, 0x2d, 0x66, 0x52, 0x10, 0xae, 0x86, 0xd3, 0x8e, 0x8f]
+ let constantStringSalt = getConstantArray()
+ let randomSalt = getRandomArray()
+ let randomArray = getRandomArray()
+ let variant = Variant.sha2
+ let iterations = 120120
+
+ // HKDF test cases
+ let hkdfb1 = HKDF(password: randomArray, salt: constantSalt, info: randomArray, keyLength: 0, variant: variant) // BAD
+ let hkdfb2 = HKDF(password: randomArray, salt: constantStringSalt, info: randomArray, keyLength: 0, variant: variant) // BAD [NOT DETECTED]
+ let hkdfg1 = HKDF(password: randomArray, salt: randomSalt, info: randomArray, keyLength: 0, variant: variant) // GOOD
+
+ // PBKDF1 test cases
+ let pbkdf1b1 = PKCS5.PBKDF1(password: randomArray, salt: constantSalt, iterations: iterations, keyLength: 0) // BAD
+ let pbkdf1b2 = PKCS5.PBKDF1(password: randomArray, salt: constantStringSalt, iterations: iterations, keyLength: 0) // BAD [NOT DETECTED]
+ let pbkdf1g1 = PKCS5.PBKDF1(password: randomArray, salt: randomSalt, iterations: iterations, keyLength: 0) // GOOD
+
+
+ // PBKDF2 test cases
+ let pbkdf2b1 = PKCS5.PBKDF2(password: randomArray, salt: constantSalt, iterations: iterations, keyLength: 0) // BAD
+ let pbkdf2b2 = PKCS5.PBKDF2(password: randomArray, salt: constantStringSalt, iterations: iterations, keyLength: 0) // BAD [NOT DETECTED]
+ let pbkdf2g1 = PKCS5.PBKDF2(password: randomArray, salt: randomSalt, iterations: iterations, keyLength: 0) // GOOD
+
+ // Scrypt test cases
+ let scryptb1 = Scrypt(password: randomArray, salt: constantSalt, dkLen: 64, N: 16384, r: 8, p: 1) // BAD
+ let scryptb2 = Scrypt(password: randomArray, salt: constantStringSalt, dkLen: 64, N: 16384, r: 8, p: 1) // BAD [NOT DETECTED]
+ let scryptg1 = Scrypt(password: randomArray, salt: randomSalt, dkLen: 64, N: 16384, r: 8, p: 1) // GOOD
+}
\ No newline at end of file
From 1bfdfc954bb00c6b6c160dbfdf35472556d49614 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Wed, 26 Oct 2022 16:30:14 -0400
Subject: [PATCH 077/465] shorten class/predicate names
---
.../java/security/InsufficientKeySize.qll | 112 +++++++++---------
1 file changed, 53 insertions(+), 59 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 46124cd4128..68190758d4f 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -15,42 +15,38 @@ abstract class InsufficientKeySizeSink extends DataFlow::Node {
predicate hasState(DataFlow::FlowState state) { state instanceof DataFlow::FlowStateEmpty }
}
+/** Provides models for asymmetric cryptography. */
private module Asymmetric {
+ /** Provides models for non-elliptic-curve asymmetric cryptography. */
private module NonEllipticCurve {
/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
- private class AsymmetricNonEcSource extends InsufficientKeySizeSource {
- AsymmetricNonEcSource() {
- this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymNonEcKeySize()
- }
+ private class Source extends InsufficientKeySizeSource {
+ Source() { this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize() }
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymNonEcKeySize().toString()
- }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
}
/** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
- private class AsymmetricNonEcSink extends InsufficientKeySizeSink {
- AsymmetricNonEcSink() {
- exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
- kg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
- DataFlow::localExprFlow(kg, ma.getQualifier()) and
- this.asExpr() = ma.getKeySizeArg()
+ private class Sink extends InsufficientKeySizeSink {
+ Sink() {
+ exists(KeyPairGenInit kpgInit, KeyPairGen kpg |
+ kpg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
+ DataFlow::localExprFlow(kpg, kpgInit.getQualifier()) and
+ this.asExpr() = kpgInit.getKeySizeArg()
)
or
- exists(AsymmetricNonEcSpec spec | this.asExpr() = spec.getKeySizeArg())
+ exists(Spec spec | this.asExpr() = spec.getKeySizeArg())
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymNonEcKeySize().toString()
- }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
}
/** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
- private int getMinAsymNonEcKeySize() { result = 2048 }
+ private int getMinKeySize() { result = 2048 }
/** An instance of an RSA, DSA, or DH algorithm specification. */
- private class AsymmetricNonEcSpec extends ClassInstanceExpr {
- AsymmetricNonEcSpec() {
+ private class Spec extends ClassInstanceExpr {
+ Spec() {
this.getConstructedType() instanceof RsaKeyGenParameterSpec or
this.getConstructedType() instanceof DsaGenParameterSpec or
this.getConstructedType() instanceof DhGenParameterSpec
@@ -61,44 +57,41 @@ private module Asymmetric {
}
}
+ /** Provides models for elliptic-curve asymmetric cryptography. */
private module EllipticCurve {
/** A source for an insufficient key size used in elliptic curve (EC) algorithms. */
- private class AsymmetricEcSource extends InsufficientKeySizeSource {
- AsymmetricEcSource() {
- this.asExpr().(IntegerLiteral).getIntValue() < getMinAsymEcKeySize()
+ private class Source extends InsufficientKeySizeSource {
+ Source() {
+ this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize()
or
// the below is needed for cases when the key size is embedded in the curve name
- getEcKeySize(this.asExpr().(StringLiteral).getValue()) < getMinAsymEcKeySize()
+ getKeySize(this.asExpr().(StringLiteral).getValue()) < getMinKeySize()
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymEcKeySize().toString()
- }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
}
/** A sink for an insufficient key size used in elliptic curve (EC) algorithms. */
- private class AsymmetricEcSink extends InsufficientKeySizeSink {
- AsymmetricEcSink() {
- exists(AsymmetricInitMethodAccess ma, AsymmetricKeyGenerator kg |
- kg.getAlgoName().matches("EC%") and
- DataFlow::localExprFlow(kg, ma.getQualifier()) and
- this.asExpr() = ma.getKeySizeArg()
+ private class Sink extends InsufficientKeySizeSink {
+ Sink() {
+ exists(KeyPairGenInit kpgInit, KeyPairGen kpg |
+ kpg.getAlgoName().matches("EC%") and
+ DataFlow::localExprFlow(kpg, kpgInit.getQualifier()) and
+ this.asExpr() = kpgInit.getKeySizeArg()
)
or
- exists(AsymmetricEcSpec s | this.asExpr() = s.getKeySizeArg())
+ exists(Spec s | this.asExpr() = s.getKeySizeArg())
}
- override predicate hasState(DataFlow::FlowState state) {
- state = getMinAsymEcKeySize().toString()
- }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
}
/** Returns the minimum recommended key size for elliptic curve (EC) algorithms. */
- private int getMinAsymEcKeySize() { result = 256 }
+ private int getMinKeySize() { result = 256 }
/** Returns the key size from an EC algorithm's curve name string */
bindingset[algorithm]
- private int getEcKeySize(string algorithm) {
+ private int getKeySize(string algorithm) {
algorithm.matches("sec%") and // specification such as "secp256r1"
result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
or
@@ -110,8 +103,8 @@ private module Asymmetric {
}
/** An instance of an elliptic curve (EC) algorithm specification. */
- private class AsymmetricEcSpec extends ClassInstanceExpr {
- AsymmetricEcSpec() { this.getConstructedType() instanceof EcGenParameterSpec }
+ private class Spec extends ClassInstanceExpr {
+ Spec() { this.getConstructedType() instanceof EcGenParameterSpec }
/** Gets the `keysize` argument of this instance. */
Argument getKeySizeArg() { result = this.getArgument(0) }
@@ -122,8 +115,8 @@ private module Asymmetric {
* A call to the `initialize` method declared in `java.security.KeyPairGenerator`
* or to the `init` method declared in `java.security.AlgorithmParameterGenerator`.
*/
- private class AsymmetricInitMethodAccess extends MethodAccess {
- AsymmetricInitMethodAccess() {
+ private class KeyPairGenInit extends MethodAccess {
+ KeyPairGenInit() {
this.getMethod() instanceof KeyPairGeneratorInitMethod or
this.getMethod() instanceof AlgoParamGeneratorInitMethod
}
@@ -136,8 +129,8 @@ private module Asymmetric {
* An instance of a `java.security.KeyPairGenerator`
* or of a `java.security.AlgorithmParameterGenerator`.
*/
- private class AsymmetricKeyGenerator extends AlgoGeneratorObject {
- AsymmetricKeyGenerator() {
+ private class KeyPairGen extends GeneratorAlgoSpec {
+ KeyPairGen() {
this instanceof JavaSecurityKeyPairGenerator or
this instanceof JavaSecurityAlgoParamGenerator
}
@@ -152,46 +145,47 @@ private module Asymmetric {
}
}
+/** Provides models for symmetric cryptography. */
private module Symmetric {
/** A source for an insufficient key size used in AES algorithms. */
- private class SymmetricSource extends InsufficientKeySizeSource {
- SymmetricSource() { this.asExpr().(IntegerLiteral).getIntValue() < getMinSymKeySize() }
+ private class Source extends InsufficientKeySizeSource {
+ Source() { this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize() }
- override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
}
/** A sink for an insufficient key size used in AES algorithms. */
- private class SymmetricSink extends InsufficientKeySizeSink {
- SymmetricSink() {
- exists(SymmetricInitMethodAccess ma, SymmetricKeyGenerator kg |
+ private class Sink extends InsufficientKeySizeSink {
+ Sink() {
+ exists(KeyGenInit kgInit, KeyGen kg |
kg.getAlgoName() = "AES" and
- DataFlow::localExprFlow(kg, ma.getQualifier()) and
- this.asExpr() = ma.getKeySizeArg()
+ DataFlow::localExprFlow(kg, kgInit.getQualifier()) and
+ this.asExpr() = kgInit.getKeySizeArg()
)
}
- override predicate hasState(DataFlow::FlowState state) { state = getMinSymKeySize().toString() }
+ override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
}
/** Returns the minimum recommended key size for AES algorithms. */
- private int getMinSymKeySize() { result = 128 }
+ private int getMinKeySize() { result = 128 }
/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
- private class SymmetricInitMethodAccess extends MethodAccess {
- SymmetricInitMethodAccess() { this.getMethod() instanceof KeyGeneratorInitMethod }
+ private class KeyGenInit extends MethodAccess {
+ KeyGenInit() { this.getMethod() instanceof KeyGeneratorInitMethod }
/** Gets the `keysize` argument of this call. */
Argument getKeySizeArg() { result = this.getArgument(0) }
}
/** An instance of a `javax.crypto.KeyGenerator`. */
- private class SymmetricKeyGenerator extends AlgoGeneratorObject instanceof JavaxCryptoKeyGenerator {
+ private class KeyGen extends GeneratorAlgoSpec instanceof JavaxCryptoKeyGenerator {
override Expr getAlgoSpec() { result = JavaxCryptoKeyGenerator.super.getAlgoSpec() }
}
}
/** An instance of a generator that specifies an encryption algorithm. */
-abstract private class AlgoGeneratorObject extends CryptoAlgoSpec {
+abstract private class GeneratorAlgoSpec extends CryptoAlgoSpec {
/** Returns an uppercase string representing the algorithm name specified by this generator object. */
string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
}
From 6f646be733495ad9c78e09e7ae1c55d1cae8580b Mon Sep 17 00:00:00 2001
From: Arthur Baars
Date: Tue, 18 Oct 2022 14:06:59 +0200
Subject: [PATCH 078/465] Ruby: document API graphs
---
.../codeql-for-ruby.rst | 3 +
.../using-api-graphs-in-ruby.rst | 183 ++++++++++++++++++
2 files changed, 186 insertions(+)
create mode 100644 docs/codeql/codeql-language-guides/using-api-graphs-in-ruby.rst
diff --git a/docs/codeql/codeql-language-guides/codeql-for-ruby.rst b/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
index bfb29a012ef..7066c108200 100644
--- a/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
+++ b/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
@@ -10,9 +10,12 @@ Experiment and learn how to write effective and efficient queries for CodeQL dat
basic-query-for-ruby-code
codeql-library-for-ruby
+ using-api-graphs-in-ruby
- :doc:`Basic query for Ruby code `: Learn to write and run a simple CodeQL query using LGTM.
- :doc:`CodeQL library for Ruby `: When you're analyzing a Ruby program, you can make use of the large collection of classes in the CodeQL library for Ruby.
+- :doc:`Using API graphs in Ruby `: API graphs are a uniform interface for referring to functions, classes, and methods defined in external libraries.
+
.. include:: ../reusables/ruby-beta-note.rst
diff --git a/docs/codeql/codeql-language-guides/using-api-graphs-in-ruby.rst b/docs/codeql/codeql-language-guides/using-api-graphs-in-ruby.rst
new file mode 100644
index 00000000000..af3b1ecfdec
--- /dev/null
+++ b/docs/codeql/codeql-language-guides/using-api-graphs-in-ruby.rst
@@ -0,0 +1,183 @@
+.. _using-api-graphs-in-ruby:
+
+Using API graphs in Ruby
+==========================
+
+API graphs are a uniform interface for referring to functions, classes, and methods defined in
+external libraries.
+
+About this article
+------------------
+
+This article describes how to use API graphs to reference classes and functions defined in library
+code. You can use API graphs to conveniently refer to external library functions when defining things like
+remote flow sources.
+
+
+Module and class references
+---------------------------
+
+The most common entry point into the API graph will be the point where a toplevel module or class is
+accessed. For example, you can access the API graph node corresponding to the ``::Regexp`` class
+by using the ``API::getTopLevelMember`` method defined in the ``codeql.ruby.ApiGraphs`` module, as the
+following snippet demonstrates.
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("Regexp")
+
+This query selects the API graph nodes corresponding to references to the ``Regexp`` class. For nested
+modules and classes, you can use the ``getMember` method. For example the following query selects
+references to the ``Net::HTTP`` class.
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("Net").getMember("HTTP")
+
+Note that the given module name *must not* contain any ```::`` symbols. Thus, something like
+`API::getTopLevelMember("Net::HTTP")`` will not do what you expect. Instead, this should be decomposed
+into an access of the ``HTTP`` member of the API graph node for ``Net``, as in the example above.
+
+Calls and class instantiations
+------------------------------
+
+To track the calls of externally defined functions, you can use the ``getMethod`` method. The
+following snippet finds all calls of ``Regexp.compile``:
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("Regexp").getMethod("compile")
+
+The example above is for a call to a class method. Tracking calls to instance methods, is a two-step
+process, first you need to find instances of the class before you can find the calls
+to methods on those instances. The following snippet finds instantiations of the ``Regexp`` class:
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("Regexp").getInstance()
+
+Note that the ``getInstance`` method also includes subclasses. For example if there is a
+``class SpecialRegexp < Regexp`` then ``getInstance`` also finds ``SpecialRegexp.new``.
+
+The following snippet builds on the above to find calls of the ``Regexp#match?`` instance method:
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("Regexp").getInstance().getMethod("match?")
+
+Subclasses
+----------
+
+For many libraries, the main mode of usage is to extend one or more library classes. To track this
+in the API graph, you can use the ``getASubclass`` method to get the API graph node corresponding to
+all the immediate subclasses of this node. To find *all* subclasses, use ``*`` or ``+`` to apply the
+method repeatedly, as in ``getASubclass*``.
+
+Note that ``getASubclass`` does not account for any subclassing that takes place in library code
+that has not been extracted. Thus, it may be necessary to account for this in the models you write.
+For example, the ``ActionController::Base`` class has a predefined subclass ``Rails::ApplicationController``. To find
+all subclasses of ``ActionController::Base``, you must explicitly include the subclasses of ``Rails::ApplicationController`` as well.
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+
+ API::Node actionController() {
+ result =
+ [
+ API::getTopLevelMember("ActionController").getMember("Base"),
+ API::getTopLevelMember("Rails").getMember("ApplicationController")
+ ].getASubclass*()
+ }
+
+ select actionController()
+
+
+Using the API graph in dataflow queries
+---------------------------------------
+
+Dataflow queries often search for points where data from external sources enters the code base
+as well as places where data leaves the code base. API graphs provide a convenient way to refer
+to external API components such as library functions and their inputs and outputs. API graph nodes
+cannot be used directly in dataflow queries they model entities that are defined externally,
+while dataflow nodes correspond to entities defined in the current code base. To brigde this gap
+the API node classes provide the ``asSource()`` and ``asSink()`` methods.
+
+The ``asSource()`` method is used to select dataflow nodes where a value from an external source
+enters the current code base. A typical example is the return value of a library function such as
+``File.read(path)``:
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("File").getMethod("read").getParameter(1).asSource()
+
+
+The ``asSink()`` method is used to select dataflow nodes where a value leaves the
+current code base and flows into an external library. For example the second parameter
+of the ``File.write(path, value)`` method.
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("File").getMethod("write").getParameter(1).asSink()
+
+A more complex example is a call to ``File.open`` with a block argument. This function creates a ```File`` instance
+and passes it to the supplied block. In this case the first parameter of the block is the place where an
+externally created value enters the code base, i.e. the ``|file|`` in the example below:
+
+.. code-block:: ruby
+
+ File.open("/my/file.txt", "w") { |file| file << "Hello world" }
+
+The following snippet finds parameters of blocks of ``File.open`` method calls:
+
+.. code-block:: ql
+
+ import codeql.ruby.ApiGraphs
+
+ select API::getTopLevelMember("File").getMethod("open").getBlock().getParameter(0).asSource()
+
+The following example is a dataflow query that that uses API graphs to find cases where data that
+is read flows into a call to ```File.write``.
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ class Configuration extends DataFlow::Configuration {
+ Configuration() { this = "File read/write Configuration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source = API::getTopLevelMember("File").getMethod("read").getReturn().asSource()
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = API::getTopLevelMember("File").getMethod("write").getParameter(1).asSink()
+ }
+ }
+
+ from DataFlow::Node src, DataFlow::Node sink, Configuration config
+ where config.hasFlow(src, sink)
+ select src, "The data read here flows into a $@ call.", sink, "File.write"
+
+Further reading
+---------------
+
+
+.. include:: ../reusables/ruby-further-reading.rst
+.. include:: ../reusables/codeql-ref-tools-further-reading.rst
From b1da636be0cab23ce4fca168b77d730c022af52b Mon Sep 17 00:00:00 2001
From: Nick Rolfe
Date: Fri, 21 Oct 2022 15:11:43 +0100
Subject: [PATCH 079/465] Ruby: first draft of data flow docs
---
.../analyzing-data-flow-in-ruby.rst | 390 ++++++++++++++++++
.../codeql-for-ruby.rst | 2 +
2 files changed, 392 insertions(+)
create mode 100644 docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst
diff --git a/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst b/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst
new file mode 100644
index 00000000000..feaa6415486
--- /dev/null
+++ b/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst
@@ -0,0 +1,390 @@
+.. _analyzing-data-flow-in-ruby:
+
+Analyzing data flow in Ruby
+=============================
+
+You can use CodeQL to track the flow of data through a Ruby program to places where the data is used.
+
+About this article
+------------------
+
+This article describes how data flow analysis is implemented in the CodeQL libraries for Ruby and includes examples to help you write your own data flow queries.
+The following sections describe how to use the libraries for local data flow, global data flow, and taint tracking.
+For a more general introduction to modeling data flow, see ":ref:`About data flow analysis `."
+
+Local data flow
+---------------
+
+Local data flow is data flow within a single method or callable. Local data flow is easier, faster, and more precise than global data flow, and is sufficient for many queries.
+
+Using local data flow
+~~~~~~~~~~~~~~~~~~~~~
+
+The local data flow library is in the module ``DataFlow`` and it defines the class ``Node``, representing any element through which data can flow.
+``Node``\ s are divided into expression nodes (``ExprNode``) and parameter nodes (``ParameterNode``).
+You can map between a data flow ``ParameterNode`` and its corresponding ``Parameter`` AST node using the ``asParameter`` member predicate.
+Meanwhile, the ``asExpr`` member predicate maps between a data flow ``ExprNode`` and its corresponding ``ExprCfgNode`` in the control-flow library.
+
+.. code-block:: ql
+
+ class Node {
+ /** Gets the expression corresponding to this node, if any. */
+ CfgNodes::ExprCfgNode asExpr() { ... }
+
+ /** Gets the parameter corresponding to this node, if any. */
+ Parameter asParameter() { ... }
+
+ ...
+ }
+
+You can also use the predicates ``exprNode`` and ``parameterNode``:
+
+.. code-block:: ql
+
+ /**
+ * Gets a node corresponding to expression `e`.
+ */
+ ExprNode exprNode(CfgNodes::ExprCfgNode e) { ... }
+
+ /**
+ * Gets the node corresponding to the value of parameter `p` at function entry.
+ */
+ ParameterNode parameterNode(Parameter p) { ... }
+
+Note that since ``asExpr`` and ``exprNode`` map between data-flow and control-flow nodes, you then need to call the ``getExpr`` member predicate on the control-flow node to map to the corresponding AST node,
+e.g. by writing ``node.asExpr().getExpr()``.
+Due to the control-flow graph being split, there can be multiple data-flow and control-flow nodes associated with a single expression AST node.
+
+The predicate ``localFlowStep(Node nodeFrom, Node nodeTo)`` holds if there is an immediate data flow edge from the node ``nodeFrom`` to the node ``nodeTo``.
+You can apply the predicate recursively, by using the ``+`` and ``*`` operators, or you can use the predefined recursive predicate ``localFlow``.
+
+For example, you can find flow from an expression ``source`` to an expression ``sink`` in zero or more local steps:
+
+.. code-block:: ql
+
+ DataFlow::localFlow(source, sink)
+
+Using local taint tracking
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Local taint tracking extends local data flow by including non-value-preserving flow steps.
+For example:
+
+.. code-block:: ruby
+
+ temp = x
+ y = temp + ", " + temp
+
+If ``x`` is a tainted string then ``y`` is also tainted.
+
+The local taint tracking library is in the module ``TaintTracking``.
+Like local data flow, a predicate ``localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo)`` holds if there is an immediate taint propagation edge from the node ``nodeFrom`` to the node ``nodeTo``.
+You can apply the predicate recursively, by using the ``+`` and ``*`` operators, or you can use the predefined recursive predicate ``localTaint``.
+
+For example, you can find taint propagation from an expression ``source`` to an expression ``sink`` in zero or more local steps:
+
+.. code-block:: ql
+
+ TaintTracking::localTaint(source, sink)
+
+
+Using local sources
+~~~~~~~~~~~~~~~~~~~
+
+When asking for local data flow or taint propagation between two expressions as above, you would normally constrain the expressions to be relevant to a certain investigation.
+The next section will give some concrete examples, but there is a more abstract concept that we should call out explicitly, namely that of a local source.
+
+A local source is a data-flow node with no local data flow into it.
+As such, it is a local origin of data flow, a place where a new value is created.
+This includes parameters (which only receive global data flow) and most expressions (because they are not value-preserving).
+Restricting attention to such local sources gives a much lighter and more performant data-flow graph and in most cases also a more suitable abstraction for the investigation of interest.
+The class ``LocalSourceNode`` represents data-flow nodes that are also local sources.
+It comes with a useful member predicate ``flowsTo(DataFlow::Node node)``, which holds if there is local data flow from the local source to ``node``.
+
+Examples
+~~~~~~~~
+
+This query finds the filename argument passed in each call to ``File.open``:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call
+ where call = API::getTopLevelMember("File").getAMethodCall("open")
+ select call.getArgument(0)
+
+Notice the use of the ``API`` module for referring to library methods.
+For more information, see ":doc:`Using API graphs in Ruby `."
+
+Unfortunately this will only give the expression in the argument, not the values which could be passed to it.
+So we use local data flow to find all expressions that flow into the argument:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call, DataFlow::ExprNode expr
+ where
+ call = API::getTopLevelMember("File").getAMethodCall("open") and
+ DataFlow::localFlow(expr, call.getArgument(0))
+ select call, expr
+
+Many expressions flow to the same call.
+If you run this query, you may notice that you get several data-flow nodes for an expression as it flows towards a call (notice repeated locations in the ``call`` column).
+We are mostly interested in the "first" of these, what might be called the local source for the file name.
+To restrict attention to such local sources, and to simultaneously make the analysis more performant, we have the QL class ``LocalSourceNode``.
+We could demand that ``expr`` is such a node:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call, DataFlow::ExprNode expr
+ where
+ call = API::getTopLevelMember("File").getAMethodCall("open") and
+ DataFlow::localFlow(expr, call.getArgument(0)) and
+ expr instanceof DataFlow::LocalSourceNode
+ select call, expr
+
+However, we could also enforce this by casting.
+That would allow us to use the member predicate ``flowsTo`` on ``LocalSourceNode`` like so:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call, DataFlow::ExprNode expr
+ where
+ call = API::getTopLevelMember("File").getAMethodCall("open") and
+ expr.(DataFlow::LocalSourceNode).flowsTo(call.getArgument(0))
+ select call, expr
+
+As an alternative, we can ask more directly that ``expr`` is a local source of the first argument, via the predicate ``getALocalSource``:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call, DataFlow::ExprNode expr
+ where
+ call = API::getTopLevelMember("File").getAMethodCall("open") and
+ expr = call.getArgument(0).getALocalSource()
+ select call, expr
+
+All these three queries give identical results.
+We now mostly have one expression per call.
+
+We may still have cases of more than one expression flowing to a call, but then they flow through different code paths (possibly due to control-flow splitting).
+
+We might want to make the source more specific, for example a parameter to a method or block.
+This query finds instances where a parameter is used as the name when opening a file:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call, DataFlow::ParameterNode p
+ where
+ call = API::getTopLevelMember("File").getAMethodCall("open") and
+ DataFlow::localFlow(p, call.getArgument(0))
+ select call, p
+
+Using the exact name supplied via the parameter may be too strict.
+If we want to know if the parameter influences the file name, we can use taint tracking instead of data flow.
+This query finds calls to ``File.open`` where the filename is derived from a parameter:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.TaintTracking
+ import codeql.ruby.ApiGraphs
+
+ from DataFlow::CallNode call, DataFlow::ParameterNode p
+ where
+ call = API::getTopLevelMember("File").getAMethodCall("open") and
+ TaintTracking::localTaint(p, call.getArgument(0))
+ select call, p
+
+Global data flow
+----------------
+
+Global data flow tracks data flow throughout the entire program, and is therefore more powerful than local data flow.
+However, global data flow is less precise than local data flow, and the analysis typically requires significantly more time and memory to perform.
+
+.. pull-quote:: Note
+
+ .. include:: ../reusables/path-problem.rst
+
+Using global data flow
+~~~~~~~~~~~~~~~~~~~~~~
+
+The global data flow library is used by extending the class ``DataFlow::Configuration``:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+
+ class MyDataFlowConfiguration extends DataFlow::Configuration {
+ MyDataFlowConfiguration() { this = "..." }
+
+ override predicate isSource(DataFlow::Node source) {
+ ...
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ ...
+ }
+ }
+
+These predicates are defined in the configuration:
+
+- ``isSource`` - defines where data may flow from.
+- ``isSink`` - defines where data may flow to.
+- ``isBarrier`` - optionally, restricts the data flow.
+- ``isAdditionalFlowStep`` - optionally, adds additional flow steps.
+
+The characteristic predicate (``MyDataFlowConfiguration()``) defines the name of the configuration, so ``"..."`` must be replaced with a unique name (for instance the class name).
+
+The data flow analysis is performed using the predicate ``hasFlow(DataFlow::Node source, DataFlow::Node sink)``:
+
+.. code-block:: ql
+
+ from MyDataFlowConfiguation dataflow, DataFlow::Node source, DataFlow::Node sink
+ where dataflow.hasFlow(source, sink)
+ select source, "Dataflow to $@.", sink, sink.toString()
+
+Using global taint tracking
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Global taint tracking is to global data flow what local taint tracking is to local data flow.
+That is, global taint tracking extends global data flow with additional non-value-preserving steps.
+The global taint tracking library is used by extending the class ``TaintTracking::Configuration``:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.TaintTracking
+
+ class MyTaintTrackingConfiguration extends TaintTracking::Configuration {
+ MyTaintTrackingConfiguration() { this = "..." }
+
+ override predicate isSource(DataFlow::Node source) {
+ ...
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ ...
+ }
+ }
+
+These predicates are defined in the configuration:
+
+- ``isSource`` - defines where taint may flow from.
+- ``isSink`` - defines where taint may flow to.
+- ``isSanitizer`` - optionally, restricts the taint flow.
+- ``isAdditionalTaintStep`` - optionally, adds additional taint steps.
+
+Similar to global data flow, the characteristic predicate (``MyTaintTrackingConfiguration()``) defines the unique name of the configuration and the taint analysis is performed using the predicate ``hasFlow(DataFlow::Node source, DataFlow::Node sink)``.
+
+Predefined sources and sinks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The data flow library contains a number of predefined sources and sinks, providing a good starting point for defining data flow based security queries.
+
+- The class ``RemoteFlowSource`` (defined in module ``codeql.ruby.dataflow.RemoteFlowSources``) represents data flow from remote network inputs. This is useful for finding security problems in networked services.
+- The library ``Concepts`` (defined in module ``codeql.ruby.Concepts``) contains several subclasses of ``DataFlow::Node`` that are security relevant, such as ``FileSystemAccess`` and ``SqlExecution``.
+
+For global flow, it is also useful to restrict sources to instances of ``LocalSourceNode``.
+The predefined sources generally do that.
+
+Class hierarchy
+~~~~~~~~~~~~~~~
+
+- ``DataFlow::Configuration`` - base class for custom global data flow analysis.
+- ``DataFlow::Node`` - an element behaving as a data-flow node.
+
+ - ``DataFlow::CfgNode`` - a control-flow node behaving as a data-flow node.
+
+ - ``DataFlow::ExprNode`` - an expression behaving as a data-flow node.
+ - ``DataFlow::ParameterNode`` - a parameter data-flow node representing the value of a parameter at method/block entry.
+
+ - ``RemoteFlowSource`` - data flow from network/remote input.
+ - ``Concepts::SystemCommandExecution`` - a data-flow node that executes an operating system command, for instance by spawning a new process.
+ - ``Concepts::FileSystemAccess`` - a data-flow node that performs a file system access, including reading and writing data, creating and deleting files and folders, checking and updating permissions, and so on.
+ - ``Concepts::Path::PathNormalization`` - a data-flow node that performs path normalization. This is often needed in order to safely access paths.
+ - ``Concepts::CodeExecution`` - a data-flow node that dynamically executes Python code.
+ - ``Concepts::SqlExecution`` - a data-flow node that executes SQL statements.
+ - ``Concepts::HTTP::Server::RouteSetup`` - a data-flow node that sets up a route on a server.
+ - ``Concepts::HTTP::Server::HttpResponse`` - a data-flow node that creates an HTTP response on a server.
+
+- ``TaintTracking::Configuration`` - base class for custom global taint tracking analysis.
+
+Examples
+~~~~~~~~
+
+This query shows a data flow configuration that uses all network input as data sources:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.TaintTracking
+ import codeql.ruby.Concepts
+ import codeql.ruby.dataflow.RemoteFlowSources
+
+ class RemoteToFileConfiguration extends TaintTracking::Configuration {
+ RemoteToFileConfiguration() { this = "RemoteToFileConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = any(FileSystemAccess fa).getAPathArgument()
+ }
+ }
+
+ from DataFlow::Node input, DataFlow::Node fileAccess, RemoteToFileConfiguration config
+ where config.hasFlow(input, fileAccess)
+ select fileAccess, "This file access uses data from $@.", input, "user-controllable input."
+
+This data flow configuration tracks data flow from environment variables to opening files:
+
+.. code-block:: ql
+
+ import codeql.ruby.DataFlow
+ import codeql.ruby.controlflow.CfgNodes
+ import codeql.ruby.ApiGraphs
+
+ class EnvironmentToFileConfiguration extends DataFlow::Configuration {
+ EnvironmentToFileConfiguration() { this = "EnvironmentToFileConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(ExprNodes::ConstantReadAccessCfgNode env |
+ env.getExpr().getName() = "ENV" and
+ env = source.asExpr().(ExprNodes::ElementReferenceCfgNode).getReceiver()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = API::getTopLevelMember("File").getAMethodCall("open").getArgument(0)
+ }
+ }
+
+ from EnvironmentToFileConfiguration config, DataFlow::Node environment, DataFlow::Node fileOpen
+ where config.hasFlow(environment, fileOpen)
+ select fileOpen, "This call to 'File.open' uses data from $@.", environment,
+ "an environment variable"
+
+Further reading
+---------------
+
+- ":ref:`Exploring data flow with path queries `"
+
+
+.. include:: ../reusables/ruby-further-reading.rst
+.. include:: ../reusables/codeql-ref-tools-further-reading.rst
diff --git a/docs/codeql/codeql-language-guides/codeql-for-ruby.rst b/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
index bfb29a012ef..8e2dfe267e3 100644
--- a/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
+++ b/docs/codeql/codeql-language-guides/codeql-for-ruby.rst
@@ -15,4 +15,6 @@ Experiment and learn how to write effective and efficient queries for CodeQL dat
- :doc:`CodeQL library for Ruby `: When you're analyzing a Ruby program, you can make use of the large collection of classes in the CodeQL library for Ruby.
+- :doc:`Analyzing data flow in Ruby `: You can use CodeQL to track the flow of data through a Ruby program to places where the data is used.
+
.. include:: ../reusables/ruby-beta-note.rst
From 65f74741103383fdd8e16ebb2c5e4d248e0e7c6c Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 27 Oct 2022 16:44:03 -0400
Subject: [PATCH 080/465] simplify algorithm.matches
---
java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 68190758d4f..256cbef022b 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -95,10 +95,10 @@ private module Asymmetric {
algorithm.matches("sec%") and // specification such as "secp256r1"
result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
or
- algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ algorithm.matches("X9.62%") and // specification such as "X9.62 prime192v2"
result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
or
- (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ algorithm.matches(["prime%", "c2tnb%"]) and // specification such as "prime192v2"
result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
From f40eefce57fda15bdf03953a7d6fcda89070b040 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 27 Oct 2022 17:11:07 -0400
Subject: [PATCH 081/465] use CompileTimeConstantExpr instead of StringLiteral
---
.../java/security/InsufficientKeySize.qll | 4 +-
.../CWE-326/InsufficientKeySizeTest.java | 38 +++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
index 256cbef022b..e09bffca8e1 100644
--- a/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
+++ b/java/ql/lib/semmle/code/java/security/InsufficientKeySize.qll
@@ -187,5 +187,7 @@ private module Symmetric {
/** An instance of a generator that specifies an encryption algorithm. */
abstract private class GeneratorAlgoSpec extends CryptoAlgoSpec {
/** Returns an uppercase string representing the algorithm name specified by this generator object. */
- string getAlgoName() { result = this.getAlgoSpec().(StringLiteral).getValue().toUpperCase() }
+ string getAlgoName() {
+ result = this.getAlgoSpec().(CompileTimeConstantExpr).getStringValue().toUpperCase()
+ }
}
diff --git a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
index e356f35d998..6f0d1f7115c 100644
--- a/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
+++ b/java/ql/test/query-tests/security/CWE-326/InsufficientKeySizeTest.java
@@ -33,6 +33,15 @@ public class InsufficientKeySizeTest {
KeyGenerator keyGen5 = KeyGenerator.getInstance("AES"); // MISSING: test KeyGenerator variable as argument
testSymmetricVariable(size2, keyGen5); // test with variable as key size
testSymmetricInt(64); // test with int literal as key size
+
+ /* Test with variable as algo name argument in `getInstance` method. */
+ final String algoName1 = "AES"; // compile-time constant
+ KeyGenerator keyGen6 = KeyGenerator.getInstance(algoName1);
+ keyGen6.init(64); // $ hasInsufficientKeySize
+
+ String algoName2 = "AES"; // not a compile-time constant
+ KeyGenerator keyGen7 = KeyGenerator.getInstance(algoName2);
+ keyGen7.init(64); // $ MISSING: hasInsufficientKeySize
}
// RSA (Asymmetric): minimum recommended key size is 2048
@@ -70,6 +79,15 @@ public class InsufficientKeySizeTest {
/* Test getting key size as return value of another method */
KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("RSA");
keyPairGen8.initialize(getRSAKeySize()); // $ hasInsufficientKeySize
+
+ /* Test with variable as algo name argument in `getInstance` method. */
+ final String algoName1 = "RSA"; // compile-time constant
+ KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance(algoName1);
+ keyPairGen9.initialize(1024); // $ hasInsufficientKeySize
+
+ String algoName2 = "RSA"; // not a compile-time constant
+ KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance(algoName2);
+ keyPairGen10.initialize(1024); // $ MISSING: hasInsufficientKeySize
}
// DSA (Asymmetric): minimum recommended key size is 2048
@@ -92,6 +110,15 @@ public class InsufficientKeySizeTest {
/* Test `AlgorithmParameterGenerator` */
AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DSA");
paramGen.init(1024); // $ hasInsufficientKeySize
+
+ /* Test with variable as algo name argument in `getInstance` method. */
+ final String algoName1 = "DSA"; // compile-time constant
+ AlgorithmParameterGenerator paramGen1 = AlgorithmParameterGenerator.getInstance(algoName1);
+ paramGen1.init(1024); // $ hasInsufficientKeySize
+
+ String algoName2 = "DSA"; // not a compile-time constant
+ AlgorithmParameterGenerator paramGen2 = AlgorithmParameterGenerator.getInstance(algoName2);
+ paramGen2.init(1024); // $ MISSING: hasInsufficientKeySize
}
// DH (Asymmetric): minimum recommended key size is 2048
@@ -173,6 +200,17 @@ public class InsufficientKeySizeTest {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); // MISSING: test KeyGenerator variable as argument
testAsymmetricEcIntVariable(size, keyPairGen); // test with variable as key size
testAsymmetricEcIntLiteral(128); // test with int literal as key size
+
+ /* Test with variable as curve name argument in `ECGenParameterSpec` constructor. */
+ final String curveName1 = "secp112r1"; // compile-time constant
+ KeyPairGenerator keyPairGen16 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec11 = new ECGenParameterSpec(curveName1); // $ hasInsufficientKeySize
+ keyPairGen16.initialize(ecSpec11);
+
+ String curveName2 = "secp112r1"; // not a compile-time constant
+ KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec12 = new ECGenParameterSpec(curveName2); // $ hasInsufficientKeySize
+ keyPairGen17.initialize(ecSpec12);
}
}
From bb9205226a6d012eafafbe2478c00cee165b621d Mon Sep 17 00:00:00 2001
From: Alex Ford
Date: Fri, 28 Oct 2022 13:36:45 +0100
Subject: [PATCH 082/465] Ruby: fix whitespace in basic query doc table
---
.../codeql/codeql-language-guides/basic-query-for-ruby-code.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst b/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst
index 1881dfc71a7..4acc85e6a85 100644
--- a/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst
+++ b/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst
@@ -80,7 +80,7 @@ After the initial ``import`` statement, this simple query comprises three parts
+---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
| Query part | Purpose | Details |
+===============================================================+===================================================================================================================+========================================================================================================================+
-| ``import codeql.ruby.AST`` | Imports the standard CodeQL AST libraries for Ruby. | Every query begins with one or more ``import`` statements. |
+| ``import codeql.ruby.AST`` | Imports the standard CodeQL AST libraries for Ruby. | Every query begins with one or more ``import`` statements. |
+---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
| ``from IfExpr ifexpr`` | Defines the variables for the query. | We use: an ``IfExpr`` variable for ``if`` expressions. |
| | Declarations are of the form: | |
From 8976ba5583a519afb031334656f6479d2d55d5f9 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 12 Oct 2022 13:09:21 +0200
Subject: [PATCH 083/465] Ruby: Add CallableNode, MethodNode, and accessors
---
ruby/ql/lib/codeql/ruby/ast/Module.qll | 32 +-
.../ruby/dataflow/internal/DataFlowPublic.qll | 295 +++++++++++++++++-
2 files changed, 315 insertions(+), 12 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll
index e13506d2eb9..c11b42c4bf6 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Module.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -77,18 +77,46 @@ class Module extends TModule {
/**
* Gets a singleton method on this module, either declared as a singleton method
* or an instance method on a singleton class.
+ *
+ * Does not take inheritance into account.
*/
- MethodBase getASingletonMethod() {
+ MethodBase getAnOwnSingletonMethod() {
result.(SingletonMethod).getObject() = this.getAnImmediateReferenceBase()
or
result = this.getASingletonClass().getAMethod().(Method)
}
+ /**
+ * Gets an instance method named `name` declared in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ Method getOwnInstanceMethod(string name) { result = this.getADeclaration().getMethod(name) }
+
+ /**
+ * Gets an instance method declared in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ Method getAnOwnInstanceMethod() { result = this.getADeclaration().getMethod(_) }
+
+ /**
+ * Gets the instance method named `name` available in this module, including methods inherited
+ * from ancestors.
+ */
+ Method getInstanceMethod(string name) { result = lookupMethod(this, name) }
+
+ /**
+ * Gets an instance method available in this module, including methods inherited
+ * from ancestors.
+ */
+ Method getAnInstanceMethod() { result = lookupMethod(this, _) }
+
/** Gets a constant or `self` access that refers to this module. */
Expr getAnImmediateReference() {
result = this.getAnImmediateReferenceBase()
or
- result.(SelfVariableAccess).getVariable().getDeclaringScope() = this.getASingletonMethod()
+ result.(SelfVariableAccess).getVariable().getDeclaringScope() = this.getAnOwnSingletonMethod()
}
}
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 6d63397ad23..50ec56019e8 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -54,6 +54,32 @@ class Node extends TNode {
* Gets a data flow node to which data may flow from this node in one local step.
*/
Node getASuccessor() { localFlowStep(this, result) }
+
+ /** Gets the constant value of this expression, if any. */
+ ConstantValue getConstantValue() { result = asExpr().getExpr().getConstantValue() }
+
+ /**
+ * Gets the callable corresponding to this block, lambda expression, or call to `proc` or `lambda`.
+ *
+ * For example, gets the callable in each of the following cases:
+ * ```rb
+ * { |x| x } # block expression
+ * ->(x) { x } # lambda expression
+ * proc { |x| x } # call to 'proc'
+ * lambda { |x| x } # call to 'lambda'
+ * ```
+ */
+ pragma[noinline]
+ CallableNode asCallable() {
+ result = this
+ or
+ exists(CallNode call |
+ call.getReceiver().asExpr().getExpr() instanceof SelfVariableAccess and
+ call.getMethodName() = ["proc", "lambda"] and
+ call.getBlock() = result and
+ this = call
+ )
+ }
}
/** A data-flow node corresponding to a call in the control-flow graph. */
@@ -181,6 +207,12 @@ class LocalSourceNode extends Node {
*/
pragma[inline]
Node getALocalUse() { hasLocalSource(result, this) }
+
+ /** Gets a method call where this node flows to the receiver. */
+ CallNode getAMethodCall() { Cached::hasMethodCall(this, result, _) }
+
+ /** Gets a call to a method named `name`, where this node flows to the receiver. */
+ CallNode getAMethodCall(string name) { Cached::hasMethodCall(this, result, name) }
}
/**
@@ -200,18 +232,38 @@ class PostUpdateNode extends Node instanceof PostUpdateNodeImpl {
}
cached
-private predicate hasLocalSource(Node sink, Node source) {
- // Declaring `source` to be a `SourceNode` currently causes a redundant check in the
- // recursive case, so instead we check it explicitly here.
- source = sink and
- source instanceof LocalSourceNode
- or
- exists(Node mid |
- hasLocalSource(mid, source) and
- localFlowStepTypeTracker(mid, sink)
- )
+private module Cached {
+ cached
+ predicate hasLocalSource(Node sink, Node source) {
+ // Declaring `source` to be a `SourceNode` currently causes a redundant check in the
+ // recursive case, so instead we check it explicitly here.
+ source = sink and
+ source instanceof LocalSourceNode
+ or
+ exists(Node mid |
+ hasLocalSource(mid, source) and
+ localFlowStepTypeTracker(mid, sink)
+ )
+ }
+
+ cached
+ predicate hasMethodCall(LocalSourceNode source, CallNode call, string name) {
+ source.flowsTo(call.getReceiver()) and
+ call.getMethodName() = name
+ }
+
+ cached
+ predicate hasYieldCall(BlockParameterNode block, CallNode yield) {
+ exists(MethodBase method, YieldCall call |
+ block.getMethod() = method and
+ call.getEnclosingMethod() = method and
+ yield.asExpr().getExpr() = call
+ )
+ }
}
+private import Cached
+
/** Gets a node corresponding to expression `e`. */
ExprNode exprNode(CfgNodes::ExprCfgNode e) { result.getExprNode() = e }
@@ -604,8 +656,231 @@ class ModuleNode instanceof Module {
/** Gets the location of this module. */
final Location getLocation() { result = super.getLocation() }
+ /**
+ * Gets `self` in a declaration of this module.
+ *
+ * This only gets `self` at the module level, not inside any (singleton) method.
+ */
+ LocalSourceNode getModuleLevelSelf() {
+ result.(SsaDefinitionNode).getVariable() = super.getADeclaration().getModuleSelfVariable()
+ }
+
+ /**
+ * Gets `self` in the module declaration or in one of its singleton methods.
+ *
+ * Does not take inheritance into account.
+ */
+ LocalSourceNode getAnOwnModuleSelf() {
+ result = this.getModuleLevelSelf()
+ or
+ result = this.getAnOwnSingletonMethod().getSelfParameter()
+ }
+
+ /**
+ * Gets a call to method `name` on `self` in the module-level scope of this module.
+ *
+ * For example,
+ * ```rb
+ * module M
+ * include A # getAModuleLevelCall("include")
+ * foo :bar # getAModuleLevelCall("foo")
+ * end
+ * ```
+ */
+ CallNode getAModuleLevelCall(string name) {
+ result = this.getModuleLevelSelf().getAMethodCall(name)
+ }
+
/** Gets a constant or `self` variable that refers to this module. */
LocalSourceNode getAnImmediateReference() {
result.asExpr().getExpr() = super.getAnImmediateReference()
}
+
+ /**
+ * Gets a singleton method declared in this module (or in a singleton class
+ * augmenting this module).
+ *
+ * Does not take inheritance into account.
+ */
+ MethodNode getAnOwnSingletonMethod() { result.asMethod() = super.getAnOwnSingletonMethod() }
+
+ /**
+ * Gets the singleton method named `name` declared in this module (or in a singleton class
+ * augmenting this module).
+ *
+ * Does not take inheritance into account.
+ */
+ MethodNode getOwnSingletonMethod(string name) {
+ result = this.getAnOwnSingletonMethod() and
+ result.getMethodName() = name
+ }
+
+ /**
+ * Gets an instance method declared in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ MethodNode getAnOwnInstanceMethod() {
+ result.asMethod() = this.getADeclaration().getAMethod().(Method)
+ }
+
+ /**
+ * Gets an instance method named `name` declared in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ MethodNode getOwnInstanceMethod(string name) {
+ result = this.getAnOwnInstanceMethod() and
+ result.getMethodName() = name
+ }
+
+ /**
+ * Gets the `self` parameter of an instance method declared in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ ParameterNode getAnOwnInstanceSelf() {
+ result = TSelfParameterNode(this.getAnOwnInstanceMethod().asMethod())
+ }
+
+ /**
+ * Gets the `self` parameter of an instance method available in this module,
+ * including those inherited from ancestors.
+ */
+ ParameterNode getAnInstanceSelf() {
+ result = TSelfParameterNode(this.getAnInstanceMethod().asMethod())
+ }
+
+ private InstanceVariableAccess getAnOwnInstanceVariableAccess(string name) {
+ exists(InstanceVariable v |
+ v.getDeclaringScope() = this.getADeclaration() and
+ v.getName() = name and
+ result.getVariable() = v
+ )
+ }
+
+ /**
+ * Gets an access to the instance variable `name` in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ LocalSourceNode getAnOwnInstanceVariableRead(string name) {
+ result.asExpr().getExpr() =
+ this.getAnOwnInstanceVariableAccess(name).(InstanceVariableReadAccess)
+ }
+
+ /**
+ * Gets the right-hand side of an assignment to the instance variable `name` in this module.
+ *
+ * Does not take inheritance into account.
+ */
+ Node getAnOwnInstanceVariableWriteValue(string name) {
+ exists(Assignment assignment |
+ assignment.getLeftOperand() = this.getAnOwnInstanceVariableAccess(name) and
+ result.asExpr().getExpr() = assignment.getRightOperand()
+ )
+ }
+
+ /**
+ * Gets the instance method named `name` available in this module, including methods inherited
+ * from ancestors.
+ */
+ MethodNode getInstanceMethod(string name) {
+ result.asCallableAstNode() = super.getInstanceMethod(name)
+ }
+
+ /**
+ * Gets an instance method named available in this module, including methods inherited
+ * from ancestors.
+ */
+ MethodNode getAnInstanceMethod() { result = this.getInstanceMethod(_) }
+}
+
+/**
+ * A representation of a run-time class.
+ */
+class ClassNode extends ModuleNode {
+ ClassNode() { isClass() }
+}
+
+/**
+ * A data flow node corresponding to a method, block, or lambda expression.
+ */
+class CallableNode extends ExprNode {
+ private Callable callable;
+
+ CallableNode() { this.asExpr().getExpr() = callable }
+
+ /** Gets the underlying AST node as a `Callable`. */
+ Callable asCallableAstNode() { result = callable }
+
+ private ParameterPosition getParameterPosition(ParameterNodeImpl node) {
+ node.isSourceParameterOf(callable, result)
+ }
+
+ /** Gets the `n`th positional parameter. */
+ ParameterNode getParameter(int n) { getParameterPosition(result).isPositional(n) }
+
+ /** Gets the keyword parameter of the given name. */
+ ParameterNode getKeywordParameter(string name) { getParameterPosition(result).isKeyword(name) }
+
+ /** Gets the `self` parameter of this callable, if any. */
+ ParameterNode getSelfParameter() { getParameterPosition(result).isSelf() }
+
+ /**
+ * Gets the `hash-splat` parameter. This is a synthetic parameter holding
+ * a hash object with entries for each keyword argument passed to the function.
+ */
+ ParameterNode getHashSplatParameter() { getParameterPosition(result).isHashSplat() }
+
+ /**
+ * Gets the block parameter of this method, if any.
+ */
+ ParameterNode getBlockParameter() { getParameterPosition(result).isBlock() }
+
+ /**
+ * Gets a `yield` in this method call or `.call` on the block parameter.
+ */
+ CallNode getABlockCall() {
+ hasYieldCall(getBlockParameter(), result)
+ or
+ result = getBlockParameter().getAMethodCall("call")
+ }
+
+ /**
+ * Gets the canonical return node from this callable.
+ *
+ * Each callable has exactly one such node, and its location may not correspond
+ * to any particular return site - consider using `getAReturningNode` to get nodes
+ * whose locations correspond to return sites.
+ */
+ Node getReturn() { result.(SynthReturnNode).getCfgScope() = callable }
+
+ /**
+ * Gets a data flow node whose value is about to be returned by this callable.
+ */
+ Node getAReturningNode() { result = this.getReturn().(SynthReturnNode).getAnInput() }
+}
+
+/**
+ * A data flow node corresponding to a method (possibly a singleton method).
+ */
+class MethodNode extends CallableNode {
+ MethodNode() { super.asCallableAstNode() instanceof MethodBase }
+
+ /** Gets the underlying AST node for this method. */
+ MethodBase asMethod() { result = this.asCallableAstNode() }
+
+ /** Gets the name of this method. */
+ string getMethodName() { result = asMethod().getName() }
+}
+
+/**
+ * A data flow node corresponding to a block argument.
+ */
+class BlockNode extends CallableNode {
+ BlockNode() { super.asCallableAstNode() instanceof Block }
+
+ /** Gets the underlying AST node for this block. */
+ Block asBlock() { result = this.asCallableAstNode() }
}
From 67772bbc433a93214812ccadace4e7d6cf819fa7 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Sat, 15 Oct 2022 17:24:07 +0200
Subject: [PATCH 084/465] Ruby: Accessors for attributes and elements
---
.../ruby/dataflow/internal/DataFlowPublic.qll | 63 +++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 50ec56019e8..2370ec3a9aa 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -213,6 +213,69 @@ class LocalSourceNode extends Node {
/** Gets a call to a method named `name`, where this node flows to the receiver. */
CallNode getAMethodCall(string name) { Cached::hasMethodCall(this, result, name) }
+
+ /** Gets a call `obj.name` with no arguments, where this node flows to `obj`. */
+ CallNode getAnAttributeRead(string name) {
+ result = this.getAMethodCall(name) and
+ result.getNumberOfArguments() = 0
+ }
+
+ /**
+ * Gets a value assigned to `name` on this object, such as the `x` in `obj.name = x`.
+ *
+ * Concretely, this gets the argument of any call to `name=` where this node flows to the receiver.
+ */
+ Node getAnAttributeWriteValue(string name) {
+ result = this.getAMethodCall(name + "=").getArgument(0)
+ }
+
+ /**
+ * Gets an access to an element on this node, such as `obj[key]`.
+ *
+ * Concretely this gets a call to `[]` with 1 argument, where this node flows to the receiver.
+ */
+ CallNode getAnElementRead() {
+ result = this.getAMethodCall("[]") and result.getNumberOfArguments() = 1
+ }
+
+ /**
+ * Gets an access to the element `key` on this node, such as `obj[:key]`.
+ *
+ * Concretely this gets a call to `[]` where this node flows to the receiver
+ * and the first and only argument has the constant value `key`.
+ */
+ CallNode getAnElementRead(ConstantValue key) {
+ result = this.getAnElementRead() and
+ key = result.getArgument(0).getConstantValue()
+ }
+
+ /**
+ * Gets a value stored as an element on this node, such as the `x` in `obj[key] = x`.
+ *
+ * Concretely, this gets the second argument from any call to `[]=` where this node flows to the receiver.
+ */
+ Node getAnElementWriteValue() {
+ exists(CallNode call |
+ call = this.getAMethodCall("[]=") and
+ call.getNumberOfArguments() = 2 and
+ result = call.getArgument(1)
+ )
+ }
+
+ /**
+ * Gets the `x` in `obj[key] = x`, where this node flows to `obj`.
+ *
+ * Concretely, this gets the second argument from any call to `[]=` where this node flows to the receiver
+ * and the first argument has constant value `key`.
+ */
+ Node getAnElementWriteValue(ConstantValue key) {
+ exists(CallNode call |
+ call = this.getAMethodCall("[]=") and
+ call.getNumberOfArguments() = 2 and
+ call.getArgument(0).getConstantValue() = key and
+ result = call.getArgument(1)
+ )
+ }
}
/**
From 156964bfc9ed727814a6bd2221cddbd22ed93d22 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Oct 2022 13:31:53 +0200
Subject: [PATCH 085/465] Ruby: add getEnclosingModule and getNestedModule
---
ruby/ql/lib/codeql/ruby/ast/Module.qll | 38 +++++++++++++++++++
.../ruby/dataflow/internal/DataFlowPublic.qll | 19 ++++++++++
2 files changed, 57 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll
index c11b42c4bf6..91ffbfb2c91 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Module.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -118,6 +118,44 @@ class Module extends TModule {
or
result.(SelfVariableAccess).getVariable().getDeclaringScope() = this.getAnOwnSingletonMethod()
}
+
+ pragma[nomagic]
+ private string getEnclosingModuleName() {
+ exists(string qname |
+ qname = this.getQualifiedName() and
+ result = qname.regexpReplaceAll("::[^:]*$", "") and
+ qname != result
+ )
+ }
+
+ pragma[nomagic]
+ private string getOwnModuleName() {
+ result = this.getQualifiedName().regexpReplaceAll("^.*::", "")
+ }
+
+ /**
+ * Gets the enclosing module, as it appears in the qualified name of this module.
+ *
+ * For example, the canonical enclosing module of `A::B` is `A`, and `A` itself has no canonical enclosing module.
+ */
+ pragma[nomagic]
+ Module getCanonicalEnclosingModule() { result.getQualifiedName() = this.getEnclosingModuleName() }
+
+ /**
+ * Gets a module named `name` declared inside this one (not aliased), provided
+ * that such a module is defined or reopened in the current codebase.
+ *
+ * For example, for `A::B` the canonical nested module named `C` would be `A::B::C`.
+ *
+ * Note that this is not the same as constant lookup. If `A::B::C` would resolve to a
+ * module whose qualified name is not `A::B::C`, then it will not be found by
+ * this predicate.
+ */
+ pragma[nomagic]
+ Module getCanonicalNestedModule(string name) {
+ result.getCanonicalEnclosingModule() = this and
+ result.getOwnModuleName() = name
+ }
}
/**
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 2370ec3a9aa..f4934500efd 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -857,6 +857,25 @@ class ModuleNode instanceof Module {
* from ancestors.
*/
MethodNode getAnInstanceMethod() { result = this.getInstanceMethod(_) }
+
+ /**
+ * Gets the enclosing module, as it appears in the qualified name of this module.
+ *
+ * For example, the canonical enclosing module of `A::B` is `A`, and `A` itself has no canonical enclosing module.
+ */
+ ModuleNode getCanonicalEnclosingModule() { result = super.getCanonicalEnclosingModule() }
+
+ /**
+ * Gets a module named `name` declared inside this one (not aliased), provided
+ * that such a module is defined or reopened in the current codebase.
+ *
+ * For example, for `A::B` the canonical nested module named `C` would be `A::B::C`.
+ *
+ * Note that this is not the same as constant lookup. If `A::B::C` would resolve to a
+ * module whose qualified name is not `A::B::C`, then it will not be found by
+ * this predicate.
+ */
+ ModuleNode getCanonicalNestedModule(string name) { result = super.getCanonicalNestedModule(name) }
}
/**
From 436cc601389b5f3e5d60ef25ce8e5ce42745584b Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Oct 2022 14:44:47 +0200
Subject: [PATCH 086/465] Ruby: update some uses of getConstantValue()
---
ruby/ql/lib/codeql/ruby/Concepts.qll | 15 +++++++--------
.../codeql/ruby/frameworks/ActionController.qll | 4 ++--
.../lib/codeql/ruby/frameworks/ActiveRecord.qll | 4 +---
ruby/ql/lib/codeql/ruby/frameworks/Rails.qll | 7 +------
.../UnsafeDeserializationCustomizations.qll | 6 +-----
ruby/ql/lib/codeql/ruby/security/XSS.qll | 5 ++---
.../security/cwe-078/NonConstantKernelOpen.ql | 2 +-
.../library-tests/dataflow/summaries/Summaries.ql | 2 +-
8 files changed, 16 insertions(+), 29 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/Concepts.qll b/ruby/ql/lib/codeql/ruby/Concepts.qll
index dd7c75565b7..031b64440bd 100644
--- a/ruby/ql/lib/codeql/ruby/Concepts.qll
+++ b/ruby/ql/lib/codeql/ruby/Concepts.qll
@@ -271,10 +271,7 @@ module Http {
/** Gets the URL pattern for this route, if it can be statically determined. */
string getUrlPattern() {
- exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
- this.getUrlPatternArg().getALocalSource() = DataFlow::exprNode(strNode) and
- result = strNode.getExpr().getConstantValue().getStringlikeValue()
- )
+ result = this.getUrlPatternArg().getALocalSource().getConstantValue().getStringlikeValue()
}
/**
@@ -538,10 +535,12 @@ module Http {
/** Gets the mimetype of this HTTP response, if it can be statically determined. */
string getMimetype() {
- exists(CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
- this.getMimetypeOrContentTypeArg().getALocalSource() = DataFlow::exprNode(strNode) and
- result = strNode.getExpr().getConstantValue().getStringlikeValue().splitAt(";", 0)
- )
+ result =
+ this.getMimetypeOrContentTypeArg()
+ .getALocalSource()
+ .getConstantValue()
+ .getStringlikeValue()
+ .splitAt(";", 0)
or
not exists(this.getMimetypeOrContentTypeArg()) and
result = this.getMimetypeDefault()
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll
index e56df1465f6..1093d406ab8 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll
@@ -234,7 +234,7 @@ private module Request {
// Request headers are prefixed with `HTTP_` to distinguish them from
// "headers" supplied by Rack middleware.
this.getMethodName() = ["get_header", "fetch_header"] and
- this.getArgument(0).asExpr().getExpr().getConstantValue().getString().regexpMatch("^HTTP_.+")
+ this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
@@ -292,7 +292,7 @@ private module Request {
EnvHttpAccess() {
any(EnvCall c).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver()) and
this.getMethodName() = "[]" and
- this.getArgument(0).asExpr().getExpr().getConstantValue().getString().regexpMatch("^HTTP_.+")
+ this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
index eff81adde23..e9109514af2 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
@@ -571,9 +571,7 @@ class ActiveRecordAssociation extends DataFlow::CallNode {
* For example, in `has_many :posts`, this is `post`.
*/
string getTargetModelName() {
- exists(string s |
- s = this.getArgument(0).asExpr().getExpr().getConstantValue().getStringlikeValue()
- |
+ exists(string s | s = this.getArgument(0).getConstantValue().getStringlikeValue() |
// has_one :profile
// belongs_to :user
this.isSingular() and
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll b/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll
index 97bd63453ee..74bd3c0a7e0 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll
@@ -212,12 +212,7 @@ private module Settings {
private class LiteralSetting extends Setting {
ConstantValue value;
- LiteralSetting() {
- exists(DataFlow::LocalSourceNode lsn |
- lsn.asExpr().getConstantValue() = value and
- lsn.flowsTo(this.getArgument(0))
- )
- }
+ LiteralSetting() { value = this.getArgument(0).getALocalSource().getConstantValue() }
string getValueText() { result = value.toString() }
diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll
index da759ea28e9..f12db1435d6 100644
--- a/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll
+++ b/ruby/ql/lib/codeql/ruby/security/UnsafeDeserializationCustomizations.qll
@@ -88,11 +88,7 @@ module UnsafeDeserialization {
private predicate isOjModePair(CfgNodes::ExprNodes::PairCfgNode p, string modeValue) {
p.getKey().getConstantValue().isStringlikeValue("mode") and
- exists(DataFlow::LocalSourceNode symbolLiteral, DataFlow::Node value |
- symbolLiteral.asExpr().getExpr().getConstantValue().isSymbol(modeValue) and
- symbolLiteral.flowsTo(value) and
- value.asExpr() = p.getValue()
- )
+ DataFlow::exprNode(p.getValue()).getALocalSource().getConstantValue().isSymbol(modeValue)
}
/**
diff --git a/ruby/ql/lib/codeql/ruby/security/XSS.qll b/ruby/ql/lib/codeql/ruby/security/XSS.qll
index 7a3b4d2f0e7..8ab1d66446e 100644
--- a/ruby/ql/lib/codeql/ruby/security/XSS.qll
+++ b/ruby/ql/lib/codeql/ruby/security/XSS.qll
@@ -180,11 +180,10 @@ private module Shared {
private predicate isFlowFromLocals0(
CfgNodes::ExprNodes::ElementReferenceCfgNode refNode, string hashKey, ErbFile erb
) {
- exists(DataFlow::Node argNode, CfgNodes::ExprNodes::StringlikeLiteralCfgNode strNode |
+ exists(DataFlow::Node argNode |
argNode.asExpr() = refNode.getArgument(0) and
refNode.getReceiver().getExpr().(MethodCall).getMethodName() = "local_assigns" and
- argNode.getALocalSource() = DataFlow::exprNode(strNode) and
- strNode.getExpr().getConstantValue().isStringlikeValue(hashKey) and
+ argNode.getALocalSource().getConstantValue().isStringlikeValue(hashKey) and
erb = refNode.getFile()
)
}
diff --git a/ruby/ql/src/queries/security/cwe-078/NonConstantKernelOpen.ql b/ruby/ql/src/queries/security/cwe-078/NonConstantKernelOpen.ql
index da490fd9ae3..a7cb2500c54 100644
--- a/ruby/ql/src/queries/security/cwe-078/NonConstantKernelOpen.ql
+++ b/ruby/ql/src/queries/security/cwe-078/NonConstantKernelOpen.ql
@@ -20,7 +20,7 @@ import codeql.ruby.ast.Literal
from AmbiguousPathCall call
where
// there is not a constant string argument
- not exists(call.getPathArgument().asExpr().getExpr().getConstantValue()) and
+ not exists(call.getPathArgument().getConstantValue()) and
// if it's a format string, then the first argument is not a constant string
not call.getPathArgument().getALocalSource().asExpr().getExpr().(StringLiteral).getComponent(0)
instanceof StringTextComponent
diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql
index fbe8f0af9d4..416a8635deb 100644
--- a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql
+++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql
@@ -112,7 +112,7 @@ private class TypeFromCodeQL extends ModelInput::TypeModel {
override DataFlow::Node getASource(string package, string type) {
package = "test" and
type = "FooOrBar" and
- result.asExpr().getExpr().getConstantValue().getString() = "magic_string"
+ result.getConstantValue().getString() = "magic_string"
}
override API::Node getAnApiNode(string package, string type) {
From 1f644a9c1dcba8615e58e5630187674f107231b3 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 12 Oct 2022 16:37:42 +0200
Subject: [PATCH 087/465] Ruby: add getEnclosingToplevel
---
ruby/ql/lib/codeql/ruby/AST.qll | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/AST.qll b/ruby/ql/lib/codeql/ruby/AST.qll
index 6ba833b2108..d517c8f4d44 100644
--- a/ruby/ql/lib/codeql/ruby/AST.qll
+++ b/ruby/ql/lib/codeql/ruby/AST.qll
@@ -36,6 +36,13 @@ private module Cached {
not s instanceof ModuleBase and
result = getEnclosingMethod(s.getOuterScope())
}
+
+ cached
+ Toplevel getEnclosingToplevel(Scope s) {
+ result = s
+ or
+ result = getEnclosingToplevel(s.getOuterScope())
+ }
}
private import Cached
@@ -66,6 +73,9 @@ class AstNode extends TAstNode {
/** Gets the enclosing method, if any. */
final MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) }
+ /** Gets the enclosing top-level. */
+ final Toplevel getEnclosingToplevel() { result = getEnclosingToplevel(scopeOfInclSynth(this)) }
+
/** Gets a textual representation of this node. */
cached
string toString() { none() }
From c8f7519cee198121ac9e201e2535ba327fd07133 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Oct 2022 19:54:27 +0200
Subject: [PATCH 088/465] Ruby: add Module.getNamespaceOrTopLevel
---
ruby/ql/lib/codeql/ruby/ast/Module.qll | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll
index 91ffbfb2c91..a511a8cf952 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Module.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -241,6 +241,14 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
* Does not include the `self` variable from any of the methods in the module.
*/
SelfVariable getModuleSelfVariable() { result.getDeclaringScope() = this }
+
+ /** Gets the nearest enclosing `Namespace` or `Toplevel`, possibly this module itself. */
+ Namespace getNamespaceOrToplevel() {
+ result = this
+ or
+ not this instanceof Namespace and
+ result = this.getEnclosingModule().getNamespaceOrToplevel()
+ }
}
/**
From 515b8366d223fe6e61ff7b030a43ec056a148f3c Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 18 Oct 2022 13:32:29 +0200
Subject: [PATCH 089/465] Ruby: add getAnAncestor, getADescendent
---
ruby/ql/lib/codeql/ruby/ast/Module.qll | 14 ++++++++++++++
.../ruby/dataflow/internal/DataFlowPublic.qll | 11 +++++++++++
2 files changed, 25 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll
index a511a8cf952..cb940b4a35a 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Module.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -23,6 +23,20 @@ class Module extends TModule {
/** Gets an `include`d module. */
Module getAnIncludedModule() { result = getAnIncludedModule(this) }
+ /** Gets the super class or an included or prepended module. */
+ Module getADirectAncestor() {
+ result = [this.getSuperClass(), this.getAPrependedModule(), this.getAnIncludedModule()]
+ }
+
+ /** Gets a direct subclass or module including or prepending this one. */
+ Module getADirectDescendent() { this = result.getADirectAncestor() }
+
+ /** Gets a module that is transitively subclassed, included, or prepended by this module. */
+ Module getAnAncestor() { result = this.getADirectAncestor*() }
+
+ /** Gets a module that transitively subclasses, includes, or prepends this module. */
+ Module getADescendent() { result = this.getADirectDescendent*() }
+
/** Holds if this module is a class. */
pragma[noinline]
predicate isClass() {
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index f4934500efd..7c2f7abbd75 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -703,6 +703,17 @@ class ModuleNode instanceof Module {
/** Gets an `include`d module. */
final ModuleNode getAnIncludedModule() { result = super.getAnIncludedModule() }
+ /** Gets the super class or an included or prepended module. */
+ final ModuleNode getADirectAncestor() { result = super.getADirectAncestor() }
+
+ /** Gets a direct subclass or module including or prepending this one. */
+ final ModuleNode getADirectDescendent() { result = super.getADirectDescendent() }
+
+ /** Gets a module that is transitively subclassed, included, or prepended by this module. */
+ final ModuleNode getAnAncestor() { result = super.getAnAncestor() }
+
+ /** Gets a module that transitively subclasses, includes, or prepends this module. */
+ final ModuleNode getADescendent() { result = super.getADescendent() }
/** Holds if this module is a class. */
predicate isClass() { super.isClass() }
From 2546d09fe272d73e3b46af763b17ecd78abf65fa Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 18 Oct 2022 13:42:45 +0200
Subject: [PATCH 090/465] Ruby: add SetterCallNode
---
.../ruby/dataflow/internal/DataFlowPublic.qll | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 7c2f7abbd75..d7acaf71b66 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -150,6 +150,29 @@ class CallNode extends LocalSourceNode, ExprNode {
}
}
+/**
+ * A call to a setter method.
+ *
+ * For example,
+ * ```rb
+ * self.foo = 10
+ * a[0] = 10
+ * ```
+ */
+class SetterCallNode extends CallNode {
+ SetterCallNode() { asExpr().getExpr() instanceof SetterMethodCall }
+
+ /**
+ * Gets the name of the method being called without the trailing `=`. For example, in the following
+ * two statements the target name is `value`:
+ * ```rb
+ * foo.value=(1)
+ * foo.value = 1
+ * ```
+ */
+ final string getTargetName() { result = asExpr().getExpr().(SetterMethodCall).getTargetName() }
+}
+
/**
* An expression, viewed as a node in a data flow graph.
*
From 77d1788619e3462b7de952fb0ba5dff8d33bc35e Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 20 Oct 2022 11:01:09 +0200
Subject: [PATCH 091/465] Ruby: add data flow versions of ArrayLiteral,
HashLiteral, Pair
---
.../ruby/dataflow/internal/DataFlowPublic.qll | 66 +++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index d7acaf71b66..fd0e37bb92e 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -737,6 +737,7 @@ class ModuleNode instanceof Module {
/** Gets a module that transitively subclasses, includes, or prepends this module. */
final ModuleNode getADescendent() { result = super.getADescendent() }
+
/** Holds if this module is a class. */
predicate isClass() { super.isClass() }
@@ -1000,3 +1001,68 @@ class BlockNode extends CallableNode {
/** Gets the underlying AST node for this block. */
Block asBlock() { result = this.asCallableAstNode() }
}
+
+/**
+ * A representation of a pair such as `K => V` or `K: V`.
+ *
+ * Unlike most expressions, pairs do not evaluate to actual objects at runtime and their nodes
+ * cannot generally be expected to have meaningful data flow edges.
+ * This node simply provides convenient access to the key and value as data flow nodes.
+ */
+class PairNode extends ExprNode {
+ PairNode() { getExprNode() instanceof CfgNodes::ExprNodes::PairCfgNode }
+
+ /**
+ * Holds if this pair is of form `key => value` or `key: value`.
+ */
+ predicate hasKeyAndValue(Node key, Node value) {
+ exists(CfgNodes::ExprNodes::PairCfgNode n |
+ getExprNode() = n and
+ key = TExprNode(n.getKey()) and
+ value = TExprNode(n.getValue())
+ )
+ }
+
+ /** Gets the key expression of this pair, such as the `K` in `K => V` or `K: V`. */
+ Node getKey() { this.hasKeyAndValue(result, _) }
+
+ /** Gets the value expression of this pair, such as the `V` in `K => V` or `K: V`. */
+ Node getValue() { this.hasKeyAndValue(_, result) }
+}
+
+/**
+ * A data-flow node that corresponds to a hash literal. Hash literals are desugared
+ * into calls to `Hash.[]`, so this includes both desugared calls as well as
+ * explicit calls.
+ */
+class HashLiteralNode extends LocalSourceNode, ExprNode {
+ HashLiteralNode() { super.getExprNode() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode }
+
+ /** Gets a pair in this hash literal. */
+ PairNode getAKeyValuePair() {
+ result.getExprNode() =
+ super.getExprNode().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair()
+ }
+
+ /** Gets the value associated with the constant `key`, if known. */
+ Node getElementFromKey(ConstantValue key) {
+ exists(ExprNode keyNode |
+ this.getAKeyValuePair().hasKeyAndValue(keyNode, result) and
+ keyNode.getConstantValue() = key
+ )
+ }
+}
+
+/**
+ * A data-flow node corresponding to an array literal. Array literals are desugared
+ * into calls to `Array.[]`, so this includes both desugared calls as well as
+ * explicit calls.
+ */
+class ArrayLiteralNode extends LocalSourceNode, ExprNode {
+ ArrayLiteralNode() { super.getExprNode() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode }
+
+ /**
+ * Gets an element of the array.
+ */
+ Node getAnElement() { result = this.(CallNode).getPositionalArgument(_) }
+}
From 046e669c786b78e8bcd7d444a0900e72e3a08a50 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 20 Oct 2022 14:29:52 +0200
Subject: [PATCH 092/465] Ruby: add getAncestorExpr
---
ruby/ql/lib/codeql/ruby/ast/Module.qll | 26 +++++++++++++++++++
.../ruby/dataflow/internal/DataFlowPublic.qll | 18 +++++++++++++
2 files changed, 44 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Module.qll b/ruby/ql/lib/codeql/ruby/ast/Module.qll
index cb940b4a35a..7d5c2fae540 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Module.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Module.qll
@@ -3,6 +3,7 @@ private import codeql.ruby.CFG
private import internal.AST
private import internal.Module
private import internal.TreeSitter
+private import internal.Scope
/**
* A representation of a run-time `module` or `class` value.
@@ -263,6 +264,31 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
not this instanceof Namespace and
result = this.getEnclosingModule().getNamespaceOrToplevel()
}
+
+ /**
+ * Gets an expression denoting the super class or an included or prepended module.
+ *
+ * For example, `C` is an ancestor expression of `M` in each of the following examples:
+ * ```rb
+ * class M < C
+ * end
+ *
+ * module M
+ * include C
+ * prepend C
+ * end
+ * ```
+ */
+ Expr getAnAncestorExpr() {
+ exists(MethodCall call |
+ call.getReceiver().(SelfVariableAccess).getVariable() = this.getModuleSelfVariable() and
+ call.getMethodName() = ["include", "prepend"] and
+ result = call.getArgument(0) and
+ scopeOfInclSynth(call) = this // only permit calls directly in the module scope, not in a block
+ )
+ or
+ result = this.(ClassDeclaration).getSuperclassExpr()
+ }
}
/**
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index fd0e37bb92e..ac7ba249467 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -738,6 +738,24 @@ class ModuleNode instanceof Module {
/** Gets a module that transitively subclasses, includes, or prepends this module. */
final ModuleNode getADescendent() { result = super.getADescendent() }
+ /**
+ * Gets the expression node denoting the super class or an included or prepended module.
+ *
+ * For example, `C` is an ancestor expression of `M` in each of the following examples:
+ * ```rb
+ * class M < C
+ * end
+ *
+ * module M
+ * include C
+ * prepend C
+ * end
+ * ```
+ */
+ final ExprNode getAnAncestorExpr() {
+ result.asExpr().getExpr() = super.getADeclaration().getAnAncestorExpr()
+ }
+
/** Holds if this module is a class. */
predicate isClass() { super.isClass() }
From 06ec03de747fcecbf1cb878eb356db2b0060a337 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 20 Oct 2022 11:07:35 +0200
Subject: [PATCH 093/465] Ruby: add convenience-accessors for ConstantValue
---
ruby/ql/lib/codeql/ruby/ast/Constant.qll | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/ast/Constant.qll b/ruby/ql/lib/codeql/ruby/ast/Constant.qll
index 187295d443b..a5a48a04ed4 100644
--- a/ruby/ql/lib/codeql/ruby/ast/Constant.qll
+++ b/ruby/ql/lib/codeql/ruby/ast/Constant.qll
@@ -170,6 +170,24 @@ module ConstantValue {
/** A constant `nil` value. */
class ConstantNilValue extends ConstantValue, TNil { }
+
+ /** Gets the integer constant `x`. */
+ ConstantValue getInt(int x) { result.getInt() = x }
+
+ /** Gets the float constant `x`. */
+ ConstantValue getFloat(float x) { result.getFloat() = x }
+
+ /** Gets the string constant `x`. */
+ ConstantValue getString(string x) { result.getString() = x }
+
+ /** Gets the symbol constant `x`. */
+ ConstantValue getSymbol(string x) { result.getSymbol() = x }
+
+ /** Gets the regexp constant `x`. */
+ ConstantValue getRegExp(string x) { result.getRegExp() = x }
+
+ /** Gets the string, symbol, or regexp constant `x`. */
+ ConstantValue getStringlikeValue(string x) { result.getStringlikeValue() = x }
}
/** An access to a constant. */
From fd61a5253db6a2a9b05678b03eae3680fc522b16 Mon Sep 17 00:00:00 2001
From: Harry Maclean
Date: Fri, 28 Oct 2022 12:14:29 +1300
Subject: [PATCH 094/465] Ruby: Recognise try/try! as code executions
---
.../codeql/ruby/frameworks/ActiveSupport.qll | 23 +++++++++++++++++++
.../active_support/ActiveSupport.expected | 9 ++++++++
.../active_support/ActiveSupport.ql | 3 +++
.../active_support/active_support.rb | 8 +++++++
4 files changed, 43 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll
index 6c57b31f6fa..bf8d46e3e7e 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll
@@ -81,6 +81,29 @@ module ActiveSupport {
preservesValue = true
}
}
+
+ /**
+ * A call to `Object#try`, which may execute its first argument as a Ruby
+ * method call.
+ * ```rb
+ * x = "abc"
+ * x.try(:upcase) # => "ABC"
+ * y = nil
+ * y.try(:upcase) # => nil
+ * ```
+ * `Object#try!` behaves similarly but raises `NoMethodError` if the
+ * receiver is not `nil` and does not respond to the method.
+ */
+ class TryCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
+ TryCallCodeExecution() {
+ this.asExpr().getExpr() instanceof UnknownMethodCall and
+ this.getMethodName() = ["try", "try!"]
+ }
+
+ override DataFlow::Node getCode() { result = this.getArgument(0) }
+
+ override predicate runsArbitraryCode() { none() }
+ }
}
/**
diff --git a/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.expected b/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.expected
index c99abbeacf3..bca8bf08959 100644
--- a/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.expected
+++ b/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.expected
@@ -5,3 +5,12 @@ constantizeCalls
loggerInstantiations
| active_support.rb:6:1:6:33 | call to new |
| active_support.rb:7:1:7:40 | call to new |
+codeExecutions
+| active_support.rb:1:1:1:22 | call to constantize |
+| active_support.rb:3:1:3:13 | call to constantize |
+| active_support.rb:4:1:4:18 | call to safe_constantize |
+| active_support.rb:296:5:296:18 | call to try |
+| active_support.rb:297:5:297:17 | call to try |
+| active_support.rb:298:5:298:19 | call to try! |
+| active_support.rb:298:5:298:35 | call to try! |
+| active_support.rb:299:5:299:18 | call to try! |
diff --git a/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.ql b/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.ql
index 149113851be..239a47434e2 100644
--- a/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.ql
+++ b/ruby/ql/test/library-tests/frameworks/active_support/ActiveSupport.ql
@@ -1,9 +1,12 @@
import codeql.ruby.frameworks.ActiveSupport
import codeql.ruby.DataFlow
import codeql.ruby.frameworks.stdlib.Logger
+import codeql.ruby.Concepts
query DataFlow::Node constantizeCalls(ActiveSupport::CoreExtensions::String::Constantize c) {
result = c.getCode()
}
query predicate loggerInstantiations(Logger::LoggerInstantiation l) { any() }
+
+query predicate codeExecutions(CodeExecution c) { any() }
diff --git a/ruby/ql/test/library-tests/frameworks/active_support/active_support.rb b/ruby/ql/test/library-tests/frameworks/active_support/active_support.rb
index 023f9724ce8..fe0256080d1 100644
--- a/ruby/ql/test/library-tests/frameworks/active_support/active_support.rb
+++ b/ruby/ql/test/library-tests/frameworks/active_support/active_support.rb
@@ -290,3 +290,11 @@ def m_deep_dup
x = source "a"
sink x.deep_dup # $hasValueFlow=a
end
+
+def m_try(method)
+ x = "abc"
+ x.try(:upcase)
+ x.try(method)
+ x.try!(:upcase).try!(:downcase)
+ x.try!(method)
+end
From 0dd63c007eb64ccc87c63bc1f40be6305a03bdc8 Mon Sep 17 00:00:00 2001
From: Harry Maclean
Date: Fri, 28 Oct 2022 12:16:02 +1300
Subject: [PATCH 095/465] Ruby: Add change note
---
ruby/ql/lib/change-notes/2022-10-28-try-code-execution.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 ruby/ql/lib/change-notes/2022-10-28-try-code-execution.md
diff --git a/ruby/ql/lib/change-notes/2022-10-28-try-code-execution.md b/ruby/ql/lib/change-notes/2022-10-28-try-code-execution.md
new file mode 100644
index 00000000000..af5b1cb59e4
--- /dev/null
+++ b/ruby/ql/lib/change-notes/2022-10-28-try-code-execution.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.
From 5369ba1d832e39013ef1d82581c2582239856290 Mon Sep 17 00:00:00 2001
From: Nick Rolfe
Date: Mon, 31 Oct 2022 11:24:30 +0000
Subject: [PATCH 096/465] ruby docs: remove distracting sentence
---
.../codeql-language-guides/analyzing-data-flow-in-ruby.rst | 1 -
1 file changed, 1 deletion(-)
diff --git a/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst b/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst
index feaa6415486..49a633ba2a7 100644
--- a/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst
+++ b/docs/codeql/codeql-language-guides/analyzing-data-flow-in-ruby.rst
@@ -97,7 +97,6 @@ The next section will give some concrete examples, but there is a more abstract
A local source is a data-flow node with no local data flow into it.
As such, it is a local origin of data flow, a place where a new value is created.
This includes parameters (which only receive global data flow) and most expressions (because they are not value-preserving).
-Restricting attention to such local sources gives a much lighter and more performant data-flow graph and in most cases also a more suitable abstraction for the investigation of interest.
The class ``LocalSourceNode`` represents data-flow nodes that are also local sources.
It comes with a useful member predicate ``flowsTo(DataFlow::Node node)``, which holds if there is local data flow from the local source to ``node``.
From b632e21ba0e4dac76929f868e0017d6e19d8b140 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 20 Oct 2022 14:29:58 +0200
Subject: [PATCH 097/465] Ruby: add ConstRef
---
.../ruby/dataflow/internal/DataFlowPublic.qll | 230 ++++++++++++++++++
1 file changed, 230 insertions(+)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index ac7ba249467..91be512d329 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -1084,3 +1084,233 @@ class ArrayLiteralNode extends LocalSourceNode, ExprNode {
*/
Node getAnElement() { result = this.(CallNode).getPositionalArgument(_) }
}
+
+/**
+ * A place in which a named constant can be found up during constant lookup.
+ */
+private newtype TConstLookupScope =
+ /** Look up in a qualified constant name `base::`. */
+ MkQualifiedLookup(ConstantAccess base) or
+ /** Look up in the ancestors of `mod`. */
+ MkAncestorLookup(Module mod) or
+ /** Look up in a module syntactically nested in `scope`. */
+ MkNestedLookup(ModuleBase scope) or
+ /** Pseudo-scope for accesses that are known to resolve to `mod`. */
+ MkExactLookup(Module mod)
+
+/**
+ * Gets a `LocalSourceNode` to represent the constant read or written by `access`.
+ */
+private LocalSourceNode getConstantAccessNode(ConstantAccess access) {
+ // Namespaces don't evaluate to the constant being accessed, they return the value of their last statement.
+ // Use the definition of 'self' in the namespace as the representative in this case.
+ if access instanceof Namespace
+ then
+ result.(SsaDefinitionNode).getDefinition().(Ssa::SelfDefinition).getSourceVariable() =
+ access.(Namespace).getModuleSelfVariable()
+ else result.asExpr().getExpr() = access
+}
+
+/**
+ * An access to a constant, such as `C`, `C::D`, or a class or module declaration.
+ *
+ * See `DataFlow::getConst` for usage example.
+ */
+class ConstRef extends LocalSourceNode {
+ private ConstantAccess access;
+
+ ConstRef() { this = getConstantAccessNode(access) }
+
+ /** Gets the underlying constant access AST node. */
+ ConstantAccess asConstantAccess() { result = access }
+
+ /** Gets the underlying module declaration, if any. */
+ Namespace asNamespaceDeclaration() { result = access }
+
+ /** Gets the module defined or re-opened by this constant access, if any. */
+ ModuleNode asModule() { result.getADeclaration() = access }
+
+ /**
+ * Gets the simple name of the constant being referenced, such as
+ * the `B` in `A::B`.
+ */
+ string getName() { result = access.getName() }
+
+ /**
+ * Holds if this might refer to a top-level constant.
+ */
+ predicate isPossiblyGlobal() {
+ exists(Module mod |
+ not exists(mod.getCanonicalEnclosingModule()) and
+ mod.getAnImmediateReference() = access
+ )
+ or
+ not exists(Module mod | mod.getAnImmediateReference() = access) and
+ not exists(access.getScopeExpr())
+ }
+
+ /**
+ * Gets a module for which this constant is the reference to an ancestor module.
+ *
+ * For example, `M` is the ancestry target of `C` in the following examples:
+ * ```rb
+ * class M < C {}
+ *
+ * module M
+ * include C
+ * end
+ *
+ * module M
+ * prepend C
+ * end
+ * ```
+ */
+ private ModuleNode getAncestryTarget() { result.getAnAncestorExpr() = this }
+
+ /**
+ * Gets a module scope in which the value of this constant is part of `Module.nesting`.
+ */
+ private ModuleBase getANestingScope() {
+ result = this.getAncestryTarget().getADeclaration()
+ or
+ result.getEnclosingModule() = this.getANestingScope()
+ }
+
+ /**
+ * Gets the known target module.
+ *
+ * We resolve these differently to prune out infeasible constant lookups.
+ */
+ private Module getExactTarget() { result.getAnImmediateReference() = access }
+
+ /**
+ * Gets a scope in which a constant lookup may access the contents of the module referenced by this constant.
+ */
+ pragma[nomagic]
+ private TConstLookupScope getATargetScope() {
+ result = MkAncestorLookup(this.getAncestryTarget().getADirectDescendent*())
+ or
+ access = any(ConstantAccess ac).getScopeExpr() and
+ result = MkQualifiedLookup(access)
+ or
+ result = MkNestedLookup(this.getANestingScope())
+ or
+ result = MkExactLookup(access.(Namespace).getModule())
+ }
+
+ /**
+ * Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes).
+ *
+ * Top-levels are not included, since this is only needed for nested constant lookup, and unqualified constants
+ * at the top-level are handled by `DataFlow::getConst`, never `ConstRef.getConst`.
+ */
+ private TConstLookupScope getLookupScope() {
+ result = MkQualifiedLookup(access.getScopeExpr())
+ or
+ not exists(this.getExactTarget()) and
+ not exists(access.getScopeExpr()) and
+ not access.hasGlobalScope() and
+ (
+ result = MkAncestorLookup(access.getEnclosingModule().getNamespaceOrToplevel().getModule())
+ or
+ result = MkNestedLookup(access.getEnclosingModule())
+ )
+ }
+
+ /**
+ * Holds if this can reference a constant named `name` from `scope` using a lookup of `kind`.
+ */
+ pragma[nomagic]
+ private predicate accesses(TConstLookupScope scope, string name) {
+ scope = this.getLookupScope() and
+ name = this.getName()
+ or
+ exists(Module mod |
+ this.getExactTarget() = mod.getCanonicalNestedModule(name) and
+ scope = MkExactLookup(mod)
+ )
+ }
+
+ /**
+ * Gets a constant reference that may resolve to a member of this node.
+ *
+ * For example `DataFlow::getConst("A").getConst("B")` finds the following:
+ * ```rb
+ * A::B # simple reference
+ *
+ * module A
+ * B # in scope
+ * module X
+ * B # in nested scope
+ * end
+ * end
+ *
+ * module X
+ * include A
+ * B # via inclusion
+ * end
+ *
+ * class X < A
+ * B # via subclassing
+ * end
+ * ```
+ */
+ pragma[inline]
+ ConstRef getConst(string name) {
+ exists(TConstLookupScope scope |
+ pragma[only_bind_into](scope) = pragma[only_bind_out](this).getATargetScope() and
+ result.accesses(pragma[only_bind_out](scope), name)
+ )
+ }
+
+ /**
+ * Gets a module that transitively subclasses, includes, or prepends the module referred to by
+ * this constant.
+ *
+ * For example, `DataFlow::getConst("A").getADescendentModule()` finds `B`, `C`, and `E`:
+ * ```rb
+ * class B < A
+ * end
+ *
+ * class C < B
+ * end
+ *
+ * module E
+ * include C
+ * end
+ * ```
+ */
+ ModuleNode getADescendentModule() { MkAncestorLookup(result) = this.getATargetScope() }
+}
+
+/**
+ * Gets a constant reference that may resolve to the top-level constant `name`.
+ *
+ * To get nested constants, call `getConst()` one or more times on the result.
+ *
+ * For example `DataFlow::getConst("A").getConst("B")` finds the following:
+ * ```rb
+ * A::B # simple reference
+ *
+ * module A
+ * B # in scope
+ * module X
+ * B # in nested scope
+ * end
+ * end
+ *
+ * module X
+ * include A
+ * B # via inclusion
+ * end
+ *
+ * class X < A
+ * B # via subclassing
+ * end
+ * ```
+ */
+pragma[nomagic]
+ConstRef getConst(string name) {
+ result.getName() = name and
+ result.isPossiblyGlobal()
+}
From 4ed61c13f89ba4dc2aab0e19c0c5b813c222bb7c Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 27 Oct 2022 09:56:58 +0200
Subject: [PATCH 098/465] Ruby: add some captured-variable flow tests
---
.../dataflow/global/Flow.expected | 33 ++++++++++++++++
.../dataflow/global/captured_variables.rb | 38 +++++++++++++++++++
2 files changed, 71 insertions(+)
create mode 100644 ruby/ql/test/library-tests/dataflow/global/captured_variables.rb
diff --git a/ruby/ql/test/library-tests/dataflow/global/Flow.expected b/ruby/ql/test/library-tests/dataflow/global/Flow.expected
index c7139c2ec5b..ffba59b0f7e 100644
--- a/ruby/ql/test/library-tests/dataflow/global/Flow.expected
+++ b/ruby/ql/test/library-tests/dataflow/global/Flow.expected
@@ -1,5 +1,17 @@
failures
edges
+| captured_variables.rb:1:24:1:24 | x : | captured_variables.rb:2:20:2:20 | x |
+| captured_variables.rb:1:24:1:24 | x : | captured_variables.rb:2:20:2:20 | x |
+| captured_variables.rb:5:20:5:30 | call to source : | captured_variables.rb:1:24:1:24 | x : |
+| captured_variables.rb:5:20:5:30 | call to source : | captured_variables.rb:1:24:1:24 | x : |
+| captured_variables.rb:21:33:21:33 | x : | captured_variables.rb:23:14:23:14 | x |
+| captured_variables.rb:21:33:21:33 | x : | captured_variables.rb:23:14:23:14 | x |
+| captured_variables.rb:27:29:27:39 | call to source : | captured_variables.rb:21:33:21:33 | x : |
+| captured_variables.rb:27:29:27:39 | call to source : | captured_variables.rb:21:33:21:33 | x : |
+| captured_variables.rb:32:31:32:31 | x : | captured_variables.rb:34:14:34:14 | x |
+| captured_variables.rb:32:31:32:31 | x : | captured_variables.rb:34:14:34:14 | x |
+| captured_variables.rb:38:27:38:37 | call to source : | captured_variables.rb:32:31:32:31 | x : |
+| captured_variables.rb:38:27:38:37 | call to source : | captured_variables.rb:32:31:32:31 | x : |
| instance_variables.rb:10:19:10:19 | x : | instance_variables.rb:11:18:11:18 | x : |
| instance_variables.rb:10:19:10:19 | x : | instance_variables.rb:11:18:11:18 | x : |
| instance_variables.rb:11:18:11:18 | x : | instance_variables.rb:11:9:11:14 | [post] self [@field] : |
@@ -152,6 +164,24 @@ edges
| instance_variables.rb:84:6:84:10 | foo13 [@field] : | instance_variables.rb:84:6:84:20 | call to get_field |
| instance_variables.rb:84:6:84:10 | foo13 [@field] : | instance_variables.rb:84:6:84:20 | call to get_field |
nodes
+| captured_variables.rb:1:24:1:24 | x : | semmle.label | x : |
+| captured_variables.rb:1:24:1:24 | x : | semmle.label | x : |
+| captured_variables.rb:2:20:2:20 | x | semmle.label | x |
+| captured_variables.rb:2:20:2:20 | x | semmle.label | x |
+| captured_variables.rb:5:20:5:30 | call to source : | semmle.label | call to source : |
+| captured_variables.rb:5:20:5:30 | call to source : | semmle.label | call to source : |
+| captured_variables.rb:21:33:21:33 | x : | semmle.label | x : |
+| captured_variables.rb:21:33:21:33 | x : | semmle.label | x : |
+| captured_variables.rb:23:14:23:14 | x | semmle.label | x |
+| captured_variables.rb:23:14:23:14 | x | semmle.label | x |
+| captured_variables.rb:27:29:27:39 | call to source : | semmle.label | call to source : |
+| captured_variables.rb:27:29:27:39 | call to source : | semmle.label | call to source : |
+| captured_variables.rb:32:31:32:31 | x : | semmle.label | x : |
+| captured_variables.rb:32:31:32:31 | x : | semmle.label | x : |
+| captured_variables.rb:34:14:34:14 | x | semmle.label | x |
+| captured_variables.rb:34:14:34:14 | x | semmle.label | x |
+| captured_variables.rb:38:27:38:37 | call to source : | semmle.label | call to source : |
+| captured_variables.rb:38:27:38:37 | call to source : | semmle.label | call to source : |
| instance_variables.rb:10:19:10:19 | x : | semmle.label | x : |
| instance_variables.rb:10:19:10:19 | x : | semmle.label | x : |
| instance_variables.rb:11:9:11:14 | [post] self [@field] : | semmle.label | [post] self [@field] : |
@@ -335,6 +365,9 @@ subpaths
| instance_variables.rb:84:6:84:10 | foo13 [@field] : | instance_variables.rb:13:5:15:7 | self in get_field [@field] : | instance_variables.rb:14:9:14:21 | return : | instance_variables.rb:84:6:84:20 | call to get_field |
| instance_variables.rb:84:6:84:10 | foo13 [@field] : | instance_variables.rb:13:5:15:7 | self in get_field [@field] : | instance_variables.rb:14:9:14:21 | return : | instance_variables.rb:84:6:84:20 | call to get_field |
#select
+| captured_variables.rb:2:20:2:20 | x | captured_variables.rb:5:20:5:30 | call to source : | captured_variables.rb:2:20:2:20 | x | $@ | captured_variables.rb:5:20:5:30 | call to source : | call to source : |
+| captured_variables.rb:23:14:23:14 | x | captured_variables.rb:27:29:27:39 | call to source : | captured_variables.rb:23:14:23:14 | x | $@ | captured_variables.rb:27:29:27:39 | call to source : | call to source : |
+| captured_variables.rb:34:14:34:14 | x | captured_variables.rb:38:27:38:37 | call to source : | captured_variables.rb:34:14:34:14 | x | $@ | captured_variables.rb:38:27:38:37 | call to source : | call to source : |
| instance_variables.rb:20:10:20:13 | @foo | instance_variables.rb:19:12:19:21 | call to taint : | instance_variables.rb:20:10:20:13 | @foo | $@ | instance_variables.rb:19:12:19:21 | call to taint : | call to taint : |
| instance_variables.rb:25:6:25:18 | call to get_field | instance_variables.rb:24:15:24:23 | call to taint : | instance_variables.rb:25:6:25:18 | call to get_field | $@ | instance_variables.rb:24:15:24:23 | call to taint : | call to taint : |
| instance_variables.rb:29:6:29:18 | call to inc_field | instance_variables.rb:28:15:28:22 | call to taint : | instance_variables.rb:29:6:29:18 | call to inc_field | $@ | instance_variables.rb:28:15:28:22 | call to taint : | call to taint : |
diff --git a/ruby/ql/test/library-tests/dataflow/global/captured_variables.rb b/ruby/ql/test/library-tests/dataflow/global/captured_variables.rb
new file mode 100644
index 00000000000..88b5be2e7c3
--- /dev/null
+++ b/ruby/ql/test/library-tests/dataflow/global/captured_variables.rb
@@ -0,0 +1,38 @@
+def capture_local_call x
+ fn = -> { sink(x) } # $ hasValueFlow=1.1
+ fn.call
+end
+capture_local_call source(1.1)
+
+def capture_escape_return1 x
+ -> {
+ sink(x) # $ MISSING: hasValueFlow=1.2
+ }
+end
+(capture_escape_return1 source(1.2)).call
+
+def capture_escape_return2 x
+ -> {
+ sink(x) # $ MISSING: hasValueFlow=1.3
+ }
+end
+Something.unknownMethod(capture_escape_return2 source(1.3))
+
+def capture_escape_unknown_call x
+ fn = -> {
+ sink(x) # $ hasValueFlow=1.4
+ }
+ Something.unknownMethod(fn)
+end
+capture_escape_unknown_call source(1.4)
+
+def call_it fn
+ fn.call
+end
+def capture_escape_known_call x
+ fn = -> {
+ sink(x) # $ hasValueFlow=1.5
+ }
+ call_it fn
+end
+capture_escape_known_call source(1.5)
From b29ac5249e497d52c0778018b972dca5f66ad9c0 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 27 Oct 2022 10:07:36 +0200
Subject: [PATCH 099/465] Ruby: add type-tracking inline test in global flow
test
---
.../global/TypeTrackingInlineTest.expected | 15 +++++++++++++++
.../dataflow/global/TypeTrackingInlineTest.ql | 1 +
2 files changed, 16 insertions(+)
create mode 100644 ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.expected
create mode 100644 ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.ql
diff --git a/ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.expected b/ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.expected
new file mode 100644
index 00000000000..aa4fa55223a
--- /dev/null
+++ b/ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.expected
@@ -0,0 +1,15 @@
+| instance_variables.rb:20:16:20:33 | # $ hasValueFlow=7 | Missing result:hasValueFlow=7 |
+| instance_variables.rb:25:21:25:39 | # $ hasValueFlow=42 | Missing result:hasValueFlow=42 |
+| instance_variables.rb:37:22:37:40 | # $ hasValueFlow=21 | Missing result:hasValueFlow=21 |
+| instance_variables.rb:41:18:41:36 | # $ hasValueFlow=22 | Missing result:hasValueFlow=22 |
+| instance_variables.rb:49:22:49:40 | # $ hasValueFlow=24 | Missing result:hasValueFlow=24 |
+| instance_variables.rb:53:22:53:40 | # $ hasValueFlow=22 | Missing result:hasValueFlow=22 |
+| instance_variables.rb:54:22:54:40 | # $ hasValueFlow=24 | Missing result:hasValueFlow=24 |
+| instance_variables.rb:55:22:55:40 | # $ hasValueFlow=25 | Missing result:hasValueFlow=25 |
+| instance_variables.rb:60:22:60:40 | # $ hasValueFlow=26 | Missing result:hasValueFlow=26 |
+| instance_variables.rb:61:22:61:40 | # $ hasValueFlow=26 | Missing result:hasValueFlow=26 |
+| instance_variables.rb:66:22:66:40 | # $ hasValueFlow=27 | Missing result:hasValueFlow=27 |
+| instance_variables.rb:67:23:67:41 | # $ hasValueFlow=27 | Missing result:hasValueFlow=27 |
+| instance_variables.rb:75:23:75:41 | # $ hasValueFlow=28 | Missing result:hasValueFlow=28 |
+| instance_variables.rb:79:23:79:41 | # $ hasValueFlow=28 | Missing result:hasValueFlow=28 |
+| instance_variables.rb:84:23:84:41 | # $ hasValueFlow=28 | Missing result:hasValueFlow=28 |
diff --git a/ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.ql b/ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.ql
new file mode 100644
index 00000000000..97df46829d9
--- /dev/null
+++ b/ruby/ql/test/library-tests/dataflow/global/TypeTrackingInlineTest.ql
@@ -0,0 +1 @@
+import TestUtilities.InlineTypeTrackingFlowTest
From 017157820ab1afc85e28f88fb973c88db7278b97 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Oct 2022 15:23:24 +0200
Subject: [PATCH 100/465] Ruby: make ParameterNode extend LocalSourceNode
---
.../lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll | 8 ++++----
.../lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
index 89010d75235..fe0899d4763 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
@@ -109,7 +109,7 @@ module LocalFlow {
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
*/
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
- nodeTo = getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
+ nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
or
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
}
@@ -299,7 +299,7 @@ private module Cached {
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
- defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
+ defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
@@ -316,7 +316,7 @@ private module Cached {
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
- defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
+ defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
@@ -366,7 +366,7 @@ private module Cached {
cached
predicate isLocalSourceNode(Node n) {
- n instanceof ParameterNode
+ n instanceof TParameterNode
or
// Expressions that can't be reached from another entry definition or expression
n instanceof ExprNode and
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 91be512d329..8af1d31e303 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -193,7 +193,7 @@ class ExprNode extends Node, TExprNode {
* The value of a parameter at function entry, viewed as a node in a data
* flow graph.
*/
-class ParameterNode extends Node, TParameterNode instanceof ParameterNodeImpl {
+class ParameterNode extends LocalSourceNode, TParameterNode instanceof ParameterNodeImpl {
/** Gets the parameter corresponding to this node, if any. */
final Parameter getParameter() { result = super.getParameter() }
}
From ff02ba5965cd31e43399a9e8a8a9558534f32dda Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 18 Oct 2022 15:33:41 +0200
Subject: [PATCH 101/465] Ruby: include SSA param input step for flowsTo
---
.../lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll | 7 ++++++-
.../lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll | 6 ++++--
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
index fe0899d4763..d8b04bebfab 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
@@ -327,7 +327,12 @@ private module Cached {
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
}
- /** This is the local flow predicate that is used in type tracking. */
+ /**
+ * This is the local flow predicate that is used in type tracking.
+ *
+ * This needs to exclude `localFlowSsaParamInput` due to a performance trick
+ * in type tracking, where such steps are treated as call steps.
+ */
cached
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 8af1d31e303..16740ce33fe 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -326,9 +326,11 @@ private module Cached {
source = sink and
source instanceof LocalSourceNode
or
- exists(Node mid |
- hasLocalSource(mid, source) and
+ exists(Node mid | hasLocalSource(mid, source) |
localFlowStepTypeTracker(mid, sink)
+ or
+ // Explicitly include the SSA param input step as type-tracking omits this step.
+ LocalFlow::localFlowSsaParamInput(mid, sink)
)
}
From 0a8f39fe96e89baa167017d290080384f373c113 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 27 Oct 2022 21:36:15 +0200
Subject: [PATCH 102/465] Ruby: recover some incomplete capture flow
---
ruby/ql/lib/codeql/ruby/dataflow/SSA.qll | 2 +-
.../dataflow/internal/DataFlowPrivate.qll | 16 ++
.../ruby/dataflow/internal/DataFlowPublic.qll | 2 +
.../dataflow/local/DataflowStep.expected | 8 +-
.../dataflow/local/TaintStep.expected | 8 +-
.../test/library-tests/variables/ssa.expected | 204 +++++++++---------
6 files changed, 129 insertions(+), 111 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll
index d828420682c..ca956ed89f5 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll
@@ -289,7 +289,7 @@ module Ssa {
)
}
- final override string toString() { result = "" }
+ final override string toString() { result = " " + this.getSourceVariable() }
override Location getLocation() { result = this.getBasicBlock().getLocation() }
}
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
index d8b04bebfab..0ccc41e3069 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll
@@ -114,6 +114,22 @@ module LocalFlow {
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
}
+ /**
+ * Holds if `nodeFrom -> nodeTo` is a step from a parameter to a capture entry node for
+ * that parameter.
+ *
+ * This is intended to recover from flow not currently recognised by ordinary capture flow.
+ */
+ predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
+ exists(Ssa::CapturedEntryDefinition def |
+ nodeFrom.asParameter().(NamedParameter).getVariable() = def.getSourceVariable()
+ or
+ nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
+ |
+ nodeTo.(SsaDefinitionNode).getDefinition() = def
+ )
+ }
+
/**
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
* involving SSA definition `def`.
diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
index 16740ce33fe..fdd21c5bf5e 100644
--- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
+++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll
@@ -331,6 +331,8 @@ private module Cached {
or
// Explicitly include the SSA param input step as type-tracking omits this step.
LocalFlow::localFlowSsaParamInput(mid, sink)
+ or
+ LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
)
}
diff --git a/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected
index a9387f49fa5..1c06c617f5f 100644
--- a/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected
+++ b/ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected
@@ -26,7 +26,7 @@
| local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... |
| local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... |
| local_dataflow.rb:10:5:13:3 | ... = ... | local_dataflow.rb:12:5:12:5 | x |
-| local_dataflow.rb:10:5:13:3 | | local_dataflow.rb:11:1:11:2 | self |
+| local_dataflow.rb:10:5:13:3 | self | local_dataflow.rb:11:1:11:2 | self |
| local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | ... = ... |
| local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | ... = ... |
| local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | __synth__0__1 |
@@ -65,7 +65,7 @@
| local_dataflow.rb:45:10:45:10 | 6 | local_dataflow.rb:45:3:45:10 | return |
| local_dataflow.rb:49:1:53:3 | [post] self | local_dataflow.rb:55:1:55:14 | self |
| local_dataflow.rb:49:1:53:3 | self | local_dataflow.rb:55:1:55:14 | self |
-| local_dataflow.rb:49:3:53:3 | | local_dataflow.rb:50:18:50:18 | x |
+| local_dataflow.rb:49:3:53:3 | x | local_dataflow.rb:50:18:50:18 | x |
| local_dataflow.rb:50:8:50:13 | "next" | local_dataflow.rb:50:3:50:13 | next |
| local_dataflow.rb:50:18:50:18 | [post] x | local_dataflow.rb:51:20:51:20 | x |
| local_dataflow.rb:50:18:50:18 | x | local_dataflow.rb:51:20:51:20 | x |
@@ -264,7 +264,7 @@
| local_dataflow.rb:118:3:118:11 | [post] self | local_dataflow.rb:119:3:119:31 | self |
| local_dataflow.rb:118:3:118:11 | call to source | local_dataflow.rb:118:3:118:31 | call to tap |
| local_dataflow.rb:118:3:118:11 | self | local_dataflow.rb:119:3:119:31 | self |
-| local_dataflow.rb:118:17:118:31 | | local_dataflow.rb:118:23:118:29 | self |
+| local_dataflow.rb:118:17:118:31 | self | local_dataflow.rb:118:23:118:29 | self |
| local_dataflow.rb:118:20:118:20 | x | local_dataflow.rb:118:20:118:20 | x |
| local_dataflow.rb:118:20:118:20 | x | local_dataflow.rb:118:28:118:28 | x |
| local_dataflow.rb:119:3:119:31 | [post] self | local_dataflow.rb:119:8:119:16 | self |
@@ -278,4 +278,4 @@
| local_dataflow.rb:123:8:123:16 | call to source | local_dataflow.rb:123:8:123:20 | call to dup |
| local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap |
| local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup |
-| local_dataflow.rb:123:26:123:45 | | local_dataflow.rb:123:32:123:43 | self |
+| local_dataflow.rb:123:26:123:45 | self | local_dataflow.rb:123:32:123:43 | self |
diff --git a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected
index 5043cbdb18f..c8af6b208df 100644
--- a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected
+++ b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected
@@ -78,7 +78,7 @@
| local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... |
| local_dataflow.rb:9:9:9:15 | call to [] | local_dataflow.rb:9:1:9:15 | ... = ... |
| local_dataflow.rb:10:5:13:3 | ... = ... | local_dataflow.rb:12:5:12:5 | x |
-| local_dataflow.rb:10:5:13:3 | | local_dataflow.rb:11:1:11:2 | self |
+| local_dataflow.rb:10:5:13:3 | self | local_dataflow.rb:11:1:11:2 | self |
| local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | ... = ... |
| local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | ... = ... |
| local_dataflow.rb:10:5:13:3 | __synth__0__1 | local_dataflow.rb:10:5:13:3 | __synth__0__1 |
@@ -123,7 +123,7 @@
| local_dataflow.rb:45:10:45:10 | 6 | local_dataflow.rb:45:3:45:10 | return |
| local_dataflow.rb:49:1:53:3 | [post] self | local_dataflow.rb:55:1:55:14 | self |
| local_dataflow.rb:49:1:53:3 | self | local_dataflow.rb:55:1:55:14 | self |
-| local_dataflow.rb:49:3:53:3 | | local_dataflow.rb:50:18:50:18 | x |
+| local_dataflow.rb:49:3:53:3 | x | local_dataflow.rb:50:18:50:18 | x |
| local_dataflow.rb:50:8:50:13 | "next" | local_dataflow.rb:50:3:50:13 | next |
| local_dataflow.rb:50:18:50:18 | [post] x | local_dataflow.rb:51:20:51:20 | x |
| local_dataflow.rb:50:18:50:18 | x | local_dataflow.rb:50:18:50:22 | ... < ... |
@@ -338,7 +338,7 @@
| local_dataflow.rb:118:3:118:11 | [post] self | local_dataflow.rb:119:3:119:31 | self |
| local_dataflow.rb:118:3:118:11 | call to source | local_dataflow.rb:118:3:118:31 | call to tap |
| local_dataflow.rb:118:3:118:11 | self | local_dataflow.rb:119:3:119:31 | self |
-| local_dataflow.rb:118:17:118:31 | | local_dataflow.rb:118:23:118:29 | self |
+| local_dataflow.rb:118:17:118:31 | self | local_dataflow.rb:118:23:118:29 | self |
| local_dataflow.rb:118:20:118:20 | x | local_dataflow.rb:118:20:118:20 | x |
| local_dataflow.rb:118:20:118:20 | x | local_dataflow.rb:118:28:118:28 | x |
| local_dataflow.rb:119:3:119:31 | [post] self | local_dataflow.rb:119:8:119:16 | self |
@@ -352,4 +352,4 @@
| local_dataflow.rb:123:8:123:16 | call to source | local_dataflow.rb:123:8:123:20 | call to dup |
| local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap |
| local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup |
-| local_dataflow.rb:123:26:123:45 | | local_dataflow.rb:123:32:123:43 | self |
+| local_dataflow.rb:123:26:123:45 | self | local_dataflow.rb:123:32:123:43 | self |
diff --git a/ruby/ql/test/library-tests/variables/ssa.expected b/ruby/ql/test/library-tests/variables/ssa.expected
index 62a3bee1a81..e0553f1ad39 100644
--- a/ruby/ql/test/library-tests/variables/ssa.expected
+++ b/ruby/ql/test/library-tests/variables/ssa.expected
@@ -13,9 +13,9 @@ definition
| instance_variables.rb:15:3:17:5 | self (m) | instance_variables.rb:15:3:17:5 | self |
| instance_variables.rb:20:1:25:3 | self (M) | instance_variables.rb:20:1:25:3 | self |
| instance_variables.rb:22:2:24:4 | self (n) | instance_variables.rb:22:2:24:4 | self |
-| instance_variables.rb:27:6:29:1 | | instance_variables.rb:1:1:44:4 | self |
+| instance_variables.rb:27:6:29:1 | self | instance_variables.rb:1:1:44:4 | self |
| instance_variables.rb:31:1:33:3 | self (bar) | instance_variables.rb:31:1:33:3 | self |
-| instance_variables.rb:32:10:32:21 | | instance_variables.rb:31:1:33:3 | self |
+| instance_variables.rb:32:10:32:21 | self | instance_variables.rb:31:1:33:3 | self |
| instance_variables.rb:35:1:44:4 | self (C) | instance_variables.rb:35:1:44:4 | self |
| instance_variables.rb:37:3:43:5 | self (x) | instance_variables.rb:37:3:43:5 | self |
| instance_variables.rb:38:4:40:6 | self (y) | instance_variables.rb:38:4:40:6 | self |
@@ -32,8 +32,8 @@ definition
| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a |
| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a |
| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a |
-| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:12:9:21:11 | self |
-| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a |
+| nested_scopes.rb:18:23:18:36 | a | nested_scopes.rb:16:29:16:29 | a |
+| nested_scopes.rb:18:23:18:36 | self | nested_scopes.rb:12:9:21:11 | self |
| nested_scopes.rb:22:9:24:11 | self (show_a2) | nested_scopes.rb:22:9:24:11 | self |
| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a |
| nested_scopes.rb:27:7:29:9 | self (show) | nested_scopes.rb:27:7:29:9 | self |
@@ -41,7 +41,7 @@ definition
| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a |
| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d |
| parameters.rb:1:1:1:1 | self (parameters.rb) | parameters.rb:1:1:62:1 | self |
-| parameters.rb:1:9:5:3 | | parameters.rb:1:1:62:1 | self |
+| parameters.rb:1:9:5:3 | self | parameters.rb:1:1:62:1 | self |
| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x |
| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y |
| parameters.rb:7:1:13:3 | self (order_pizza) | parameters.rb:7:1:13:3 | self |
@@ -49,7 +49,7 @@ definition
| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas |
| parameters.rb:15:1:19:3 | self (print_map) | parameters.rb:15:1:19:3 | self |
| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map |
-| parameters.rb:16:12:18:5 | | parameters.rb:15:1:19:3 | self |
+| parameters.rb:16:12:18:5 | self | parameters.rb:15:1:19:3 | self |
| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key |
| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value |
| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block |
@@ -76,8 +76,8 @@ definition
| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a |
| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b |
| parameters.rb:53:1:53:6 | ... = ... | parameters.rb:53:1:53:1 | x |
-| parameters.rb:54:9:57:3 | | parameters.rb:1:1:62:1 | self |
-| parameters.rb:54:9:57:3 | | parameters.rb:53:1:53:1 | x |
+| parameters.rb:54:9:57:3 | self | parameters.rb:1:1:62:1 | self |
+| parameters.rb:54:9:57:3 | x | parameters.rb:53:1:53:1 | x |
| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y |
| parameters.rb:54:19:54:23 | ... = ... | parameters.rb:53:1:53:1 | x |
| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x |
@@ -86,11 +86,11 @@ definition
| parameters.rb:59:23:59:23 | b | parameters.rb:59:23:59:23 | b |
| parameters.rb:59:25:59:25 | c | parameters.rb:59:25:59:25 | c |
| scopes.rb:1:1:1:15 | self (scopes.rb) | scopes.rb:1:1:49:4 | self |
-| scopes.rb:2:9:6:3 | | scopes.rb:1:1:49:4 | self |
+| scopes.rb:2:9:6:3 | self | scopes.rb:1:1:49:4 | self |
| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a |
| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a |
+| scopes.rb:9:9:18:3 | a | scopes.rb:7:1:7:1 | a |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self |
| scopes.rb:11:4:11:9 | ... = ... | scopes.rb:7:1:7:1 | a |
| scopes.rb:13:4:13:4 | ... = ... | scopes.rb:7:1:7:1 | a |
| scopes.rb:13:7:13:7 | ... = ... | scopes.rb:13:7:13:7 | b |
@@ -120,11 +120,11 @@ definition
| ssa.rb:25:1:30:3 | self (m2) | ssa.rb:25:1:30:3 | self |
| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements |
| ssa.rb:26:3:28:5 | ... = ... | ssa.rb:26:7:26:10 | elem |
-| ssa.rb:26:3:28:5 | | ssa.rb:25:1:30:3 | self |
+| ssa.rb:26:3:28:5 | self | ssa.rb:25:1:30:3 | self |
| ssa.rb:26:3:28:5 | __synth__0__1 | ssa.rb:26:3:28:5 | __synth__0__1 |
| ssa.rb:26:3:28:5 | call to each | ssa.rb:26:7:26:10 | elem |
| ssa.rb:32:1:36:3 | self (m3) | ssa.rb:32:1:36:3 | self |
-| ssa.rb:33:16:35:5 | | ssa.rb:32:1:36:3 | self |
+| ssa.rb:33:16:35:5 | self | ssa.rb:32:1:36:3 | self |
| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x |
| ssa.rb:38:1:42:3 | self (m4) | ssa.rb:38:1:42:3 | self |
| ssa.rb:40:3:40:9 | ... = ... | ssa.rb:40:3:40:4 | m3 |
@@ -147,19 +147,19 @@ definition
| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a |
| ssa.rb:65:3:65:15 | ... = ... | ssa.rb:65:3:65:10 | captured |
| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured |
-| ssa.rb:66:11:70:5 | | ssa.rb:64:1:72:3 | self |
-| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured |
+| ssa.rb:66:11:70:5 | captured | ssa.rb:65:3:65:10 | captured |
+| ssa.rb:66:11:70:5 | self | ssa.rb:64:1:72:3 | self |
| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a |
| ssa.rb:69:5:69:17 | ... = ... | ssa.rb:65:3:65:10 | captured |
| ssa.rb:74:1:79:3 | self (m10) | ssa.rb:74:1:79:3 | self |
| ssa.rb:75:3:75:14 | ... = ... | ssa.rb:75:3:75:10 | captured |
-| ssa.rb:76:7:78:5 | | ssa.rb:74:1:79:3 | self |
-| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured |
+| ssa.rb:76:7:78:5 | captured | ssa.rb:75:3:75:10 | captured |
+| ssa.rb:76:7:78:5 | self | ssa.rb:74:1:79:3 | self |
| ssa.rb:81:1:88:3 | self (m11) | ssa.rb:81:1:88:3 | self |
| ssa.rb:82:3:82:14 | ... = ... | ssa.rb:82:3:82:10 | captured |
-| ssa.rb:83:7:87:5 | | ssa.rb:81:1:88:3 | self |
-| ssa.rb:84:10:86:8 | | ssa.rb:81:1:88:3 | self |
-| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured |
+| ssa.rb:83:7:87:5 | self | ssa.rb:81:1:88:3 | self |
+| ssa.rb:84:10:86:8 | captured | ssa.rb:82:3:82:10 | captured |
+| ssa.rb:84:10:86:8 | self | ssa.rb:81:1:88:3 | self |
read
| class_variables.rb:1:1:1:3 | self (class_variables.rb) | class_variables.rb:1:1:29:4 | self | class_variables.rb:3:1:3:5 | self |
| class_variables.rb:5:1:7:3 | self (print) | class_variables.rb:5:1:7:3 | self | class_variables.rb:6:2:6:6 | self |
@@ -179,8 +179,8 @@ read
| instance_variables.rb:15:3:17:5 | self (m) | instance_variables.rb:15:3:17:5 | self | instance_variables.rb:16:5:16:6 | self |
| instance_variables.rb:20:1:25:3 | self (M) | instance_variables.rb:20:1:25:3 | self | instance_variables.rb:21:2:21:3 | self |
| instance_variables.rb:22:2:24:4 | self (n) | instance_variables.rb:22:2:24:4 | self | instance_variables.rb:23:4:23:5 | self |
-| instance_variables.rb:27:6:29:1 | | instance_variables.rb:1:1:44:4 | self | instance_variables.rb:28:3:28:4 | self |
-| instance_variables.rb:32:10:32:21 | | instance_variables.rb:31:1:33:3 | self | instance_variables.rb:32:12:32:13 | self |
+| instance_variables.rb:27:6:29:1 | self | instance_variables.rb:1:1:44:4 | self | instance_variables.rb:28:3:28:4 | self |
+| instance_variables.rb:32:10:32:21 | self | instance_variables.rb:31:1:33:3 | self | instance_variables.rb:32:12:32:13 | self |
| instance_variables.rb:35:1:44:4 | self (C) | instance_variables.rb:35:1:44:4 | self | instance_variables.rb:36:3:36:4 | self |
| instance_variables.rb:37:3:43:5 | self (x) | instance_variables.rb:37:3:43:5 | self | instance_variables.rb:41:4:41:4 | self |
| instance_variables.rb:37:3:43:5 | self (x) | instance_variables.rb:37:3:43:5 | self | instance_variables.rb:42:4:42:7 | self |
@@ -202,8 +202,8 @@ read
| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:15:11:15:11 | a |
| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a |
| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a |
-| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:12:9:21:11 | self | nested_scopes.rb:18:29:18:34 | self |
-| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a |
+| nested_scopes.rb:18:23:18:36 | a | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a |
+| nested_scopes.rb:18:23:18:36 | self | nested_scopes.rb:12:9:21:11 | self | nested_scopes.rb:18:29:18:34 | self |
| nested_scopes.rb:22:9:24:11 | self (show_a2) | nested_scopes.rb:22:9:24:11 | self | nested_scopes.rb:23:11:23:16 | self |
| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a |
| nested_scopes.rb:27:7:29:9 | self (show) | nested_scopes.rb:27:7:29:9 | self | nested_scopes.rb:28:11:28:16 | self |
@@ -211,8 +211,8 @@ read
| nested_scopes.rb:30:16:30:19 | self (class << ...) | nested_scopes.rb:30:7:33:9 | self | nested_scopes.rb:32:11:32:16 | self |
| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a |
| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d |
-| parameters.rb:1:9:5:3 | | parameters.rb:1:1:62:1 | self | parameters.rb:3:4:3:9 | self |
-| parameters.rb:1:9:5:3 | | parameters.rb:1:1:62:1 | self | parameters.rb:4:4:4:9 | self |
+| parameters.rb:1:9:5:3 | self | parameters.rb:1:1:62:1 | self | parameters.rb:3:4:3:9 | self |
+| parameters.rb:1:9:5:3 | self | parameters.rb:1:1:62:1 | self | parameters.rb:4:4:4:9 | self |
| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x |
| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y |
| parameters.rb:7:1:13:3 | self (order_pizza) | parameters.rb:7:1:13:3 | self | parameters.rb:9:5:9:33 | self |
@@ -222,7 +222,7 @@ read
| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas |
| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:11:14:11:19 | pizzas |
| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map |
-| parameters.rb:16:12:18:5 | | parameters.rb:15:1:19:3 | self | parameters.rb:17:5:17:28 | self |
+| parameters.rb:16:12:18:5 | self | parameters.rb:15:1:19:3 | self | parameters.rb:17:5:17:28 | self |
| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key |
| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value |
| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block |
@@ -246,8 +246,8 @@ read
| parameters.rb:49:1:51:3 | self (tuples) | parameters.rb:49:1:51:3 | self | parameters.rb:50:3:50:18 | self |
| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a |
| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b |
-| parameters.rb:54:9:57:3 | | parameters.rb:1:1:62:1 | self | parameters.rb:55:4:55:9 | self |
-| parameters.rb:54:9:57:3 | | parameters.rb:1:1:62:1 | self | parameters.rb:56:4:56:9 | self |
+| parameters.rb:54:9:57:3 | self | parameters.rb:1:1:62:1 | self | parameters.rb:55:4:55:9 | self |
+| parameters.rb:54:9:57:3 | self | parameters.rb:1:1:62:1 | self | parameters.rb:56:4:56:9 | self |
| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y |
| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x |
| parameters.rb:59:1:61:3 | self (tuples_nested) | parameters.rb:59:1:61:3 | self | parameters.rb:60:3:60:23 | self |
@@ -255,19 +255,19 @@ read
| parameters.rb:59:23:59:23 | b | parameters.rb:59:23:59:23 | b | parameters.rb:60:16:60:16 | b |
| parameters.rb:59:25:59:25 | c | parameters.rb:59:25:59:25 | c | parameters.rb:60:21:60:21 | c |
| scopes.rb:1:1:1:15 | self (scopes.rb) | scopes.rb:1:1:49:4 | self | scopes.rb:8:1:8:6 | self |
-| scopes.rb:2:9:6:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:3:4:3:9 | self |
-| scopes.rb:2:9:6:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:3:9:3:9 | self |
-| scopes.rb:2:9:6:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:5:4:5:9 | self |
+| scopes.rb:2:9:6:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:3:4:3:9 | self |
+| scopes.rb:2:9:6:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:3:9:3:9 | self |
+| scopes.rb:2:9:6:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:5:4:5:9 | self |
| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a |
| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:10:4:10:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:12:4:12:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:14:4:14:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:15:4:15:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:16:4:16:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:17:4:17:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a |
-| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:11:4:11:4 | a |
+| scopes.rb:9:9:18:3 | a | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a |
+| scopes.rb:9:9:18:3 | a | scopes.rb:7:1:7:1 | a | scopes.rb:11:4:11:4 | a |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:10:4:10:9 | self |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:12:4:12:9 | self |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:14:4:14:9 | self |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:15:4:15:9 | self |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:16:4:16:9 | self |
+| scopes.rb:9:9:18:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:17:4:17:9 | self |
| scopes.rb:11:4:11:9 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:12:9:12:9 | a |
| scopes.rb:13:4:13:4 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:14:9:14:9 | a |
| scopes.rb:13:7:13:7 | ... = ... | scopes.rb:13:7:13:7 | b | scopes.rb:15:9:15:9 | b |
@@ -308,10 +308,10 @@ read
| ssa.rb:25:1:30:3 | self (m2) | ssa.rb:25:1:30:3 | self | ssa.rb:29:3:29:11 | self |
| ssa.rb:25:8:25:15 | elements | ssa.rb:25:8:25:15 | elements | ssa.rb:26:15:26:22 | elements |
| ssa.rb:26:3:28:5 | ... = ... | ssa.rb:26:7:26:10 | elem | ssa.rb:27:10:27:13 | elem |
-| ssa.rb:26:3:28:5 | | ssa.rb:25:1:30:3 | self | ssa.rb:27:5:27:13 | self |
+| ssa.rb:26:3:28:5 | self | ssa.rb:25:1:30:3 | self | ssa.rb:27:5:27:13 | self |
| ssa.rb:26:3:28:5 | __synth__0__1 | ssa.rb:26:3:28:5 | __synth__0__1 | ssa.rb:26:3:28:5 | __synth__0__1 |
| ssa.rb:26:3:28:5 | call to each | ssa.rb:26:7:26:10 | elem | ssa.rb:29:8:29:11 | elem |
-| ssa.rb:33:16:35:5 | | ssa.rb:32:1:36:3 | self | ssa.rb:34:5:34:10 | self |
+| ssa.rb:33:16:35:5 | self | ssa.rb:32:1:36:3 | self | ssa.rb:34:5:34:10 | self |
| ssa.rb:33:20:33:20 | x | ssa.rb:33:20:33:20 | x | ssa.rb:34:10:34:10 | x |
| ssa.rb:38:1:42:3 | self (m4) | ssa.rb:38:1:42:3 | self | ssa.rb:39:3:39:9 | self |
| ssa.rb:38:1:42:3 | self (m4) | ssa.rb:38:1:42:3 | self | ssa.rb:39:8:39:9 | self |
@@ -331,18 +331,18 @@ read
| ssa.rb:64:1:72:3 | self (m9) | ssa.rb:64:1:72:3 | self | ssa.rb:71:3:71:15 | self |
| ssa.rb:64:8:64:8 | a | ssa.rb:64:8:64:8 | a | ssa.rb:66:3:66:3 | a |
| ssa.rb:66:3:70:5 | call to times | ssa.rb:65:3:65:10 | captured | ssa.rb:71:8:71:15 | captured |
-| ssa.rb:66:11:70:5 | | ssa.rb:64:1:72:3 | self | ssa.rb:67:5:67:10 | self |
-| ssa.rb:66:11:70:5 | | ssa.rb:64:1:72:3 | self | ssa.rb:68:5:68:17 | self |
-| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured |
-| ssa.rb:66:11:70:5 | | ssa.rb:65:3:65:10 | captured | ssa.rb:69:5:69:12 | captured |
+| ssa.rb:66:11:70:5 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:68:10:68:17 | captured |
+| ssa.rb:66:11:70:5 | captured | ssa.rb:65:3:65:10 | captured | ssa.rb:69:5:69:12 | captured |
+| ssa.rb:66:11:70:5 | self | ssa.rb:64:1:72:3 | self | ssa.rb:67:5:67:10 | self |
+| ssa.rb:66:11:70:5 | self | ssa.rb:64:1:72:3 | self | ssa.rb:68:5:68:17 | self |
| ssa.rb:66:15:66:15 | a | ssa.rb:66:15:66:15 | a | ssa.rb:67:10:67:10 | a |
| ssa.rb:74:1:79:3 | self (m10) | ssa.rb:74:1:79:3 | self | ssa.rb:76:3:78:5 | self |
-| ssa.rb:76:7:78:5 | | ssa.rb:74:1:79:3 | self | ssa.rb:77:6:77:23 | self |
-| ssa.rb:76:7:78:5 | | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured |
+| ssa.rb:76:7:78:5 | captured | ssa.rb:75:3:75:10 | captured | ssa.rb:77:15:77:22 | captured |
+| ssa.rb:76:7:78:5 | self | ssa.rb:74:1:79:3 | self | ssa.rb:77:6:77:23 | self |
| ssa.rb:81:1:88:3 | self (m11) | ssa.rb:81:1:88:3 | self | ssa.rb:83:3:87:5 | self |
-| ssa.rb:83:7:87:5 | | ssa.rb:81:1:88:3 | self | ssa.rb:84:6:86:8 | self |
-| ssa.rb:84:10:86:8 | | ssa.rb:81:1:88:3 | self | ssa.rb:85:10:85:22 | self |
-| ssa.rb:84:10:86:8 | | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured |
+| ssa.rb:83:7:87:5 | self | ssa.rb:81:1:88:3 | self | ssa.rb:84:6:86:8 | self |
+| ssa.rb:84:10:86:8 | captured | ssa.rb:82:3:82:10 | captured | ssa.rb:85:15:85:22 | captured |
+| ssa.rb:84:10:86:8 | self | ssa.rb:81:1:88:3 | self | ssa.rb:85:10:85:22 | self |
firstRead
| class_variables.rb:1:1:1:3 | self (class_variables.rb) | class_variables.rb:1:1:29:4 | self | class_variables.rb:3:1:3:5 | self |
| class_variables.rb:5:1:7:3 | self (print) | class_variables.rb:5:1:7:3 | self | class_variables.rb:6:2:6:6 | self |
@@ -358,8 +358,8 @@ firstRead
| instance_variables.rb:15:3:17:5 | self (m) | instance_variables.rb:15:3:17:5 | self | instance_variables.rb:16:5:16:6 | self |
| instance_variables.rb:20:1:25:3 | self (M) | instance_variables.rb:20:1:25:3 | self | instance_variables.rb:21:2:21:3 | self |
| instance_variables.rb:22:2:24:4 | self (n) | instance_variables.rb:22:2:24:4 | self | instance_variables.rb:23:4:23:5 | self |
-| instance_variables.rb:27:6:29:1 | | instance_variables.rb:1:1:44:4 | self | instance_variables.rb:28:3:28:4 | self |
-| instance_variables.rb:32:10:32:21 | | instance_variables.rb:31:1:33:3 | self | instance_variables.rb:32:12:32:13 | self |
+| instance_variables.rb:27:6:29:1 | self | instance_variables.rb:1:1:44:4 | self | instance_variables.rb:28:3:28:4 | self |
+| instance_variables.rb:32:10:32:21 | self | instance_variables.rb:31:1:33:3 | self | instance_variables.rb:32:12:32:13 | self |
| instance_variables.rb:35:1:44:4 | self (C) | instance_variables.rb:35:1:44:4 | self | instance_variables.rb:36:3:36:4 | self |
| instance_variables.rb:37:3:43:5 | self (x) | instance_variables.rb:37:3:43:5 | self | instance_variables.rb:41:4:41:4 | self |
| instance_variables.rb:38:4:40:6 | self (y) | instance_variables.rb:38:4:40:6 | self | instance_variables.rb:39:6:39:7 | self |
@@ -376,15 +376,15 @@ firstRead
| nested_scopes.rb:13:11:13:15 | ... = ... | nested_scopes.rb:13:11:13:11 | a | nested_scopes.rb:14:16:14:16 | a |
| nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:15:23:15:23 | a | nested_scopes.rb:16:13:16:13 | a |
| nested_scopes.rb:17:15:17:19 | ... = ... | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:15:18:15 | a |
-| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:12:9:21:11 | self | nested_scopes.rb:18:29:18:34 | self |
-| nested_scopes.rb:18:23:18:36 | | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a |
+| nested_scopes.rb:18:23:18:36 | a | nested_scopes.rb:16:29:16:29 | a | nested_scopes.rb:18:34:18:34 | a |
+| nested_scopes.rb:18:23:18:36 | self | nested_scopes.rb:12:9:21:11 | self | nested_scopes.rb:18:29:18:34 | self |
| nested_scopes.rb:22:9:24:11 | self (show_a2) | nested_scopes.rb:22:9:24:11 | self | nested_scopes.rb:23:11:23:16 | self |
| nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:22:21:22:21 | a | nested_scopes.rb:23:16:23:16 | a |
| nested_scopes.rb:27:7:29:9 | self (show) | nested_scopes.rb:27:7:29:9 | self | nested_scopes.rb:28:11:28:16 | self |
| nested_scopes.rb:30:16:30:19 | self (class << ...) | nested_scopes.rb:30:7:33:9 | self | nested_scopes.rb:32:11:32:16 | self |
| nested_scopes.rb:31:11:31:16 | ... = ... | nested_scopes.rb:31:11:31:11 | a | nested_scopes.rb:32:16:32:16 | a |
| nested_scopes.rb:40:1:40:18 | ... = ... | nested_scopes.rb:40:1:40:1 | d | nested_scopes.rb:41:1:41:1 | d |
-| parameters.rb:1:9:5:3 | | parameters.rb:1:1:62:1 | self | parameters.rb:3:4:3:9 | self |
+| parameters.rb:1:9:5:3 | self | parameters.rb:1:1:62:1 | self | parameters.rb:3:4:3:9 | self |
| parameters.rb:1:14:1:14 | x | parameters.rb:1:14:1:14 | x | parameters.rb:3:9:3:9 | x |
| parameters.rb:2:4:2:8 | ... = ... | parameters.rb:1:18:1:18 | y | parameters.rb:4:9:4:9 | y |
| parameters.rb:7:1:13:3 | self (order_pizza) | parameters.rb:7:1:13:3 | self | parameters.rb:9:5:9:33 | self |
@@ -393,7 +393,7 @@ firstRead
| parameters.rb:7:17:7:22 | client | parameters.rb:7:17:7:22 | client | parameters.rb:11:41:11:46 | client |
| parameters.rb:7:26:7:31 | pizzas | parameters.rb:7:26:7:31 | pizzas | parameters.rb:8:6:8:11 | pizzas |
| parameters.rb:15:17:15:19 | map | parameters.rb:15:17:15:19 | map | parameters.rb:16:3:16:5 | map |
-| parameters.rb:16:12:18:5 | | parameters.rb:15:1:19:3 | self | parameters.rb:17:5:17:28 | self |
+| parameters.rb:16:12:18:5 | self | parameters.rb:15:1:19:3 | self | parameters.rb:17:5:17:28 | self |
| parameters.rb:16:16:16:18 | key | parameters.rb:16:16:16:18 | key | parameters.rb:17:13:17:15 | key |
| parameters.rb:16:21:16:25 | value | parameters.rb:16:21:16:25 | value | parameters.rb:17:22:17:26 | value |
| parameters.rb:21:17:21:21 | block | parameters.rb:21:17:21:21 | block | parameters.rb:22:3:22:7 | block |
@@ -416,7 +416,7 @@ firstRead
| parameters.rb:49:1:51:3 | self (tuples) | parameters.rb:49:1:51:3 | self | parameters.rb:50:3:50:18 | self |
| parameters.rb:49:13:49:13 | a | parameters.rb:49:13:49:13 | a | parameters.rb:50:11:50:11 | a |
| parameters.rb:49:15:49:15 | b | parameters.rb:49:15:49:15 | b | parameters.rb:50:16:50:16 | b |
-| parameters.rb:54:9:57:3 | | parameters.rb:1:1:62:1 | self | parameters.rb:55:4:55:9 | self |
+| parameters.rb:54:9:57:3 | self | parameters.rb:1:1:62:1 | self | parameters.rb:55:4:55:9 | self |
| parameters.rb:54:14:54:14 | y | parameters.rb:54:14:54:14 | y | parameters.rb:56:9:56:9 | y |
| parameters.rb:55:4:55:9 | phi | parameters.rb:53:1:53:1 | x | parameters.rb:55:9:55:9 | x |
| parameters.rb:59:1:61:3 | self (tuples_nested) | parameters.rb:59:1:61:3 | self | parameters.rb:60:3:60:23 | self |
@@ -424,11 +424,11 @@ firstRead
| parameters.rb:59:23:59:23 | b | parameters.rb:59:23:59:23 | b | parameters.rb:60:16:60:16 | b |
| parameters.rb:59:25:59:25 | c | parameters.rb:59:25:59:25 | c | parameters.rb:60:21:60:21 | c |
| scopes.rb:1:1:1:15 | self (scopes.rb) | scopes.rb:1:1:49:4 | self | scopes.rb:8:1:8:6 | self |
-| scopes.rb:2:9:6:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:3:4:3:9 | self |
+| scopes.rb:2:9:6:3 | self | scopes.rb:1:1:49:4 | self | scopes.rb:3:4:3:9 | self |
| scopes.rb:4:4:4:8 | ... = ... | scopes.rb:4:4:4:4 | a | scopes.rb:5:9:5:9 | a |
| scopes.rb:7:1:7:5 | ... = ... | scopes.rb:7:1:7:1 | a | scopes.rb:8:6:8:6 | a |
-| scopes.rb:9:9:18:3 | | scopes.rb:1:1:49:4 | self | scopes.rb:10:4:10:9 | self |
-| scopes.rb:9:9:18:3 | | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a |
+| scopes.rb:9:9:18:3 | a | scopes.rb:7:1:7:1 | a | scopes.rb:10:9:10:9 | a |
+| scopes.rb:9:9:18:3 |