From 6739750d2abd0b6d0f51546e668d1ecbc89c653d Mon Sep 17 00:00:00 2001
From: Maiky <76447395+maikypedia@users.noreply.github.com>
Date: Thu, 23 Nov 2023 12:48:33 +0100
Subject: [PATCH 001/155] Add Unsafe Unpacking Query (CWE-022)
---
.../swift/security/UnsafeUnpackExtensions.qll | 78 +++++++++++++++++++
.../swift/security/UnsafeUnpackQuery.qll | 33 ++++++++
.../Security/CWE-022/UnsafeUnpack.qhelp | 43 ++++++++++
.../Security/CWE-022/UnsafeUnpack.ql | 24 ++++++
.../Security/CWE-022/ZIPFoundationBad.swift | 28 +++++++
.../Security/CWE-022/ZipArchiveGood.swift | 25 ++++++
.../Security/CWE-022/ZipBad.swift | 28 +++++++
.../UnsafeUnpack.expected | 13 ++++
.../CWE-022-Unsafe-Unpack/UnsafeUnpack.qlref | 1 +
.../CWE-022-Unsafe-Unpack/UnsafeUnpack.swift | 71 +++++++++++++++++
10 files changed, 344 insertions(+)
create mode 100644 swift/ql/lib/codeql/swift/security/UnsafeUnpackExtensions.qll
create mode 100644 swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll
create mode 100644 swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp
create mode 100644 swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
create mode 100644 swift/ql/src/experimental/Security/CWE-022/ZIPFoundationBad.swift
create mode 100644 swift/ql/src/experimental/Security/CWE-022/ZipArchiveGood.swift
create mode 100644 swift/ql/src/experimental/Security/CWE-022/ZipBad.swift
create mode 100644 swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected
create mode 100644 swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.qlref
create mode 100644 swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift
diff --git a/swift/ql/lib/codeql/swift/security/UnsafeUnpackExtensions.qll b/swift/ql/lib/codeql/swift/security/UnsafeUnpackExtensions.qll
new file mode 100644
index 000000000000..c102aa40a1ef
--- /dev/null
+++ b/swift/ql/lib/codeql/swift/security/UnsafeUnpackExtensions.qll
@@ -0,0 +1,78 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about
+ * unsafe unpack vulnerabilities, as well as extension points for
+ * adding your own.
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.dataflow.ExternalFlow
+
+/**
+ * A dataflow source for unsafe unpack vulnerabilities.
+ */
+abstract class UnsafeUnpackSource extends DataFlow::Node { }
+
+/**
+ * A dataflow sink for unsafe unpack vulnerabilities.
+ */
+abstract class UnsafeUnpackSink extends DataFlow::Node { }
+
+/**
+ * A barrier for unsafe unpack vulnerabilities.
+ */
+abstract class UnsafeUnpackBarrier extends DataFlow::Node { }
+
+/**
+ * A unit class for adding additional flow steps.
+ */
+class UnsafeUnpackAdditionalFlowStep extends Unit {
+ /**
+ * Holds if the step from `node1` to `node2` should be considered a flow
+ * step for paths related to unsafe unpack vulnerabilities.
+ */
+ abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
+}
+
+/**
+ * A sink defined in a CSV model.
+ */
+private class DefaultUnsafeUnpackSink extends UnsafeUnpackSink {
+ DefaultUnsafeUnpackSink() { sinkNode(this, "unsafe-unpack") }
+}
+
+private class UnsafeUnpackSinks extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ ";Zip;true;unzipFile(_:destination:overwrite:password:progress:fileOutputHandler:);;;Argument[0];unsafe-unpack",
+ ";FileManager;true;unzipItem(at:to:skipCRC32:progress:pathEncoding:);;;Argument[0];unsafe-unpack",
+ ]
+ }
+}
+
+/**
+ * An additional taint step for unsafe unpack vulnerabilities.
+ */
+private class UnsafeUnpackAdditionalDataFlowStep extends UnsafeUnpackAdditionalFlowStep {
+ override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ exists(CallExpr initCall, CallExpr call |
+ // If a zip file is remotely downloaded the destination path is tainted
+ call.getStaticTarget().(Method).hasQualifiedName("Data", "write(to:options:)") and
+ call.getQualifier() = initCall and
+ initCall.getStaticTarget().(Initializer).getEnclosingDecl().(TypeDecl).getName() = "Data" and
+ nodeFrom.asExpr() = initCall and
+ nodeTo.asExpr() = call.getAnArgument().getExpr()
+ )
+ }
+}
+
+/**
+ * A barrier for unsafe unpack vulnerabilities.
+ */
+private class UnsafeUnpackDefaultBarrier extends UnsafeUnpackBarrier {
+ UnsafeUnpackDefaultBarrier() {
+ // any numeric type
+ this.asExpr().getType().getUnderlyingType().getABaseType*().getName() = "Numeric"
+ }
+}
diff --git a/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll b/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll
new file mode 100644
index 000000000000..dbc0f733b526
--- /dev/null
+++ b/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides default sources, sinks and sanitizers for reasoning about
+ * unsafe unpack vulnerabilities, as well as extension points for
+ * adding your own.
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.dataflow.TaintTracking
+import codeql.swift.dataflow.FlowSources
+import codeql.swift.security.UnsafeUnpackExtensions
+
+/**
+ * A taint configuration for tainted data that reaches a unsafe unpack sink.
+ */
+module UnsafeUnpackConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node node) {
+ node instanceof FlowSource or node instanceof RemoteFlowSource
+ }
+
+ predicate isSink(DataFlow::Node node) { node instanceof UnsafeUnpackSink }
+
+ predicate isBarrier(DataFlow::Node barrier) { barrier instanceof UnsafeUnpackBarrier }
+
+ predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ any(UnsafeUnpackAdditionalFlowStep s).step(nodeFrom, nodeTo)
+ }
+}
+
+/**
+ * Detect taint flow of tainted data that reaches a unsafe unpack sink.
+ */
+module UnsafeUnpackFlow = TaintTracking::Global;
diff --git a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp
new file mode 100644
index 000000000000..df162180c536
--- /dev/null
+++ b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+Unpacking files from a malicious zip without properly validating that the destination file path
+is within the destination directory, or allowing symlinks to point to files outside the extraction directory,
+allows an attacker to extract files to arbitrary locations outside the extraction directory. This helps
+overwrite sensitive user data and, in some cases, can lead to code execution if an
+attacker overwrites an application's shared object file.
+
+
+
+
+
Consider using a safer module, such as: ZIPArchive
+
+
+
+
+The following examples unpacks a remote zip using `Zip.unzipFile()` which is vulnerable to path traversal.
+
+
+
+
+The following examples unpacks a remote zip using `fileManager.unzipItem()` which is vulnerable to symlink path traversal.
+
+
+
+
+
Consider using a safer module, such as: ZIPArchive
+
+
\ No newline at end of file
diff --git a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
new file mode 100644
index 000000000000..c50cc6c3b4f9
--- /dev/null
+++ b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
@@ -0,0 +1,24 @@
+/**
+ * @name Arbitrary file write during a zip extraction from a user controlled source
+ * @description Unpacking user controlled zips without validating if destination path file
+ * is within the destination directory can cause files outside
+ * the destination directory to be overwritten.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.8
+ * @precision high
+ * @id swift/unsafe-unpacking
+ * @tags security
+ * experimental
+ * external/cwe/cwe-022
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.security.UnsafeUnpackQuery
+import UnsafeUnpackFlow::PathGraph
+
+from UnsafeUnpackFlow::PathNode sourceNode, UnsafeUnpackFlow::PathNode sinkNode
+where UnsafeUnpackFlow::flowPath(sourceNode, sinkNode)
+select sinkNode.getNode(), sourceNode, sinkNode,
+ "Unsafe unpacking from a malicious zip retrieved from a remote location."
diff --git a/swift/ql/src/experimental/Security/CWE-022/ZIPFoundationBad.swift b/swift/ql/src/experimental/Security/CWE-022/ZIPFoundationBad.swift
new file mode 100644
index 000000000000..7dfd86feffdb
--- /dev/null
+++ b/swift/ql/src/experimental/Security/CWE-022/ZIPFoundationBad.swift
@@ -0,0 +1,28 @@
+import Foundation
+import ZIPFoundation
+
+
+func unzipFile(at sourcePath: String, to destinationPath: String) {
+ do {
+ let remoteURL = URL(string: "https://example.com/")!
+
+ let source = URL(fileURLWithPath: sourcePath)
+ let destination = URL(fileURLWithPath: destinationPath)
+
+ // Malicious zip is downloaded
+ try Data(contentsOf: remoteURL).write(to: source)
+
+ let fileManager = FileManager()
+ // Malicious zip is unpacked
+ try fileManager.unzipItem(at:source, to: destination)
+ } catch {
+ }
+}
+
+func main() {
+ let sourcePath = "/sourcePath"
+ let destinationPath = "/destinationPath"
+ unzipFile(at: sourcePath, to: destinationPath)
+}
+
+main()
\ No newline at end of file
diff --git a/swift/ql/src/experimental/Security/CWE-022/ZipArchiveGood.swift b/swift/ql/src/experimental/Security/CWE-022/ZipArchiveGood.swift
new file mode 100644
index 000000000000..e30f17c16923
--- /dev/null
+++ b/swift/ql/src/experimental/Security/CWE-022/ZipArchiveGood.swift
@@ -0,0 +1,25 @@
+import Foundation
+import ZipArchive
+
+func unzipFile(at sourcePath: String, to destinationPath: String) {
+ do {
+ let remoteURL = URL(string: "https://example.com/")!
+
+ let source = URL(fileURLWithPath: sourcePath)
+
+ // Malicious zip is downloaded
+ try Data(contentsOf: remoteURL).write(to: source)
+
+ // ZipArchive is safe
+ try SSZipArchive.unzipFile(atPath: sourcePath, toDestination: destinationPath, delegate: self)
+ } catch {
+ }
+}
+
+func main() {
+ let sourcePath = "/sourcePath"
+ let destinationPath = "/destinationPath"
+ unzipFile(at: sourcePath, to: destinationPath)
+}
+
+main()
\ No newline at end of file
diff --git a/swift/ql/src/experimental/Security/CWE-022/ZipBad.swift b/swift/ql/src/experimental/Security/CWE-022/ZipBad.swift
new file mode 100644
index 000000000000..4a90e6b3ea86
--- /dev/null
+++ b/swift/ql/src/experimental/Security/CWE-022/ZipBad.swift
@@ -0,0 +1,28 @@
+import Foundation
+import Zip
+
+
+func unzipFile(at sourcePath: String, to destinationPath: String) {
+ do {
+ let remoteURL = URL(string: "https://example.com/")!
+
+ let source = URL(fileURLWithPath: sourcePath)
+ let destination = URL(fileURLWithPath: destinationPath)
+
+ // Malicious zip is downloaded
+ try Data(contentsOf: remoteURL).write(to: source)
+
+ let fileManager = FileManager()
+ // Malicious zip is unpacked
+ try Zip.unzipFile(source, destination: destination, overwrite: true, password: nil)
+ } catch {
+ }
+}
+
+func main() {
+ let sourcePath = "/sourcePath"
+ let destinationPath = "/destinationPath"
+ unzipFile(at: sourcePath, to: destinationPath)
+}
+
+main()
\ No newline at end of file
diff --git a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected
new file mode 100644
index 000000000000..4e79ee8b503e
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected
@@ -0,0 +1,13 @@
+edges
+| UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:60:60:60:60 | source |
+| UnsafeUnpack.swift:60:60:60:60 | source | UnsafeUnpack.swift:62:27:62:27 | source |
+| UnsafeUnpack.swift:60:60:60:60 | source | UnsafeUnpack.swift:65:39:65:39 | source |
+nodes
+| UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | semmle.label | call to Data.init(contentsOf:options:) |
+| UnsafeUnpack.swift:60:60:60:60 | source | semmle.label | source |
+| UnsafeUnpack.swift:62:27:62:27 | source | semmle.label | source |
+| UnsafeUnpack.swift:65:39:65:39 | source | semmle.label | source |
+subpaths
+#select
+| UnsafeUnpack.swift:62:27:62:27 | source | UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:62:27:62:27 | source | Unsafe unpacking from a malicious zip retrieved from a remote location. |
+| UnsafeUnpack.swift:65:39:65:39 | source | UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:65:39:65:39 | source | Unsafe unpacking from a malicious zip retrieved from a remote location. |
diff --git a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.qlref b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.qlref
new file mode 100644
index 000000000000..1d1a5a3a84ce
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-022/UnsafeUnpack.ql
\ No newline at end of file
diff --git a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift
new file mode 100644
index 000000000000..2f599b891502
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift
@@ -0,0 +1,71 @@
+
+// --- stubs ---
+struct URL
+{
+ init?(string: String) {}
+ init(fileURLWithPath: String) {}
+}
+
+class Zip {
+ class func unzipFile(_ zipFilePath: URL, destination: URL, overwrite: Bool, password: String?, progress: ((_ progress: Double) -> ())? = nil, fileOutputHandler: ((_ unzippedFile: URL) -> Void)? = nil) throws {}
+}
+
+
+class NSObject {
+}
+
+class Progress : NSObject {
+
+}
+
+class FileManager : NSObject {
+ func unzipItem(at sourceURL: URL, to destinationURL: URL, skipCRC32: Bool = false,
+ progress: Progress? = nil, pathEncoding: String.Encoding? = nil) throws {}
+}
+
+protocol DataProtocol { }
+class Data : DataProtocol {
+ struct ReadingOptions : OptionSet { let rawValue: Int }
+ struct WritingOptions : OptionSet { let rawValue: Int }
+
+ init(_ elements: S) { count = 0 }
+ init(contentsOf: URL, options: ReadingOptions) { count = 0 }
+ func write(to: URL, options: Data.WritingOptions = []) {}
+
+ var count: Int
+}
+
+extension String {
+
+ struct Encoding {
+ var rawValue: UInt
+
+ init(rawValue: UInt) { self.rawValue = rawValue }
+
+ static let ascii = Encoding(rawValue: 1)
+ }
+ init(contentsOf url: URL) throws {
+ self.init("")
+ }
+}
+
+// --- tests ---
+
+func testCommandInjectionQhelpExamples() {
+ guard let remoteURL = URL(string: "https://example.com/") else {
+ return
+ }
+
+ let source = URL(fileURLWithPath: "/sourcePath")
+ let destination = URL(fileURLWithPath: "/destination")
+
+ try Data(contentsOf: remoteURL, options: []).write(to: source)
+ do {
+ try Zip.unzipFile(source, destination: destination, overwrite: true, password: nil) // BAD
+
+ let fileManager = FileManager()
+ try fileManager.unzipItem(at: source, to: destination) // BAD
+ } catch {
+ print("Error: \(error)")
+ }
+}
\ No newline at end of file
From 96f8a02a7280f105bbf6ea12f7698dd43aecd716 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 15 Jan 2024 13:00:39 +0100
Subject: [PATCH 002/155] JS: Treat private-field methods as private
---
.../ql/lib/semmle/javascript/Classes.qll | 23 ++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/Classes.qll b/javascript/ql/lib/semmle/javascript/Classes.qll
index c7ad74561bba..f5877a78371b 100644
--- a/javascript/ql/lib/semmle/javascript/Classes.qll
+++ b/javascript/ql/lib/semmle/javascript/Classes.qll
@@ -516,16 +516,37 @@ class MemberDeclaration extends @property, Documentable {
*/
predicate hasPublicKeyword() { has_public_keyword(this) }
+ /**
+ * Holds if this member is considered private.
+ *
+ * This may occur in two cases:
+ * - it is a TypeScript member annotated with the `private` keyword, or
+ * - the member has a private name, such as `#foo`, referring to a private field in the class
+ */
+ predicate isPrivate() { this.hasPrivateKeyword() or this.hasPrivateFieldName() }
+
/**
* Holds if this is a TypeScript member annotated with the `private` keyword.
*/
- predicate isPrivate() { has_private_keyword(this) }
+ predicate hasPrivateKeyword() { has_private_keyword(this) }
/**
* Holds if this is a TypeScript member annotated with the `protected` keyword.
*/
predicate isProtected() { has_protected_keyword(this) }
+ /**
+ * Holds if the member has a private name, such as `#foo`, referring to a private field in the class.
+ *
+ * For example:
+ * ```js
+ * class Foo {
+ * #method() {}
+ * }
+ * ```
+ */
+ predicate hasPrivateFieldName() { this.getNameExpr().(Label).getName().charAt(0) = "#" }
+
/**
* Gets the expression specifying the name of this member,
* or nothing if this is a call signature.
From ddbacc3d4a4d589765668f74778737766637f313 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Fri, 26 Jan 2024 11:11:01 +0100
Subject: [PATCH 003/155] Shared: add test case for stateful outBarrier bug
---
.../library-tests/dataflow/inoutbarriers/A.java | 11 +++++++++++
.../dataflow/inoutbarriers/test.expected | 14 +++++++++-----
.../library-tests/dataflow/inoutbarriers/test.ql | 5 +++++
3 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/java/ql/test/library-tests/dataflow/inoutbarriers/A.java b/java/ql/test/library-tests/dataflow/inoutbarriers/A.java
index 51604991371b..692077018371 100644
--- a/java/ql/test/library-tests/dataflow/inoutbarriers/A.java
+++ b/java/ql/test/library-tests/dataflow/inoutbarriers/A.java
@@ -1,10 +1,17 @@
class A {
static String fsrc = "";
+ String fsink = "";
String src(String s) { return s; }
void sink(String s) { }
+ static String flowThroughSink(String s) {
+ A obj = new A();
+ obj.fsink = s;
+ return obj.fsink;
+ }
+
void foo() {
String s = fsrc;
sink(fsrc);
@@ -13,5 +20,9 @@ void foo() {
sink(s);
sink(s);
+
+ s = fsrc;
+ s = flowThroughSink(s);
+ sink(s);
}
}
diff --git a/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected b/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected
index de785df0a1d4..8d5a58661ab4 100644
--- a/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected
+++ b/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected
@@ -1,7 +1,11 @@
inconsistentFlow
+| A.java:24:9:24:12 | fsrc | A.java:26:10:26:10 | s | spurious state-flow in configuration both |
+| A.java:24:9:24:12 | fsrc | A.java:26:10:26:10 | s | spurious state-flow in configuration sinkbarrier |
#select
-| A.java:9:16:9:19 | fsrc | A.java:13:10:13:10 | s | nobarrier, sinkbarrier |
-| A.java:9:16:9:19 | fsrc | A.java:15:10:15:10 | s | nobarrier |
-| A.java:10:10:10:13 | fsrc | A.java:10:10:10:13 | fsrc | both, nobarrier, sinkbarrier, srcbarrier |
-| A.java:12:9:12:14 | src(...) | A.java:13:10:13:10 | s | both, nobarrier, sinkbarrier, srcbarrier |
-| A.java:12:9:12:14 | src(...) | A.java:15:10:15:10 | s | nobarrier, srcbarrier |
+| A.java:16:16:16:19 | fsrc | A.java:20:10:20:10 | s | nobarrier, sinkbarrier |
+| A.java:16:16:16:19 | fsrc | A.java:22:10:22:10 | s | nobarrier |
+| A.java:17:10:17:13 | fsrc | A.java:17:10:17:13 | fsrc | both, nobarrier, sinkbarrier, srcbarrier |
+| A.java:19:9:19:14 | src(...) | A.java:20:10:20:10 | s | both, nobarrier, sinkbarrier, srcbarrier |
+| A.java:19:9:19:14 | src(...) | A.java:22:10:22:10 | s | nobarrier, srcbarrier |
+| A.java:24:9:24:12 | fsrc | A.java:11:17:11:17 | s | both, nobarrier, sinkbarrier, srcbarrier |
+| A.java:24:9:24:12 | fsrc | A.java:26:10:26:10 | s | nobarrier, srcbarrier |
diff --git a/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql b/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql
index 82d35f0483cc..7972386a2dca 100644
--- a/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql
+++ b/java/ql/test/library-tests/dataflow/inoutbarriers/test.ql
@@ -12,6 +12,11 @@ predicate sink0(Node n) {
sink.getMethod().hasName("sink") and
sink.getAnArgument() = n.asExpr()
)
+ or
+ exists(AssignExpr assign |
+ assign.getDest().(FieldAccess).getField().hasName("fsink") and
+ n.asExpr() = assign.getSource()
+ )
}
module Conf1 implements ConfigSig {
From d1310c74fcb33f1ad789796c2176e383d372338a Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 25 Jan 2024 14:27:54 +0100
Subject: [PATCH 004/155] Shared: remove old stateful outBarrier check
---
.../codeql/dataflow/internal/DataFlowImpl.qll | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
index 27aa1d38e6e4..7a5f76715e5a 100644
--- a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
+++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
@@ -3773,14 +3773,11 @@ module MakeImpl {
}
override PathNodeImpl getASuccessorImpl() {
- not outBarrier(node, state) and
- (
- // an intermediate step to another intermediate node
- result = this.getSuccMid()
- or
- // a final step to a sink
- result = this.getSuccMid().projectToSink()
- )
+ // an intermediate step to another intermediate node
+ result = this.getSuccMid()
+ or
+ // a final step to a sink
+ result = this.getSuccMid().projectToSink()
}
override predicate isSource() {
From f15ead613023ff9ee42a3cce43f74592a6d5c1e5 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 25 Jan 2024 14:28:06 +0100
Subject: [PATCH 005/155] Shared: check stateful outBarrier as part of pathStep
SCC
---
.../codeql/dataflow/internal/DataFlowImpl.qll | 20 ++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
index 7a5f76715e5a..0708bf301af9 100644
--- a/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
+++ b/shared/dataflow/codeql/dataflow/internal/DataFlowImpl.qll
@@ -2682,6 +2682,7 @@ module MakeImpl {
) {
not isUnreachableInCall1(node2, cc) and
not inBarrier(node2, state) and
+ not outBarrier(node1, state) and
(
localFlowEntry(node1, pragma[only_bind_into](state)) and
(
@@ -3757,6 +3758,9 @@ module MakeImpl {
override NodeEx getNodeEx() { result = node }
+ pragma[inline]
+ final NodeEx getNodeExOutgoing() { result = node and not outBarrier(node, state) }
+
override FlowState getState() { result = state }
CallContext getCallContext() { result = cc }
@@ -3928,14 +3932,14 @@ module MakeImpl {
ap instanceof AccessPathNil
)
or
- jumpStepEx(mid.getNodeEx(), node) and
+ jumpStepEx(mid.getNodeExOutgoing(), node) and
state = mid.getState() and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
t = mid.getType() and
ap = mid.getAp()
or
- additionalJumpStep(mid.getNodeEx(), node) and
+ additionalJumpStep(mid.getNodeExOutgoing(), node) and
state = mid.getState() and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
@@ -3943,7 +3947,7 @@ module MakeImpl {
t = node.getDataFlowType() and
ap = TAccessPathNil()
or
- additionalJumpStateStep(mid.getNodeEx(), mid.getState(), node, state) and
+ additionalJumpStateStep(mid.getNodeExOutgoing(), mid.getState(), node, state) and
cc instanceof CallContextAny and
sc instanceof SummaryCtxNone and
mid.getAp() instanceof AccessPathNil and
@@ -3978,7 +3982,7 @@ module MakeImpl {
) {
ap0 = mid.getAp() and
c = ap0.getHead() and
- Stage5::readStepCand(mid.getNodeEx(), c, node) and
+ Stage5::readStepCand(mid.getNodeExOutgoing(), c, node) and
state = mid.getState() and
cc = mid.getCallContext()
}
@@ -3991,7 +3995,7 @@ module MakeImpl {
exists(DataFlowType contentType |
t0 = mid.getType() and
ap0 = mid.getAp() and
- Stage5::storeStepCand(mid.getNodeEx(), _, c, node, contentType, t) and
+ Stage5::storeStepCand(mid.getNodeExOutgoing(), _, c, node, contentType, t) and
state = mid.getState() and
cc = mid.getCallContext() and
compatibleTypes(t0, contentType)
@@ -4009,7 +4013,8 @@ module MakeImpl {
not outBarrier(retNode, state) and
innercc = mid.getCallContext() and
innercc instanceof CallContextNoCall and
- apa = mid.getAp().getApprox()
+ apa = mid.getAp().getApprox() and
+ not outBarrier(retNode, state)
)
}
@@ -4130,7 +4135,8 @@ module MakeImpl {
pathNode(_, ret, state, cc, sc, t, ap, _) and
kind = ret.getKind() and
apa = ap.getApprox() and
- parameterFlowThroughAllowed(sc.getParamNode(), kind)
+ parameterFlowThroughAllowed(sc.getParamNode(), kind) and
+ not outBarrier(ret, state)
)
}
From ee8e9a4e66b976aa98e28464bfe3a61fb84f9e0d Mon Sep 17 00:00:00 2001
From: Asger F
Date: Fri, 26 Jan 2024 11:13:44 +0100
Subject: [PATCH 006/155] Shared: update test output
---
java/ql/test/library-tests/dataflow/inoutbarriers/test.expected | 2 --
1 file changed, 2 deletions(-)
diff --git a/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected b/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected
index 8d5a58661ab4..90f99161585a 100644
--- a/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected
+++ b/java/ql/test/library-tests/dataflow/inoutbarriers/test.expected
@@ -1,6 +1,4 @@
inconsistentFlow
-| A.java:24:9:24:12 | fsrc | A.java:26:10:26:10 | s | spurious state-flow in configuration both |
-| A.java:24:9:24:12 | fsrc | A.java:26:10:26:10 | s | spurious state-flow in configuration sinkbarrier |
#select
| A.java:16:16:16:19 | fsrc | A.java:20:10:20:10 | s | nobarrier, sinkbarrier |
| A.java:16:16:16:19 | fsrc | A.java:22:10:22:10 | s | nobarrier |
From 19cb7adb6db17a3131b7db93482abc6a0d93ceff Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Thu, 20 Apr 2023 11:42:11 +0200
Subject: [PATCH 007/155] Migrate path injection sinks to MaD
Deprecate and stop using PathCreation
Path creation sinks are now summaries
---
.../2023-04-20-deprecated-path-creation.md | 4 +
java/ql/lib/ext/java.io.model.yml | 7 +-
java/ql/lib/ext/java.nio.file.model.yml | 12 +-
.../code/java/security/PathCreation.qll | 26 +-
.../code/java/security/TaintedPathQuery.qll | 11 +-
.../src/Security/CWE/CWE-022/TaintedPath.ql | 18 +-
.../Security/CWE/CWE-022/TaintedPathLocal.ql | 18 +-
...04-20-path-injection-precision-improved.md | 4 +
.../Security/CWE/CWE-073/FilePathInjection.ql | 8 +
.../pathcreation/PathCreation.expected | 1 +
.../CWE-022/semmle/tests/TaintedPath.ql | 11 +
.../CWE-022/semmle/tests/TaintedPath.qlref | 1 -
.../security/CWE-022/semmle/tests/Test.java | 260 +++++++++++-------
.../CWE-022/semmle/tests/mad/Test.java | 228 ---------------
14 files changed, 219 insertions(+), 390 deletions(-)
create mode 100644 java/ql/lib/change-notes/2023-04-20-deprecated-path-creation.md
create mode 100644 java/ql/src/change-notes/2023-04-20-path-injection-precision-improved.md
create mode 100644 java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql
delete mode 100644 java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.qlref
delete mode 100644 java/ql/test/query-tests/security/CWE-022/semmle/tests/mad/Test.java
diff --git a/java/ql/lib/change-notes/2023-04-20-deprecated-path-creation.md b/java/ql/lib/change-notes/2023-04-20-deprecated-path-creation.md
new file mode 100644
index 000000000000..c955a459ca08
--- /dev/null
+++ b/java/ql/lib/change-notes/2023-04-20-deprecated-path-creation.md
@@ -0,0 +1,4 @@
+---
+category: deprecated
+---
+* The `PathCreation` class in `PathCreation.qll` has been deprecated.
diff --git a/java/ql/lib/ext/java.io.model.yml b/java/ql/lib/ext/java.io.model.yml
index 1bd9251c29d8..17dbc1464dc4 100644
--- a/java/ql/lib/ext/java.io.model.yml
+++ b/java/ql/lib/ext/java.io.model.yml
@@ -3,18 +3,17 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
- - ["java.io", "File", False, "File", "(File,String)", "", "Argument[1]", "path-injection", "manual"] # old PathCreation
- - ["java.io", "File", False, "File", "(String)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation
- - ["java.io", "File", False, "File", "(String,String)", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation
- - ["java.io", "File", False, "File", "(URI)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation
- ["java.io", "File", True, "createNewFile", "()", "", "Argument[this]", "path-injection", "ai-manual"]
- ["java.io", "File", True, "createTempFile", "(String,String,File)", "", "Argument[2]", "path-injection", "ai-manual"]
- ["java.io", "File", True, "renameTo", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileInputStream", True, "FileInputStream", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.io", "FileInputStream", True, "FileInputStream", "(FileDescriptor)", "", "Argument[0]", "path-injection", "manual"]
- ["java.io", "FileInputStream", True, "FileInputStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileOutputStream", False, "FileOutputStream", "", "", "Argument[0]", "path-injection", "manual"]
- ["java.io", "FileOutputStream", False, "write", "", "", "Argument[0]", "file-content-store", "manual"]
- ["java.io", "FileReader", True, "FileReader", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.io", "FileReader", True, "FileReader", "(FileDescriptor)", "", "Argument[0]", "path-injection", "manual"]
+ - ["java.io", "FileReader", True, "FileReader", "(File,Charset)", "", "Argument[0]", "path-injection", "manual"]
- ["java.io", "FileReader", True, "FileReader", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileReader", True, "FileReader", "(String,Charset)", "", "Argument[0]", "path-injection", "manual"]
- ["java.io", "FileSystem", True, "createDirectory", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
diff --git a/java/ql/lib/ext/java.nio.file.model.yml b/java/ql/lib/ext/java.nio.file.model.yml
index 3c77c876eee1..a35c575e9cb5 100644
--- a/java/ql/lib/ext/java.nio.file.model.yml
+++ b/java/ql/lib/ext/java.nio.file.model.yml
@@ -37,15 +37,8 @@ extensions:
- ["java.nio.file", "Files", False, "write", "", "", "Argument[1]", "file-content-store", "manual"]
- ["java.nio.file", "Files", False, "writeString", "", "", "Argument[0]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "writeString", "", "", "Argument[1]", "file-content-store", "manual"]
- - ["java.nio.file", "FileSystem", False, "getPath", "", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation
- ["java.nio.file", "FileSystems", False, "newFileSystem", "(URI,Map)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "FileSystems", False, "newFileSystem", "(URI,Map)", "", "Argument[0]", "request-forgery", "ai-manual"]
- - ["java.nio.file", "Path", False, "of", "(String,String[])", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation
- - ["java.nio.file", "Path", False, "of", "(URI)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation
- - ["java.nio.file", "Path", False, "resolve", "(String)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation
- - ["java.nio.file", "Path", False, "resolveSibling", "(String)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation
- - ["java.nio.file", "Paths", False, "get", "(String,String[])", "", "Argument[0..1]", "path-injection", "manual"] # old PathCreation
- - ["java.nio.file", "Paths", False, "get", "(URI)", "", "Argument[0]", "path-injection", "manual"] # old PathCreation
- ["java.nio.file", "SecureDirectoryStream", True, "deleteDirectory", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "SecureDirectoryStream", True, "deleteFile", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- addsTo:
@@ -63,7 +56,7 @@ extensions:
- ["java.nio.file", "Files", True, "newDirectoryStream", "(Path,DirectoryStream$Filter)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Files", True, "newDirectoryStream", "(Path)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Files", True, "walk", "(Path,FileVisitOption[])", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- - ["java.nio.file", "FileSystem", True, "getPath", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
+ - ["java.nio.file", "FileSystem", True, "getPath", "(String,String[])", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "FileSystem", True, "getPath", "(String,String[])", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "FileSystem", True, "getPathMatcher", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "FileSystem", True, "getRootDirectories", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
@@ -76,7 +69,8 @@ extensions:
- ["java.nio.file", "Path", True, "relativize", "(Path)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Path", True, "resolve", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "resolve", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- - ["java.nio.file", "Path", True, "resolveSibling", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
+ - ["java.nio.file", "Path", True, "resolveSibling", "", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
+ - ["java.nio.file", "Path", True, "resolveSibling", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toAbsolutePath", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", False, "toFile", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
diff --git a/java/ql/lib/semmle/code/java/security/PathCreation.qll b/java/ql/lib/semmle/code/java/security/PathCreation.qll
index 924d42674fbb..3d40a1d4fdb5 100644
--- a/java/ql/lib/semmle/code/java/security/PathCreation.qll
+++ b/java/ql/lib/semmle/code/java/security/PathCreation.qll
@@ -1,11 +1,13 @@
/**
+ * DEPRECATED.
+ *
* Models the different ways to create paths. Either by using `java.io.File`-related APIs or `java.nio.file.Path`-related APIs.
*/
import java
-/** Models the creation of a path. */
-abstract class PathCreation extends Expr {
+/** DEPRECATED: Models the creation of a path. */
+abstract deprecated class PathCreation extends Expr {
/**
* Gets an input that is used in the creation of this path.
* This excludes inputs of type `File` and `Path`.
@@ -14,7 +16,7 @@ abstract class PathCreation extends Expr {
}
/** Models the `java.nio.file.Paths.get` method. */
-private class PathsGet extends PathCreation, MethodCall {
+deprecated private class PathsGet extends PathCreation, MethodCall {
PathsGet() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePaths and
@@ -26,7 +28,7 @@ private class PathsGet extends PathCreation, MethodCall {
}
/** Models the `java.nio.file.FileSystem.getPath` method. */
-private class FileSystemGetPath extends PathCreation, MethodCall {
+deprecated private class FileSystemGetPath extends PathCreation, MethodCall {
FileSystemGetPath() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypeFileSystem and
@@ -38,7 +40,7 @@ private class FileSystemGetPath extends PathCreation, MethodCall {
}
/** Models the `new java.io.File(...)` constructor. */
-private class FileCreation extends PathCreation, ClassInstanceExpr {
+deprecated private class FileCreation extends PathCreation, ClassInstanceExpr {
FileCreation() { this.getConstructedType() instanceof TypeFile }
override Expr getAnInput() {
@@ -49,7 +51,7 @@ private class FileCreation extends PathCreation, ClassInstanceExpr {
}
/** Models the `java.nio.file.Path.resolveSibling` method. */
-private class PathResolveSiblingCreation extends PathCreation, MethodCall {
+deprecated private class PathResolveSiblingCreation extends PathCreation, MethodCall {
PathResolveSiblingCreation() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePath and
@@ -65,7 +67,7 @@ private class PathResolveSiblingCreation extends PathCreation, MethodCall {
}
/** Models the `java.nio.file.Path.resolve` method. */
-private class PathResolveCreation extends PathCreation, MethodCall {
+deprecated private class PathResolveCreation extends PathCreation, MethodCall {
PathResolveCreation() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePath and
@@ -81,7 +83,7 @@ private class PathResolveCreation extends PathCreation, MethodCall {
}
/** Models the `java.nio.file.Path.of` method. */
-private class PathOfCreation extends PathCreation, MethodCall {
+deprecated private class PathOfCreation extends PathCreation, MethodCall {
PathOfCreation() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePath and
@@ -93,7 +95,7 @@ private class PathOfCreation extends PathCreation, MethodCall {
}
/** Models the `new java.io.FileWriter(...)` constructor. */
-private class FileWriterCreation extends PathCreation, ClassInstanceExpr {
+deprecated private class FileWriterCreation extends PathCreation, ClassInstanceExpr {
FileWriterCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileWriter") }
override Expr getAnInput() {
@@ -104,7 +106,7 @@ private class FileWriterCreation extends PathCreation, ClassInstanceExpr {
}
/** Models the `new java.io.FileReader(...)` constructor. */
-private class FileReaderCreation extends PathCreation, ClassInstanceExpr {
+deprecated private class FileReaderCreation extends PathCreation, ClassInstanceExpr {
FileReaderCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileReader") }
override Expr getAnInput() {
@@ -115,7 +117,7 @@ private class FileReaderCreation extends PathCreation, ClassInstanceExpr {
}
/** Models the `new java.io.FileInputStream(...)` constructor. */
-private class FileInputStreamCreation extends PathCreation, ClassInstanceExpr {
+deprecated private class FileInputStreamCreation extends PathCreation, ClassInstanceExpr {
FileInputStreamCreation() {
this.getConstructedType().hasQualifiedName("java.io", "FileInputStream")
}
@@ -128,7 +130,7 @@ private class FileInputStreamCreation extends PathCreation, ClassInstanceExpr {
}
/** Models the `new java.io.FileOutputStream(...)` constructor. */
-private class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr {
+deprecated private class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr {
FileOutputStreamCreation() {
this.getConstructedType().hasQualifiedName("java.io", "FileOutputStream")
}
diff --git a/java/ql/lib/semmle/code/java/security/TaintedPathQuery.qll b/java/ql/lib/semmle/code/java/security/TaintedPathQuery.qll
index 85265f6b169b..63bd4949699a 100644
--- a/java/ql/lib/semmle/code/java/security/TaintedPathQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/TaintedPathQuery.qll
@@ -8,6 +8,13 @@ private import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.security.PathSanitizer
private import semmle.code.java.security.Sanitizers
+/** A sink for tainted path flow configurations. */
+abstract class TaintedPathSink extends DataFlow::Node { }
+
+private class DefaultTaintedPathSink extends TaintedPathSink {
+ DefaultTaintedPathSink() { sinkNode(this, "path-injection") }
+}
+
/**
* A unit class for adding additional taint steps.
*
@@ -55,7 +62,7 @@ private class TaintPreservingUriCtorParam extends Parameter {
module TaintedPathConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
- predicate isSink(DataFlow::Node sink) { sinkNode(sink, "path-injection") }
+ predicate isSink(DataFlow::Node sink) { sink instanceof TaintedPathSink }
predicate isBarrier(DataFlow::Node sanitizer) {
sanitizer instanceof SimpleTypeSanitizer or
@@ -76,7 +83,7 @@ module TaintedPathFlow = TaintTracking::Global;
module TaintedPathLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
- predicate isSink(DataFlow::Node sink) { sinkNode(sink, "path-injection") }
+ predicate isSink(DataFlow::Node sink) { sink instanceof TaintedPathSink }
predicate isBarrier(DataFlow::Node sanitizer) {
sanitizer instanceof SimpleTypeSanitizer or
diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql
index 96e8e66c7cdb..3963442d6489 100644
--- a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql
+++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql
@@ -18,21 +18,7 @@ import semmle.code.java.security.PathCreation
import semmle.code.java.security.TaintedPathQuery
import TaintedPathFlow::PathGraph
-/**
- * Gets the data-flow node at which to report a path ending at `sink`.
- *
- * Previously this query flagged alerts exclusively at `PathCreation` sites,
- * so to avoid perturbing existing alerts, where a `PathCreation` exists we
- * continue to report there; otherwise we report directly at `sink`.
- */
-DataFlow::Node getReportingNode(DataFlow::Node sink) {
- TaintedPathFlow::flowTo(sink) and
- if exists(PathCreation pc | pc.getAnInput() = sink.asExpr())
- then result.asExpr() = any(PathCreation pc | pc.getAnInput() = sink.asExpr())
- else result = sink
-}
-
from TaintedPathFlow::PathNode source, TaintedPathFlow::PathNode sink
where TaintedPathFlow::flowPath(source, sink)
-select getReportingNode(sink.getNode()), source, sink, "This path depends on a $@.",
- source.getNode(), "user-provided value"
+select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(),
+ "user-provided value"
diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql
index 8e56121883f5..60dc6b54be85 100644
--- a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql
+++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql
@@ -18,21 +18,7 @@ import semmle.code.java.security.PathCreation
import semmle.code.java.security.TaintedPathQuery
import TaintedPathLocalFlow::PathGraph
-/**
- * Gets the data-flow node at which to report a path ending at `sink`.
- *
- * Previously this query flagged alerts exclusively at `PathCreation` sites,
- * so to avoid perturbing existing alerts, where a `PathCreation` exists we
- * continue to report there; otherwise we report directly at `sink`.
- */
-DataFlow::Node getReportingNode(DataFlow::Node sink) {
- TaintedPathLocalFlow::flowTo(sink) and
- if exists(PathCreation pc | pc.getAnInput() = sink.asExpr())
- then result.asExpr() = any(PathCreation pc | pc.getAnInput() = sink.asExpr())
- else result = sink
-}
-
from TaintedPathLocalFlow::PathNode source, TaintedPathLocalFlow::PathNode sink
where TaintedPathLocalFlow::flowPath(source, sink)
-select getReportingNode(sink.getNode()), source, sink, "This path depends on a $@.",
- source.getNode(), "user-provided value"
+select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(),
+ "user-provided value"
diff --git a/java/ql/src/change-notes/2023-04-20-path-injection-precision-improved.md b/java/ql/src/change-notes/2023-04-20-path-injection-precision-improved.md
new file mode 100644
index 000000000000..763cedea45da
--- /dev/null
+++ b/java/ql/src/change-notes/2023-04-20-path-injection-precision-improved.md
@@ -0,0 +1,4 @@
+---
+category: majorAnalysis
+---
+* The sinks of the queries `java/path-injection` and `java/path-injection-local` have been reworked. Path creation sinks have been converted to summaries instead, while sinks now are actual file read/write operations only. This has reduced the false positive ratio of both queries.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql
index d0b59bf1136d..7f6528a66708 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql
@@ -16,6 +16,10 @@ import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.FlowSources
+<<<<<<< HEAD
+=======
+import semmle.code.java.security.TaintedPathQuery
+>>>>>>> 9e469c9c32 (Migrate path injection sinks to MaD)
import JFinalController
import semmle.code.java.security.PathSanitizer
private import semmle.code.java.security.Sanitizers
@@ -52,7 +56,11 @@ module InjectFilePathConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
predicate isSink(DataFlow::Node sink) {
+<<<<<<< HEAD
sinkNode(sink, "path-injection") and
+=======
+ sink instanceof TaintedPathSink and
+>>>>>>> 9e469c9c32 (Migrate path injection sinks to MaD)
not sink instanceof NormalizedPathNode
}
diff --git a/java/ql/test/library-tests/pathcreation/PathCreation.expected b/java/ql/test/library-tests/pathcreation/PathCreation.expected
index c0ac69c7da42..41e10fb6aaf1 100644
--- a/java/ql/test/library-tests/pathcreation/PathCreation.expected
+++ b/java/ql/test/library-tests/pathcreation/PathCreation.expected
@@ -1,3 +1,4 @@
+WARNING: Type PathCreation has been deprecated and may be removed in future (PathCreation.ql:4,6-18)
| PathCreation.java:13:18:13:32 | new File(...) | PathCreation.java:13:27:13:31 | "dir" |
| PathCreation.java:14:19:14:40 | new File(...) | PathCreation.java:14:28:14:32 | "dir" |
| PathCreation.java:14:19:14:40 | new File(...) | PathCreation.java:14:35:14:39 | "sub" |
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql
new file mode 100644
index 000000000000..e17123ce7810
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql
@@ -0,0 +1,11 @@
+import java
+import TestUtilities.InlineFlowTest
+import semmle.code.java.security.TaintedPathQuery
+
+class HasFlowTest extends InlineFlowTest {
+ override predicate hasTaintFlow(DataFlow::Node src, DataFlow::Node sink) {
+ TaintedPathFlow::flow(src, sink)
+ }
+
+ override predicate hasValueFlow(DataFlow::Node src, DataFlow::Node sink) { none() }
+}
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.qlref b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.qlref
deleted file mode 100644
index 1677939387da..000000000000
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-022/TaintedPath.ql
\ No newline at end of file
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java
index 080cc263f085..872f2a01b65f 100644
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java
+++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java
@@ -1,112 +1,168 @@
-// Semmle test case for CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
-// http://cwe.mitre.org/data/definitions/22.html
-package test.cwe22.semmle.tests;
-
-import javax.servlet.http.*;
-import javax.servlet.ServletException;
-
-import java.io.*;
-import java.net.*;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.FileSystems;
-
-import org.apache.commons.io.output.LockableFileWriter;
-
-class Test {
- void doGet1(InetAddress address)
- throws IOException {
- String temp = address.getHostName();
- File file;
- Path path;
-
- // BAD: construct a file path with user input
- file = new File(temp);
-
- // BAD: construct a path with user input
- path = Paths.get(temp);
-
- // BAD: construct a path with user input
- path = FileSystems.getDefault().getPath(temp);
-
- // BAD: insufficient check
- if (temp.startsWith("/some_safe_dir/")) {
- file = new File(temp);
- }
- }
-
- void doGet2(InetAddress address)
- throws IOException {
- String temp = address.getHostName();
- File file;
-
- // GOOD: check string is safe
- if(isSafe(temp))
- file = new File(temp);
- }
-
- void doGet3(InetAddress address)
- throws IOException {
- String temp = address.getHostName();
- File file;
-
- // FALSE NEGATIVE: inadequate check - fails to account
- // for '.'s
- if(isSortOfSafe(temp))
- file = new File(temp);
- }
+import javax.xml.transform.stream.StreamResult;
+import org.apache.commons.io.FileUtils;
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.taskdefs.Copy;
+import org.apache.tools.ant.taskdefs.Expand;
+import org.apache.tools.ant.types.FileSet;
+import org.codehaus.cargo.container.installer.ZipURLInstaller;
+import org.kohsuke.stapler.framework.io.LargeText;
+import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
+import org.springframework.util.FileCopyUtils;
+
+public class Test {
+
+ private InetAddress address;
+
+ public Object source() {
+ return address.getHostName();
+ }
- boolean isSafe(String pathSpec) {
- // no file separators
- if (pathSpec.contains(File.separator))
- return false;
- // at most one dot
- int indexOfDot = pathSpec.indexOf('.');
- if (indexOfDot != -1 && pathSpec.indexOf('.', indexOfDot + 1) != -1)
- return false;
- return true;
- }
+ void test() throws IOException {
+ // "java.lang;Module;true;getResourceAsStream;(String);;Argument[0];read-file;ai-generated"
+ getClass().getModule().getResourceAsStream((String) source()); // $ hasTaintFlow
+ // "java.lang;Class;false;getResource;(String);;Argument[0];read-file;ai-generated"
+ getClass().getResource((String) source()); // $ hasTaintFlow
+ // "java.lang;ClassLoader;true;getSystemResourceAsStream;(String);;Argument[0];read-file;ai-generated"
+ ClassLoader.getSystemResourceAsStream((String) source()); // $ hasTaintFlow
+ // "java.io;File;true;createTempFile;(String,String,File);;Argument[2];create-file;ai-generated"
+ File.createTempFile(";", ";", (File) source()); // $ hasTaintFlow
+ // "java.io;File;true;renameTo;(File);;Argument[0];create-file;ai-generated"
+ new File("").renameTo((File) source()); // $ hasTaintFlow
+ // "java.io;FileInputStream;true;FileInputStream;(File);;Argument[0];read-file;ai-generated"
+ new FileInputStream((File) source()); // $ hasTaintFlow
+ // "java.io;FileInputStream;true;FileInputStream;(FileDescriptor);;Argument[0];read-file;manual"
+ new FileInputStream((FileDescriptor) source()); // $ hasTaintFlow
+ // "java.io;FileInputStream;true;FileInputStream;(Strrirng);;Argument[0];read-file;manual"
+ new FileInputStream((String) source()); // $ hasTaintFlow
+ // "java.io;FileReader;true;FileReader;(File);;Argument[0];read-file;ai-generated"
+ new FileReader((File) source()); // $ hasTaintFlow
+ // "java.io;FileReader;true;FileReader;(FileDescriptor);;Argument[0];read-file;manual"
+ new FileReader((FileDescriptor) source()); // $ hasTaintFlow
+ // "java.io;FileReader;true;FileReader;(File,Charset);;Argument[0];read-file;manual"
+ new FileReader((File) source(), null); // $ hasTaintFlow
+ // "java.io;FileReader;true;FileReader;(String);;Argument[0];read-file;ai-generated"
+ new FileReader((String) source()); // $ hasTaintFlow
+ // "java.io;FileReader;true;FileReader;(String,Charset);;Argument[0];read-file;manual"
+ new FileReader((String) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;copy;;;Argument[0];read-file;manual"
+ Files.copy((Path) source(), (Path) null); // $ hasTaintFlow
+ Files.copy((Path) source(), (OutputStream) null); // $ hasTaintFlow
+ Files.copy((InputStream) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;copy;;;Argument[1];create-file;manual"
+ Files.copy((Path) null, (Path) source()); // $ hasTaintFlow
+ Files.copy((Path) null, (OutputStream) source()); // $ hasTaintFlow
+ Files.copy((InputStream) null, (Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createDirectories;;;Argument[0];create-file;manual"
+ Files.createDirectories((Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createDirectory;;;Argument[0];create-file;manual"
+ Files.createDirectory((Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createFile;;;Argument[0];create-file;manual"
+ Files.createFile((Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createLink;;;Argument[0];create-file;manual"
+ Files.createLink((Path) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file;manual"
+ Files.createSymbolicLink((Path) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createTempDirectory;(Path,String,FileAttribute[]);;Argument[0];create-file;manual"
+ Files.createTempDirectory((Path) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;createTempFile;(Path,String,String,FileAttribute[]);;Argument[0];create-file;manual"
+ Files.createTempFile((Path) source(), null, null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;delete;(Path);;Argument[0];delete-file;ai-generated"
+ Files.delete((Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;deleteIfExists;(Path);;Argument[0];delete-file;ai-generated"
+ Files.deleteIfExists((Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;lines;(Path,Charset);;Argument[0];read-file;ai-generated"
+ Files.lines((Path) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;move;;;Argument[1];create-file;manual"
+ Files.move(null, (Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;newBufferedReader;(Path,Charset);;Argument[0];read-file;ai-generated"
+ Files.newBufferedReader((Path) source(), null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;newBufferedWriter;;;Argument[0];create-file;manual"
+ Files.newBufferedWriter((Path) source()); // $ hasTaintFlow
+ Files.newBufferedWriter((Path) source(), (Charset) null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file;manual"
+ Files.newOutputStream((Path) source()); // $ hasTaintFlow
+ // "java.nio.file;Files;false;write;;;Argument[0];create-file;manual"
+ Files.write((Path) source(), (byte[]) null); // $ hasTaintFlow
+ Files.write((Path) source(), (Iterable) null); // $ hasTaintFlow
+ Files.write((Path) source(), (Iterable) null, (Charset) null); // $ hasTaintFlow
+ // "java.nio.file;Files;false;writeString;;;Argument[0];create-file;manual"
+ Files.writeString((Path) source(), (CharSequence) null); // $ hasTaintFlow
+ Files.writeString((Path) source(), (CharSequence) null, (Charset) null); // $ hasTaintFlow
+ // "javax.xml.transform.stream;StreamResult";true;"StreamResult;(File);;Argument[0];create-file;ai-generated"
+ new StreamResult((File) source()); // $ hasTaintFlow
+ // "org.apache.commons.io;FileUtils;true;openInputStream;(File);;Argument[0];read-file;ai-generated"
+ FileUtils.openInputStream((File) source()); // $ hasTaintFlow
+ // "org.codehaus.cargo.container.installer;ZipURLInstaller;true;ZipURLInstaller;(URL,String,String);;Argument[1];create-file;ai-generated"
+ new ZipURLInstaller((URL) null, (String) source(), ""); // $ hasTaintFlow
+ // "org.codehaus.cargo.container.installer;ZipURLInstaller;true;ZipURLInstaller;(URL,String,String);;Argument[2];create-file;ai-generated"
+ new ZipURLInstaller((URL) null, "", (String) source()); // $ hasTaintFlow
+ // "org.springframework.util;FileCopyUtils;false;copy;(byte[],File);;Argument[1];create-file;manual"
+ FileCopyUtils.copy((byte[]) null, (File) source()); // $ hasTaintFlow
+ // "org.springframework.util;FileCopyUtils;false;copy;(File,File);;Argument[0];create-file;manual"
+ FileCopyUtils.copy((File) source(), null); // $ hasTaintFlow
+ // "org.springframework.util;FileCopyUtils;false;copy;(File,File);;Argument[1];create-file;manual"
+ FileCopyUtils.copy((File) null, (File) source()); // $ hasTaintFlow
+ }
- boolean isSortOfSafe(String pathSpec) {
- // no file separators
- if (pathSpec.contains(File.separator))
- return false;
- return true;
- }
+ void test(AntClassLoader acl) {
+ // "org.apache.tools.ant;AntClassLoader;true;addPathComponent;(File);;Argument[0];read-file;ai-generated"
+ acl.addPathComponent((File) source()); // $ hasTaintFlow
+ // "org.apache.tools.ant;AntClassLoader;true;AntClassLoader;(ClassLoader,Project,Path,boolean);;Argument[2];read-file;ai-generated"
+ new AntClassLoader(null, null, (org.apache.tools.ant.types.Path) source(), false); // $ hasTaintFlow
+ // "org.apache.tools.ant;AntClassLoader;true;AntClassLoader;(Project,Path,boolean);;Argument[1];read-file;ai-generated"
+ new AntClassLoader(null, (org.apache.tools.ant.types.Path) source(), false); // $ hasTaintFlow
+ // "org.apache.tools.ant;AntClassLoader;true;AntClassLoader;(Project,Path);;Argument[1];read-file;ai-generated"
+ new AntClassLoader(null, (org.apache.tools.ant.types.Path) source()); // $ hasTaintFlow
+ // "org.kohsuke.stapler.framework.io;LargeText;true;LargeText;(File,Charset,boolean,boolean);;Argument[0];read-file;ai-generated"
+ new LargeText((File) source(), null, false, false); // $ hasTaintFlow
+ }
- public class MyServlet extends HttpServlet {
- public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
- String filename = br.readLine();
- // BAD: construct a file path with user input
- BufferedWriter bw = new BufferedWriter(new FileWriter("dir/"+filename, true));
+ void doGet6(String root, InetAddress address) throws IOException {
+ String temp = address.getHostName();
+ // GOOD: Use `contains` and `startsWith` to check if the path is safe
+ if (!temp.contains("..") && temp.startsWith(root + "/")) {
+ File file = new File(temp);
}
}
- void doGet4(InetAddress address)
- throws IOException {
- String temp = address.getHostName();
- // BAD: open a file based on user input, using a MaD-documented API
- new LockableFileWriter(temp);
- }
+ void test(DirectoryScanner ds) {
+ // "org.apache.tools.ant;DirectoryScanner;true;setBasedir;(File);;Argument[0];read-file;ai-generated"
+ ds.setBasedir((File) source()); // $ hasTaintFlow
+ }
- void doGet5(InetAddress address)
- throws URISyntaxException {
- String t = address.getHostName();
- // BAD: construct a file path with user input
- new File(new URI(null, t, null));
- new File(new URI(t, t, null, t));
- new File(new URI(t, null, t, t));
- new File(new URI(null, null, t, null, null));
- new File(new URI(null, null, null, 0, t, null, null));
- }
+ void test(Copy cp) {
+ // "org.apache.tools.ant.taskdefs;Copy;true;addFileset;(FileSet);;Argument[0];read-file;ai-generated"
+ cp.addFileset((FileSet) source()); // $ hasTaintFlow
+ // "org.apache.tools.ant.taskdefs;Copy;true;setFile;(File);;Argument[0];read-file;ai-generated"
+ cp.setFile((File) source()); // $ hasTaintFlow
+ // "org.apache.tools.ant.taskdefs;Copy;true;setTodir;(File);;Argument[0];create-file;ai-generated"
+ cp.setTodir((File) source()); // $ hasTaintFlow
+ // "org.apache.tools.ant.taskdefs;Copy;true;setTofile;(File);;Argument[0];create-file;ai-generated"
+ cp.setTofile((File) source()); // $ hasTaintFlow
+ }
- void doGet6(String root, InetAddress address)
- throws IOException{
- String temp = address.getHostName();
- // GOOD: Use `contains` and `startsWith` to check if the path is safe
- if (!temp.contains("..") && temp.startsWith(root + "/")) {
- File file = new File(temp);
- }
- }
+ void test(Expand ex) {
+ // "org.apache.tools.ant.taskdefs;Expand;true;setDest;(File);;Argument[0];create-file;ai-generated"
+ ex.setDest((File) source()); // $ hasTaintFlow
+ // "org.apache.tools.ant.taskdefs;Expand;true;setSrc;(File);;Argument[0];read-file;ai-generated"
+ ex.setSrc((File) source()); // $ hasTaintFlow
+ }
+
+ void test(ChainedOptionsBuilder cob) {
+ // "org.openjdk.jmh.runner.options;ChainedOptionsBuilder;true;result;(String);;Argument[0];create-file;ai-generated"
+ cob.result((String) source()); // $ hasTaintFlow
+ }
}
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/mad/Test.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/mad/Test.java
deleted file mode 100644
index 169f3535c6b9..000000000000
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/mad/Test.java
+++ /dev/null
@@ -1,228 +0,0 @@
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import javax.activation.FileDataSource;
-import javax.xml.transform.stream.StreamResult;
-import org.apache.cxf.common.classloader.ClassLoaderUtils;
-import org.apache.cxf.common.jaxb.JAXBUtils;
-import org.apache.cxf.configuration.jsse.SSLUtils;
-import org.apache.cxf.resource.ExtendedURIResolver;
-import org.apache.cxf.resource.URIResolver;
-import org.apache.cxf.staxutils.StaxUtils;
-import org.apache.cxf.tools.corba.utils.FileOutputStreamFactory;
-import org.apache.cxf.tools.corba.utils.OutputStreamFactory;
-import org.apache.cxf.tools.util.FileWriterUtil;
-import org.apache.cxf.tools.util.OutputStreamCreator;
-import org.apache.commons.io.FileUtils;
-import org.apache.tools.ant.AntClassLoader;
-import org.apache.tools.ant.DirectoryScanner;
-import org.apache.tools.ant.taskdefs.Copy;
-import org.apache.tools.ant.taskdefs.Expand;
-import org.apache.tools.ant.types.FileSet;
-import org.codehaus.cargo.container.installer.ZipURLInstaller;
-import org.kohsuke.stapler.framework.io.LargeText;
-import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
-import org.springframework.util.FileCopyUtils;
-
-public class Test {
-
- private InetAddress address;
-
- public Object source() {
- return address.getHostName();
- }
-
- void test() throws IOException {
- // "java.lang;Module;true;getResourceAsStream;(String);;Argument[0];read-file;ai-generated"
- getClass().getModule().getResourceAsStream((String) source());
- // "java.lang;Class;false;getResource;(String);;Argument[0];read-file;ai-generated"
- getClass().getResource((String) source());
- // "java.lang;ClassLoader;true;getSystemResourceAsStream;(String);;Argument[0];read-file;ai-generated"
- ClassLoader.getSystemResourceAsStream((String) source());
- // "java.io;File;true;createTempFile;(String,String,File);;Argument[2];create-file;ai-generated"
- File.createTempFile(";", ";", (File) source());
- // "java.io;File;true;renameTo;(File);;Argument[0];create-file;ai-generated"
- new File("").renameTo((File) source());
- // "java.io;FileInputStream;true;FileInputStream;(File);;Argument[0];read-file;ai-generated"
- new FileInputStream((File) source());
- // "java.io;FileReader;true;FileReader;(File);;Argument[0];read-file;ai-generated"
- new FileReader((File) source());
- // "java.io;FileReader;true;FileReader;(String);;Argument[0];read-file;ai-generated"
- new FileReader((String) source());
- // "java.nio.file;Files;false;copy;(Path,OutputStream);;Argument[0];read-file;manual"
- Files.copy((Path) source(), (OutputStream) null);
- // "java.nio.file;Files;false;copy;(Path,Path,CopyOption[]);;Argument[0];read-file;manual"
- Files.copy((Path) source(), (Path) null);
- // "java.nio.file;Files;false;copy;(Path,Path,CopyOption[]);;Argument[1];create-file;manual"
- Files.copy((Path) null, (Path) source());
- // "java.nio.file;Files;false;copy;(InputStream,Path,CopyOption[]);;Argument[1];create-file;manual"
- Files.copy((InputStream) null, (Path) source());
- // "java.nio.file;Files;false;createDirectories;;;Argument[0];create-file;manual"
- Files.createDirectories((Path) source());
- // "java.nio.file;Files;false;createDirectory;;;Argument[0];create-file;manual"
- Files.createDirectory((Path) source());
- // "java.nio.file;Files;false;createFile;;;Argument[0];create-file;manual"
- Files.createFile((Path) source());
- // "java.nio.file;Files;false;createLink;;;Argument[0];create-file;manual"
- Files.createLink((Path) source(), null);
- // "java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file;manual"
- Files.createSymbolicLink((Path) source(), null);
- // "java.nio.file;Files;false;createTempDirectory;(Path,String,FileAttribute[]);;Argument[0];create-file;manual"
- Files.createTempDirectory((Path) source(), null);
- // "java.nio.file;Files;false;createTempFile;(Path,String,String,FileAttribute[]);;Argument[0];create-file;manual"
- Files.createTempFile((Path) source(), null, null);
- // "java.nio.file;Files;false;delete;(Path);;Argument[0];delete-file;ai-generated"
- Files.delete((Path) source());
- // "java.nio.file;Files;false;deleteIfExists;(Path);;Argument[0];delete-file;ai-generated"
- Files.deleteIfExists((Path) source());
- // "java.nio.file;Files;false;lines;(Path,Charset);;Argument[0];read-file;ai-generated"
- Files.lines((Path) source(), null);
- // "java.nio.file;Files;false;move;;;Argument[1];create-file;manual"
- Files.move(null, (Path) source());
- // "java.nio.file;Files;false;newBufferedReader;(Path,Charset);;Argument[0];read-file;ai-generated"
- Files.newBufferedReader((Path) source(), null);
- // "java.nio.file;Files;false;newBufferedWriter;;;Argument[0];create-file;manual"
- Files.newBufferedWriter((Path) source());
- Files.newBufferedWriter((Path) source(), (Charset) null);
- // "java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file;manual"
- Files.newOutputStream((Path) source());
- // "java.nio.file;Files;false;write;;;Argument[0];create-file;manual"
- Files.write((Path) source(), (byte[]) null);
- Files.write((Path) source(), (Iterable) null);
- Files.write((Path) source(), (Iterable) null, (Charset) null);
- // "java.nio.file;Files;false;writeString;;;Argument[0];create-file;manual"
- Files.writeString((Path) source(), (CharSequence) null);
- Files.writeString((Path) source(), (CharSequence) null, (Charset) null);
- // "javax.xml.transform.stream;StreamResult";true;"StreamResult;(File);;Argument[0];create-file;ai-generated"
- new StreamResult((File) source());
- // "org.apache.commons.io;FileUtils;true;openInputStream;(File);;Argument[0];read-file;ai-generated"
- FileUtils.openInputStream((File) source());
- // "org.codehaus.cargo.container.installer;ZipURLInstaller;true;ZipURLInstaller;(URL,String,String);;Argument[1];create-file;ai-generated"
- new ZipURLInstaller((URL) null, (String) source(), "");
- // "org.codehaus.cargo.container.installer;ZipURLInstaller;true;ZipURLInstaller;(URL,String,String);;Argument[2];create-file;ai-generated"
- new ZipURLInstaller((URL) null, "", (String) source());
- // "org.springframework.util;FileCopyUtils;false;copy;(byte[],File);;Argument[1];create-file;manual"
- FileCopyUtils.copy((byte[]) null, (File) source());
- // "org.springframework.util;FileCopyUtils;false;copy;(File,File);;Argument[0];create-file;manual"
- FileCopyUtils.copy((File) source(), null);
- // "org.springframework.util;FileCopyUtils;false;copy;(File,File);;Argument[1];create-file;manual"
- FileCopyUtils.copy((File) null, (File) source());
- // "javax.activation;FileDataSource;true;FileDataSource;(String);;Argument[0];path-injection;manual"
- new FileDataSource((String) source());
- // "javax.activation;FileDataSource;true;FileDataSource;(File);;Argument[0];path-injection;manual"
- new FileDataSource((File) source());
- // "org.apache.cxf.common.classloader;ClassLoaderUtils;true;getResourceAsStream;(String,Class);;Argument[0];path-injection;manual"
- ClassLoaderUtils.getResourceAsStream((String) source(), null);
- // "org.apache.cxf.common.jaxb;JAXBUtils;true;createFileCodeWriter;(File);;Argument[0];path-injection;manual"
- JAXBUtils.createFileCodeWriter((File) source());
- // "org.apache.cxf.common.jaxb;JAXBUtils;true;createFileCodeWriter;(File,String);;Argument[0];path-injection;manual"
- JAXBUtils.createFileCodeWriter((File) source(), null);
- // "org.apache.cxf.configuration.jsse:SSLUtils;true;loadFile;(String);;Argument[0];path-injection;manual"
- new SSLUtils() {
- public void test() {
- loadFile((String) source());
- }
- };
- // "org.apache.cxf.helpers;FileUtils;true;delete;(File);;Argument[0];path-injection;manual"
- org.apache.cxf.helpers.FileUtils.delete((File) source());
- // "org.apache.cxf.helpers;FileUtils;true;delete;(File,boolean);;Argument[0];path-injection;manual"
- org.apache.cxf.helpers.FileUtils.delete((File) source(), false);
- // "org.apache.cxf.helpers;FileUtils;true;mkdir;(File);;Argument[0];path-injection;manual"
- org.apache.cxf.helpers.FileUtils.mkDir((File) source());
- // "org.apache.cxf.helpers;FileUtils;true;readLines;(File);;Argument[0];path-injection;manual"
- org.apache.cxf.helpers.FileUtils.readLines((File) source());
- // "org.apache.cxf.helpers;FileUtils;true;removeDir;(File);;Argument[0];path-injection;manual"
- org.apache.cxf.helpers.FileUtils.removeDir((File) source());
- // "org.apache.cxf.resource;ExtendedURIResolver;true;resolve;(String,String);;Argument[1];path-injection;manual"
- new ExtendedURIResolver().resolve(null, (String) source()); // $ SSRF
- // "org.apache.cxf.resource;URIResolver;true;URIResolver;(String,String);;Argument[0];path-injection;manual"
- new URIResolver((String) source(), null); // $ SSRF
- // "org.apache.cxf.resource;URIResolver;true;URIResolver;(String,String,Class);;Argument[0];path-injection;manual"
- new URIResolver((String) source(), null, null); // $ SSRF
- // "org.apache.cxf.resource;URIResolver;true;resolve;(String,String,Class);;Argument[0];path-injection;manual"
- new URIResolver().resolve((String) source(), null, null); // $ SSRF
- // "org.apache.cxf.staxutils;StaxUtils;true;read;(File);;Argument[0];path-injection;manual"
- StaxUtils.read((File) source()); // $ SSRF
- // "org.apache.cxf.tools.corba.utils;FileOutputStreamFactory;true;FileOutputStreamFactory;(String);;Argument[0];path-injection;manual"
- new FileOutputStreamFactory((String) source()); // $ SSRF
- // "org.apache.cxf.tools.corba.utils;FileOutputStreamFactory;true;FileOutputStreamFactory;(String,FileOutputStreamFactory);;Argument[0];path-injection;manual"
- new FileOutputStreamFactory((String) source(), null); // $ SSRF
- // "org.apache.cxf.tools.corba.utils;OutputStreamFactory;true;createOutputStream;(String);;Argument[0];path-injection;manual"
- new FileOutputStreamFactory().createOutputStream((String) source()); // $ SSRF
- // "org.apache.cxf.tools.corba.utils;OutputStreamFactory;true;createOutputStream;(String,String);;Argument[0];path-injection;manual"
- new FileOutputStreamFactory().createOutputStream((String) source(), null); // $ SSRF
- // "org.apache.cxf.tools.corba.utils;OutputStreamFactory;true;createOutputStream;(String,String);;Argument[1];path-injection;manual"
- new FileOutputStreamFactory().createOutputStream(null, (String) source()); // $ SSRF
- // @formatter:off
- // "org.apache.cxf.tools.util;FileWriterUtil;true;FileWriterUtil;(String,OutputStreamCreator);;Argument[0];path-injection;manual"
- new FileWriterUtil((String) source(), null); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;buildDir;(String);;Argument[0];path-injection;manual"
- new FileWriterUtil().buildDir((String) source()); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getFileToWrite;(String,String);;Argument[0];path-injection;manual"
- new FileWriterUtil().getFileToWrite((String) source(), (String) null); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getFileToWrite;(String,String);;Argument[1];path-injection;manual"
- new FileWriterUtil().getFileToWrite((String) null, (String) source()); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getWriter;(File,String);;Argument[0];path-injection;manual"
- new FileWriterUtil().getWriter((File) source(), (String) null); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getWriter;(String,String);;Argument[0];path-injection;manual"
- new FileWriterUtil().getWriter((String) source(), null); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getWriter;(String,String);;Argument[1];path-injection;manual"
- new FileWriterUtil().getWriter((String) null, (String) source()); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getWriter;(String,String,String);;Argument[0];path-injection;manual"
- new FileWriterUtil().getWriter((String) source(), null, null); // $ SSRF
- // "org.apache.cxf.tools.util;FileWriterUtil;true;getWriter;(String,String,String);;Argument[1];path-injection;manual"
- new FileWriterUtil().getWriter((String) null, (String) source(), null); // $ SSRF
- // "org.apache.cxf.tools.util;OutputStreamCreator;true;createOutputStream;(File);;Argument[0];path-injection;manual"
- new OutputStreamCreator().createOutputStream((File) source()); // $ SSRF
- // @formatter:on
- }
-
- void test(AntClassLoader acl) {
- // "org.apache.tools.ant;AntClassLoader;true;addPathComponent;(File);;Argument[0];read-file;ai-generated"
- acl.addPathComponent((File) source());
- // "org.apache.tools.ant;AntClassLoader;true;AntClassLoader;(ClassLoader,Project,Path,boolean);;Argument[2];read-file;ai-generated"
- new AntClassLoader(null, null, (org.apache.tools.ant.types.Path) source(), false);
- // "org.apache.tools.ant;AntClassLoader;true;AntClassLoader;(Project,Path,boolean);;Argument[1];read-file;ai-generated"
- new AntClassLoader(null, (org.apache.tools.ant.types.Path) source(), false);
- // "org.apache.tools.ant;AntClassLoader;true;AntClassLoader;(Project,Path);;Argument[1];read-file;ai-generated"
- new AntClassLoader(null, (org.apache.tools.ant.types.Path) source());
- // "org.kohsuke.stapler.framework.io;LargeText;true;LargeText;(File,Charset,boolean,boolean);;Argument[0];read-file;ai-generated"
- new LargeText((File) source(), null, false, false);
- }
-
- void test(DirectoryScanner ds) {
- // "org.apache.tools.ant;DirectoryScanner;true;setBasedir;(File);;Argument[0];read-file;ai-generated"
- ds.setBasedir((File) source());
- }
-
- void test(Copy cp) {
- // "org.apache.tools.ant.taskdefs;Copy;true;addFileset;(FileSet);;Argument[0];read-file;ai-generated"
- cp.addFileset((FileSet) source());
- // "org.apache.tools.ant.taskdefs;Copy;true;setFile;(File);;Argument[0];read-file;ai-generated"
- cp.setFile((File) source());
- // "org.apache.tools.ant.taskdefs;Copy;true;setTodir;(File);;Argument[0];create-file;ai-generated"
- cp.setTodir((File) source());
- // "org.apache.tools.ant.taskdefs;Copy;true;setTofile;(File);;Argument[0];create-file;ai-generated"
- cp.setTofile((File) source());
- }
-
- void test(Expand ex) {
- // "org.apache.tools.ant.taskdefs;Expand;true;setDest;(File);;Argument[0];create-file;ai-generated"
- ex.setDest((File) source());
- // "org.apache.tools.ant.taskdefs;Expand;true;setSrc;(File);;Argument[0];read-file;ai-generated"
- ex.setSrc((File) source());
- }
-
- void test(ChainedOptionsBuilder cob) {
- // "org.openjdk.jmh.runner.options;ChainedOptionsBuilder;true;result;(String);;Argument[0];create-file;ai-generated"
- cob.result((String) source());
- }
-}
From 1d2a51c522c1357ec94c91585be310dacd3b1bf2 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 26 Jan 2024 12:20:47 +0100
Subject: [PATCH 008/155] Rename change note
---
...mproved.md => 2024-01-26-path-injection-precision-improved.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename java/ql/src/change-notes/{2023-04-20-path-injection-precision-improved.md => 2024-01-26-path-injection-precision-improved.md} (100%)
diff --git a/java/ql/src/change-notes/2023-04-20-path-injection-precision-improved.md b/java/ql/src/change-notes/2024-01-26-path-injection-precision-improved.md
similarity index 100%
rename from java/ql/src/change-notes/2023-04-20-path-injection-precision-improved.md
rename to java/ql/src/change-notes/2024-01-26-path-injection-precision-improved.md
From 2a146405ac32f2c2372764a4c379104797b1a648 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 26 Jan 2024 12:31:48 +0100
Subject: [PATCH 009/155] Adjust tests
---
.../Security/CWE/CWE-073/FilePathInjection.ql | 7 -
.../CWE-073/FilePathInjection.expected | 3 -
.../CWE-022/semmle/tests/TaintedPath.expected | 494 ------------------
.../CWE-022/semmle/tests/TaintedPath.java | 40 +-
.../CWE-022/semmle/tests/TaintedPath.ql | 9 +-
.../security/CWE-022/semmle/tests/Test.java | 2 -
6 files changed, 22 insertions(+), 533 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql
index 7f6528a66708..6fab554ac672 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-073/FilePathInjection.ql
@@ -16,10 +16,7 @@ import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.FlowSources
-<<<<<<< HEAD
-=======
import semmle.code.java.security.TaintedPathQuery
->>>>>>> 9e469c9c32 (Migrate path injection sinks to MaD)
import JFinalController
import semmle.code.java.security.PathSanitizer
private import semmle.code.java.security.Sanitizers
@@ -56,11 +53,7 @@ module InjectFilePathConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
predicate isSink(DataFlow::Node sink) {
-<<<<<<< HEAD
- sinkNode(sink, "path-injection") and
-=======
sink instanceof TaintedPathSink and
->>>>>>> 9e469c9c32 (Migrate path injection sinks to MaD)
not sink instanceof NormalizedPathNode
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected
index cd2b49f28c1e..07be573dbf8e 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected
@@ -3,7 +3,6 @@ edges
| FilePathInjection.java:64:21:64:34 | getPara(...) : String | FilePathInjection.java:72:47:72:59 | finalFilePath |
| FilePathInjection.java:87:21:87:34 | getPara(...) : String | FilePathInjection.java:95:47:95:59 | finalFilePath |
| FilePathInjection.java:177:50:177:58 | file : File | FilePathInjection.java:182:30:182:33 | file |
-| FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:209:24:209:31 | filePath |
| FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:209:24:209:31 | filePath : String |
| FilePathInjection.java:209:15:209:32 | new File(...) : File | FilePathInjection.java:217:19:217:22 | file : File |
| FilePathInjection.java:209:24:209:31 | filePath : String | FilePathInjection.java:209:15:209:32 | new File(...) : File |
@@ -19,7 +18,6 @@ nodes
| FilePathInjection.java:182:30:182:33 | file | semmle.label | file |
| FilePathInjection.java:205:17:205:44 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| FilePathInjection.java:209:15:209:32 | new File(...) : File | semmle.label | new File(...) : File |
-| FilePathInjection.java:209:24:209:31 | filePath | semmle.label | filePath |
| FilePathInjection.java:209:24:209:31 | filePath : String | semmle.label | filePath : String |
| FilePathInjection.java:217:19:217:22 | file : File | semmle.label | file : File |
subpaths
@@ -28,4 +26,3 @@ subpaths
| FilePathInjection.java:72:47:72:59 | finalFilePath | FilePathInjection.java:64:21:64:34 | getPara(...) : String | FilePathInjection.java:72:47:72:59 | finalFilePath | External control of file name or path due to $@. | FilePathInjection.java:64:21:64:34 | getPara(...) | user-provided value |
| FilePathInjection.java:95:47:95:59 | finalFilePath | FilePathInjection.java:87:21:87:34 | getPara(...) : String | FilePathInjection.java:95:47:95:59 | finalFilePath | External control of file name or path due to $@. | FilePathInjection.java:87:21:87:34 | getPara(...) | user-provided value |
| FilePathInjection.java:182:30:182:33 | file | FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:182:30:182:33 | file | External control of file name or path due to $@. | FilePathInjection.java:205:17:205:44 | getParameter(...) | user-provided value |
-| FilePathInjection.java:209:24:209:31 | filePath | FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:209:24:209:31 | filePath | External control of file name or path due to $@. | FilePathInjection.java:205:17:205:44 | getParameter(...) | user-provided value |
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected
index 0e2d90c3709c..e69de29bb2d1 100644
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected
+++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected
@@ -1,494 +0,0 @@
-edges
-| TaintedPath.java:12:38:12:110 | new BufferedReader(...) : BufferedReader | TaintedPath.java:13:24:13:37 | filenameReader : BufferedReader |
-| TaintedPath.java:12:57:12:109 | new InputStreamReader(...) : InputStreamReader | TaintedPath.java:12:38:12:110 | new BufferedReader(...) : BufferedReader |
-| TaintedPath.java:12:79:12:99 | getInputStream(...) : InputStream | TaintedPath.java:12:57:12:109 | new InputStreamReader(...) : InputStreamReader |
-| TaintedPath.java:13:24:13:37 | filenameReader : BufferedReader | TaintedPath.java:13:24:13:48 | readLine(...) : String |
-| TaintedPath.java:13:24:13:48 | readLine(...) : String | TaintedPath.java:15:68:15:75 | filename |
-| TaintedPath.java:38:41:39:70 | new BufferedReader(...) : BufferedReader | TaintedPath.java:40:27:40:40 | filenameReader : BufferedReader |
-| TaintedPath.java:39:17:39:69 | new InputStreamReader(...) : InputStreamReader | TaintedPath.java:38:41:39:70 | new BufferedReader(...) : BufferedReader |
-| TaintedPath.java:39:39:39:59 | getInputStream(...) : InputStream | TaintedPath.java:39:17:39:69 | new InputStreamReader(...) : InputStreamReader |
-| TaintedPath.java:40:27:40:40 | filenameReader : BufferedReader | TaintedPath.java:40:27:40:51 | readLine(...) : String |
-| TaintedPath.java:40:27:40:51 | readLine(...) : String | TaintedPath.java:43:46:43:53 | filename |
-| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp |
-| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:27:21:27:24 | temp |
-| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:30:44:30:47 | temp |
-| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:34:21:34:24 | temp |
-| Test.java:79:33:79:99 | new BufferedReader(...) : BufferedReader | Test.java:80:31:80:32 | br : BufferedReader |
-| Test.java:79:52:79:98 | new InputStreamReader(...) : InputStreamReader | Test.java:79:33:79:99 | new BufferedReader(...) : BufferedReader |
-| Test.java:79:74:79:97 | getInputStream(...) : ServletInputStream | Test.java:79:52:79:98 | new InputStreamReader(...) : InputStreamReader |
-| Test.java:80:31:80:32 | br : BufferedReader | Test.java:80:31:80:43 | readLine(...) : String |
-| Test.java:80:31:80:43 | readLine(...) : String | Test.java:82:67:82:81 | ... + ... |
-| Test.java:88:17:88:37 | getHostName(...) : String | Test.java:90:26:90:29 | temp |
-| Test.java:95:14:95:34 | getHostName(...) : String | Test.java:97:26:97:26 | t : String |
-| Test.java:97:26:97:26 | t : String | Test.java:97:12:97:33 | new URI(...) |
-| Test.java:97:26:97:26 | t : String | Test.java:98:23:98:23 | t : String |
-| Test.java:98:23:98:23 | t : String | Test.java:98:12:98:33 | new URI(...) |
-| Test.java:98:23:98:23 | t : String | Test.java:99:29:99:29 | t : String |
-| Test.java:99:29:99:29 | t : String | Test.java:99:12:99:33 | new URI(...) |
-| Test.java:99:29:99:29 | t : String | Test.java:100:32:100:32 | t : String |
-| Test.java:100:32:100:32 | t : String | Test.java:100:12:100:45 | new URI(...) |
-| Test.java:100:32:100:32 | t : String | Test.java:101:41:101:41 | t : String |
-| Test.java:101:41:101:41 | t : String | Test.java:101:12:101:54 | new URI(...) |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:45:61:45:68 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:47:41:47:48 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:49:56:49:63 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:51:46:51:53 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:53:38:53:45 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:55:36:55:43 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:57:31:57:38 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:59:33:59:40 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:61:27:61:34 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:63:27:63:34 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:65:40:65:47 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:67:47:67:54 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:69:40:69:47 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:71:38:71:45 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:73:33:73:40 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:75:33:75:40 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:77:41:77:48 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:79:42:79:49 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:81:37:81:44 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:83:29:83:36 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:85:37:85:44 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:87:28:87:35 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:89:33:89:40 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:91:40:91:47 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:93:40:93:47 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:94:40:94:47 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:96:38:96:45 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:98:28:98:35 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:99:28:99:35 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:100:28:100:35 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:102:34:102:41 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:103:34:103:41 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:105:33:105:40 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:107:42:107:49 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:109:50:109:57 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:111:54:111:61 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:113:50:113:57 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:115:35:115:42 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:117:48:117:55 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:119:37:119:44 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:121:35:121:42 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:123:55:123:62 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:125:47:125:54 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:127:47:127:54 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:131:35:131:42 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:135:56:135:63 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:137:56:137:63 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:141:59:141:66 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:143:59:143:66 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:145:58:145:65 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:147:34:147:41 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:149:34:149:41 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:151:44:151:51 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:153:31:153:38 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:155:46:155:53 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:157:46:157:53 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:159:67:159:74 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:161:67:161:74 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:163:73:163:80 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:166:37:166:44 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:168:48:168:55 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:170:54:170:61 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:172:69:172:76 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:174:47:174:54 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:176:49:176:56 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:178:64:178:71 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:180:49:180:56 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:182:64:182:71 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:184:61:184:68 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:190:37:190:44 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:192:74:192:81 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:194:68:194:75 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:196:68:196:75 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:198:30:198:37 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:203:30:203:37 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:208:33:208:40 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:210:27:210:34 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:212:28:212:35 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:214:29:214:36 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:219:27:219:34 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:221:26:221:33 | source(...) : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:226:29:226:36 | source(...) : String |
-| mad/Test.java:45:61:45:68 | source(...) : String | mad/Test.java:45:52:45:68 | (...)... |
-| mad/Test.java:47:41:47:48 | source(...) : String | mad/Test.java:47:32:47:48 | (...)... |
-| mad/Test.java:49:56:49:63 | source(...) : String | mad/Test.java:49:47:49:63 | (...)... |
-| mad/Test.java:51:46:51:53 | source(...) : String | mad/Test.java:51:39:51:53 | (...)... |
-| mad/Test.java:53:38:53:45 | source(...) : String | mad/Test.java:53:31:53:45 | (...)... |
-| mad/Test.java:55:36:55:43 | source(...) : String | mad/Test.java:55:29:55:43 | (...)... |
-| mad/Test.java:57:31:57:38 | source(...) : String | mad/Test.java:57:24:57:38 | (...)... |
-| mad/Test.java:59:33:59:40 | source(...) : String | mad/Test.java:59:24:59:40 | (...)... |
-| mad/Test.java:61:27:61:34 | source(...) : String | mad/Test.java:61:20:61:34 | (...)... |
-| mad/Test.java:63:27:63:34 | source(...) : String | mad/Test.java:63:20:63:34 | (...)... |
-| mad/Test.java:65:40:65:47 | source(...) : String | mad/Test.java:65:33:65:47 | (...)... |
-| mad/Test.java:67:47:67:54 | source(...) : String | mad/Test.java:67:40:67:54 | (...)... |
-| mad/Test.java:69:40:69:47 | source(...) : String | mad/Test.java:69:33:69:47 | (...)... |
-| mad/Test.java:71:38:71:45 | source(...) : String | mad/Test.java:71:31:71:45 | (...)... |
-| mad/Test.java:73:33:73:40 | source(...) : String | mad/Test.java:73:26:73:40 | (...)... |
-| mad/Test.java:75:33:75:40 | source(...) : String | mad/Test.java:75:26:75:40 | (...)... |
-| mad/Test.java:77:41:77:48 | source(...) : String | mad/Test.java:77:34:77:48 | (...)... |
-| mad/Test.java:79:42:79:49 | source(...) : String | mad/Test.java:79:35:79:49 | (...)... |
-| mad/Test.java:81:37:81:44 | source(...) : String | mad/Test.java:81:30:81:44 | (...)... |
-| mad/Test.java:83:29:83:36 | source(...) : String | mad/Test.java:83:22:83:36 | (...)... |
-| mad/Test.java:85:37:85:44 | source(...) : String | mad/Test.java:85:30:85:44 | (...)... |
-| mad/Test.java:87:28:87:35 | source(...) : String | mad/Test.java:87:21:87:35 | (...)... |
-| mad/Test.java:89:33:89:40 | source(...) : String | mad/Test.java:89:26:89:40 | (...)... |
-| mad/Test.java:91:40:91:47 | source(...) : String | mad/Test.java:91:33:91:47 | (...)... |
-| mad/Test.java:93:40:93:47 | source(...) : String | mad/Test.java:93:33:93:47 | (...)... |
-| mad/Test.java:94:40:94:47 | source(...) : String | mad/Test.java:94:33:94:47 | (...)... |
-| mad/Test.java:96:38:96:45 | source(...) : String | mad/Test.java:96:31:96:45 | (...)... |
-| mad/Test.java:98:28:98:35 | source(...) : String | mad/Test.java:98:21:98:35 | (...)... |
-| mad/Test.java:99:28:99:35 | source(...) : String | mad/Test.java:99:21:99:35 | (...)... |
-| mad/Test.java:100:28:100:35 | source(...) : String | mad/Test.java:100:21:100:35 | (...)... |
-| mad/Test.java:102:34:102:41 | source(...) : String | mad/Test.java:102:27:102:41 | (...)... |
-| mad/Test.java:103:34:103:41 | source(...) : String | mad/Test.java:103:27:103:41 | (...)... |
-| mad/Test.java:105:33:105:40 | source(...) : String | mad/Test.java:105:26:105:40 | (...)... |
-| mad/Test.java:107:42:107:49 | source(...) : String | mad/Test.java:107:35:107:49 | (...)... |
-| mad/Test.java:109:50:109:57 | source(...) : String | mad/Test.java:109:41:109:57 | (...)... |
-| mad/Test.java:111:54:111:61 | source(...) : String | mad/Test.java:111:45:111:61 | (...)... |
-| mad/Test.java:113:50:113:57 | source(...) : String | mad/Test.java:113:43:113:57 | (...)... |
-| mad/Test.java:115:35:115:42 | source(...) : String | mad/Test.java:115:28:115:42 | (...)... |
-| mad/Test.java:117:48:117:55 | source(...) : String | mad/Test.java:117:41:117:55 | (...)... |
-| mad/Test.java:119:37:119:44 | source(...) : String | mad/Test.java:119:28:119:44 | (...)... |
-| mad/Test.java:121:35:121:42 | source(...) : String | mad/Test.java:121:28:121:42 | (...)... |
-| mad/Test.java:123:55:123:62 | source(...) : String | mad/Test.java:123:46:123:62 | (...)... |
-| mad/Test.java:125:47:125:54 | source(...) : String | mad/Test.java:125:40:125:54 | (...)... |
-| mad/Test.java:127:47:127:54 | source(...) : String | mad/Test.java:127:40:127:54 | (...)... |
-| mad/Test.java:131:35:131:42 | source(...) : String | mad/Test.java:131:26:131:42 | (...)... |
-| mad/Test.java:135:56:135:63 | source(...) : String | mad/Test.java:135:49:135:63 | (...)... |
-| mad/Test.java:137:56:137:63 | source(...) : String | mad/Test.java:137:49:137:63 | (...)... |
-| mad/Test.java:141:59:141:66 | source(...) : String | mad/Test.java:141:52:141:66 | (...)... |
-| mad/Test.java:143:59:143:66 | source(...) : String | mad/Test.java:143:52:143:66 | (...)... |
-| mad/Test.java:145:58:145:65 | source(...) : String | mad/Test.java:145:49:145:65 | (...)... |
-| mad/Test.java:147:34:147:41 | source(...) : String | mad/Test.java:147:25:147:41 | (...)... |
-| mad/Test.java:149:34:149:41 | source(...) : String | mad/Test.java:149:25:149:41 | (...)... |
-| mad/Test.java:151:44:151:51 | source(...) : String | mad/Test.java:151:35:151:51 | (...)... |
-| mad/Test.java:153:31:153:38 | source(...) : String | mad/Test.java:153:24:153:38 | (...)... |
-| mad/Test.java:155:46:155:53 | source(...) : String | mad/Test.java:155:37:155:53 | (...)... |
-| mad/Test.java:157:46:157:53 | source(...) : String | mad/Test.java:157:37:157:53 | (...)... |
-| mad/Test.java:159:67:159:74 | source(...) : String | mad/Test.java:159:58:159:74 | (...)... |
-| mad/Test.java:161:67:161:74 | source(...) : String | mad/Test.java:161:58:161:74 | (...)... |
-| mad/Test.java:163:73:163:80 | source(...) : String | mad/Test.java:163:64:163:80 | (...)... |
-| mad/Test.java:166:37:166:44 | source(...) : String | mad/Test.java:166:28:166:44 | (...)... |
-| mad/Test.java:168:48:168:55 | source(...) : String | mad/Test.java:168:39:168:55 | (...)... |
-| mad/Test.java:170:54:170:61 | source(...) : String | mad/Test.java:170:45:170:61 | (...)... |
-| mad/Test.java:172:69:172:76 | source(...) : String | mad/Test.java:172:60:172:76 | (...)... |
-| mad/Test.java:174:47:174:54 | source(...) : String | mad/Test.java:174:40:174:54 | (...)... |
-| mad/Test.java:176:49:176:56 | source(...) : String | mad/Test.java:176:40:176:56 | (...)... |
-| mad/Test.java:178:64:178:71 | source(...) : String | mad/Test.java:178:55:178:71 | (...)... |
-| mad/Test.java:180:49:180:56 | source(...) : String | mad/Test.java:180:40:180:56 | (...)... |
-| mad/Test.java:182:64:182:71 | source(...) : String | mad/Test.java:182:55:182:71 | (...)... |
-| mad/Test.java:184:61:184:68 | source(...) : String | mad/Test.java:184:54:184:68 | (...)... |
-| mad/Test.java:190:37:190:44 | source(...) : String | mad/Test.java:190:30:190:44 | (...)... |
-| mad/Test.java:192:74:192:81 | source(...) : String | mad/Test.java:192:40:192:81 | (...)... |
-| mad/Test.java:194:68:194:75 | source(...) : String | mad/Test.java:194:34:194:75 | (...)... |
-| mad/Test.java:196:68:196:75 | source(...) : String | mad/Test.java:196:34:196:75 | (...)... |
-| mad/Test.java:198:30:198:37 | source(...) : String | mad/Test.java:198:23:198:37 | (...)... |
-| mad/Test.java:203:30:203:37 | source(...) : String | mad/Test.java:203:23:203:37 | (...)... |
-| mad/Test.java:208:33:208:40 | source(...) : String | mad/Test.java:208:23:208:40 | (...)... |
-| mad/Test.java:210:27:210:34 | source(...) : String | mad/Test.java:210:20:210:34 | (...)... |
-| mad/Test.java:212:28:212:35 | source(...) : String | mad/Test.java:212:21:212:35 | (...)... |
-| mad/Test.java:214:29:214:36 | source(...) : String | mad/Test.java:214:22:214:36 | (...)... |
-| mad/Test.java:219:27:219:34 | source(...) : String | mad/Test.java:219:20:219:34 | (...)... |
-| mad/Test.java:221:26:221:33 | source(...) : String | mad/Test.java:221:19:221:33 | (...)... |
-| mad/Test.java:226:29:226:36 | source(...) : String | mad/Test.java:226:20:226:36 | (...)... |
-nodes
-| TaintedPath.java:12:38:12:110 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader |
-| TaintedPath.java:12:57:12:109 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
-| TaintedPath.java:12:79:12:99 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
-| TaintedPath.java:13:24:13:37 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader |
-| TaintedPath.java:13:24:13:48 | readLine(...) : String | semmle.label | readLine(...) : String |
-| TaintedPath.java:15:68:15:75 | filename | semmle.label | filename |
-| TaintedPath.java:38:41:39:70 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader |
-| TaintedPath.java:39:17:39:69 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
-| TaintedPath.java:39:39:39:59 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
-| TaintedPath.java:40:27:40:40 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader |
-| TaintedPath.java:40:27:40:51 | readLine(...) : String | semmle.label | readLine(...) : String |
-| TaintedPath.java:43:46:43:53 | filename | semmle.label | filename |
-| Test.java:19:18:19:38 | getHostName(...) : String | semmle.label | getHostName(...) : String |
-| Test.java:24:20:24:23 | temp | semmle.label | temp |
-| Test.java:27:21:27:24 | temp | semmle.label | temp |
-| Test.java:30:44:30:47 | temp | semmle.label | temp |
-| Test.java:34:21:34:24 | temp | semmle.label | temp |
-| Test.java:79:33:79:99 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader |
-| Test.java:79:52:79:98 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
-| Test.java:79:74:79:97 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream |
-| Test.java:80:31:80:32 | br : BufferedReader | semmle.label | br : BufferedReader |
-| Test.java:80:31:80:43 | readLine(...) : String | semmle.label | readLine(...) : String |
-| Test.java:82:67:82:81 | ... + ... | semmle.label | ... + ... |
-| Test.java:88:17:88:37 | getHostName(...) : String | semmle.label | getHostName(...) : String |
-| Test.java:90:26:90:29 | temp | semmle.label | temp |
-| Test.java:95:14:95:34 | getHostName(...) : String | semmle.label | getHostName(...) : String |
-| Test.java:97:12:97:33 | new URI(...) | semmle.label | new URI(...) |
-| Test.java:97:26:97:26 | t : String | semmle.label | t : String |
-| Test.java:98:12:98:33 | new URI(...) | semmle.label | new URI(...) |
-| Test.java:98:23:98:23 | t : String | semmle.label | t : String |
-| Test.java:99:12:99:33 | new URI(...) | semmle.label | new URI(...) |
-| Test.java:99:29:99:29 | t : String | semmle.label | t : String |
-| Test.java:100:12:100:45 | new URI(...) | semmle.label | new URI(...) |
-| Test.java:100:32:100:32 | t : String | semmle.label | t : String |
-| Test.java:101:12:101:54 | new URI(...) | semmle.label | new URI(...) |
-| Test.java:101:41:101:41 | t : String | semmle.label | t : String |
-| mad/Test.java:40:16:40:36 | getHostName(...) : String | semmle.label | getHostName(...) : String |
-| mad/Test.java:45:52:45:68 | (...)... | semmle.label | (...)... |
-| mad/Test.java:45:61:45:68 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:47:32:47:48 | (...)... | semmle.label | (...)... |
-| mad/Test.java:47:41:47:48 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:49:47:49:63 | (...)... | semmle.label | (...)... |
-| mad/Test.java:49:56:49:63 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:51:39:51:53 | (...)... | semmle.label | (...)... |
-| mad/Test.java:51:46:51:53 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:53:31:53:45 | (...)... | semmle.label | (...)... |
-| mad/Test.java:53:38:53:45 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:55:29:55:43 | (...)... | semmle.label | (...)... |
-| mad/Test.java:55:36:55:43 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:57:24:57:38 | (...)... | semmle.label | (...)... |
-| mad/Test.java:57:31:57:38 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:59:24:59:40 | (...)... | semmle.label | (...)... |
-| mad/Test.java:59:33:59:40 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:61:20:61:34 | (...)... | semmle.label | (...)... |
-| mad/Test.java:61:27:61:34 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:63:20:63:34 | (...)... | semmle.label | (...)... |
-| mad/Test.java:63:27:63:34 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:65:33:65:47 | (...)... | semmle.label | (...)... |
-| mad/Test.java:65:40:65:47 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:67:40:67:54 | (...)... | semmle.label | (...)... |
-| mad/Test.java:67:47:67:54 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:69:33:69:47 | (...)... | semmle.label | (...)... |
-| mad/Test.java:69:40:69:47 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:71:31:71:45 | (...)... | semmle.label | (...)... |
-| mad/Test.java:71:38:71:45 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:73:26:73:40 | (...)... | semmle.label | (...)... |
-| mad/Test.java:73:33:73:40 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:75:26:75:40 | (...)... | semmle.label | (...)... |
-| mad/Test.java:75:33:75:40 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:77:34:77:48 | (...)... | semmle.label | (...)... |
-| mad/Test.java:77:41:77:48 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:79:35:79:49 | (...)... | semmle.label | (...)... |
-| mad/Test.java:79:42:79:49 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:81:30:81:44 | (...)... | semmle.label | (...)... |
-| mad/Test.java:81:37:81:44 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:83:22:83:36 | (...)... | semmle.label | (...)... |
-| mad/Test.java:83:29:83:36 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:85:30:85:44 | (...)... | semmle.label | (...)... |
-| mad/Test.java:85:37:85:44 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:87:21:87:35 | (...)... | semmle.label | (...)... |
-| mad/Test.java:87:28:87:35 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:89:26:89:40 | (...)... | semmle.label | (...)... |
-| mad/Test.java:89:33:89:40 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:91:33:91:47 | (...)... | semmle.label | (...)... |
-| mad/Test.java:91:40:91:47 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:93:33:93:47 | (...)... | semmle.label | (...)... |
-| mad/Test.java:93:40:93:47 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:94:33:94:47 | (...)... | semmle.label | (...)... |
-| mad/Test.java:94:40:94:47 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:96:31:96:45 | (...)... | semmle.label | (...)... |
-| mad/Test.java:96:38:96:45 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:98:21:98:35 | (...)... | semmle.label | (...)... |
-| mad/Test.java:98:28:98:35 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:99:21:99:35 | (...)... | semmle.label | (...)... |
-| mad/Test.java:99:28:99:35 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:100:21:100:35 | (...)... | semmle.label | (...)... |
-| mad/Test.java:100:28:100:35 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:102:27:102:41 | (...)... | semmle.label | (...)... |
-| mad/Test.java:102:34:102:41 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:103:27:103:41 | (...)... | semmle.label | (...)... |
-| mad/Test.java:103:34:103:41 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:105:26:105:40 | (...)... | semmle.label | (...)... |
-| mad/Test.java:105:33:105:40 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:107:35:107:49 | (...)... | semmle.label | (...)... |
-| mad/Test.java:107:42:107:49 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:109:41:109:57 | (...)... | semmle.label | (...)... |
-| mad/Test.java:109:50:109:57 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:111:45:111:61 | (...)... | semmle.label | (...)... |
-| mad/Test.java:111:54:111:61 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:113:43:113:57 | (...)... | semmle.label | (...)... |
-| mad/Test.java:113:50:113:57 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:115:28:115:42 | (...)... | semmle.label | (...)... |
-| mad/Test.java:115:35:115:42 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:117:41:117:55 | (...)... | semmle.label | (...)... |
-| mad/Test.java:117:48:117:55 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:119:28:119:44 | (...)... | semmle.label | (...)... |
-| mad/Test.java:119:37:119:44 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:121:28:121:42 | (...)... | semmle.label | (...)... |
-| mad/Test.java:121:35:121:42 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:123:46:123:62 | (...)... | semmle.label | (...)... |
-| mad/Test.java:123:55:123:62 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:125:40:125:54 | (...)... | semmle.label | (...)... |
-| mad/Test.java:125:47:125:54 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:127:40:127:54 | (...)... | semmle.label | (...)... |
-| mad/Test.java:127:47:127:54 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:131:26:131:42 | (...)... | semmle.label | (...)... |
-| mad/Test.java:131:35:131:42 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:135:49:135:63 | (...)... | semmle.label | (...)... |
-| mad/Test.java:135:56:135:63 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:137:49:137:63 | (...)... | semmle.label | (...)... |
-| mad/Test.java:137:56:137:63 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:141:52:141:66 | (...)... | semmle.label | (...)... |
-| mad/Test.java:141:59:141:66 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:143:52:143:66 | (...)... | semmle.label | (...)... |
-| mad/Test.java:143:59:143:66 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:145:49:145:65 | (...)... | semmle.label | (...)... |
-| mad/Test.java:145:58:145:65 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:147:25:147:41 | (...)... | semmle.label | (...)... |
-| mad/Test.java:147:34:147:41 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:149:25:149:41 | (...)... | semmle.label | (...)... |
-| mad/Test.java:149:34:149:41 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:151:35:151:51 | (...)... | semmle.label | (...)... |
-| mad/Test.java:151:44:151:51 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:153:24:153:38 | (...)... | semmle.label | (...)... |
-| mad/Test.java:153:31:153:38 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:155:37:155:53 | (...)... | semmle.label | (...)... |
-| mad/Test.java:155:46:155:53 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:157:37:157:53 | (...)... | semmle.label | (...)... |
-| mad/Test.java:157:46:157:53 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:159:58:159:74 | (...)... | semmle.label | (...)... |
-| mad/Test.java:159:67:159:74 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:161:58:161:74 | (...)... | semmle.label | (...)... |
-| mad/Test.java:161:67:161:74 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:163:64:163:80 | (...)... | semmle.label | (...)... |
-| mad/Test.java:163:73:163:80 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:166:28:166:44 | (...)... | semmle.label | (...)... |
-| mad/Test.java:166:37:166:44 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:168:39:168:55 | (...)... | semmle.label | (...)... |
-| mad/Test.java:168:48:168:55 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:170:45:170:61 | (...)... | semmle.label | (...)... |
-| mad/Test.java:170:54:170:61 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:172:60:172:76 | (...)... | semmle.label | (...)... |
-| mad/Test.java:172:69:172:76 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:174:40:174:54 | (...)... | semmle.label | (...)... |
-| mad/Test.java:174:47:174:54 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:176:40:176:56 | (...)... | semmle.label | (...)... |
-| mad/Test.java:176:49:176:56 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:178:55:178:71 | (...)... | semmle.label | (...)... |
-| mad/Test.java:178:64:178:71 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:180:40:180:56 | (...)... | semmle.label | (...)... |
-| mad/Test.java:180:49:180:56 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:182:55:182:71 | (...)... | semmle.label | (...)... |
-| mad/Test.java:182:64:182:71 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:184:54:184:68 | (...)... | semmle.label | (...)... |
-| mad/Test.java:184:61:184:68 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:190:30:190:44 | (...)... | semmle.label | (...)... |
-| mad/Test.java:190:37:190:44 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:192:40:192:81 | (...)... | semmle.label | (...)... |
-| mad/Test.java:192:74:192:81 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:194:34:194:75 | (...)... | semmle.label | (...)... |
-| mad/Test.java:194:68:194:75 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:196:34:196:75 | (...)... | semmle.label | (...)... |
-| mad/Test.java:196:68:196:75 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:198:23:198:37 | (...)... | semmle.label | (...)... |
-| mad/Test.java:198:30:198:37 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:203:23:203:37 | (...)... | semmle.label | (...)... |
-| mad/Test.java:203:30:203:37 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:208:23:208:40 | (...)... | semmle.label | (...)... |
-| mad/Test.java:208:33:208:40 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:210:20:210:34 | (...)... | semmle.label | (...)... |
-| mad/Test.java:210:27:210:34 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:212:21:212:35 | (...)... | semmle.label | (...)... |
-| mad/Test.java:212:28:212:35 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:214:22:214:36 | (...)... | semmle.label | (...)... |
-| mad/Test.java:214:29:214:36 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:219:20:219:34 | (...)... | semmle.label | (...)... |
-| mad/Test.java:219:27:219:34 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:221:19:221:33 | (...)... | semmle.label | (...)... |
-| mad/Test.java:221:26:221:33 | source(...) : String | semmle.label | source(...) : String |
-| mad/Test.java:226:20:226:36 | (...)... | semmle.label | (...)... |
-| mad/Test.java:226:29:226:36 | source(...) : String | semmle.label | source(...) : String |
-subpaths
-#select
-| TaintedPath.java:15:53:15:76 | new FileReader(...) | TaintedPath.java:12:79:12:99 | getInputStream(...) : InputStream | TaintedPath.java:15:68:15:75 | filename | This path depends on a $@. | TaintedPath.java:12:79:12:99 | getInputStream(...) | user-provided value |
-| TaintedPath.java:43:25:43:54 | resolve(...) | TaintedPath.java:39:39:39:59 | getInputStream(...) : InputStream | TaintedPath.java:43:46:43:53 | filename | This path depends on a $@. | TaintedPath.java:39:39:39:59 | getInputStream(...) | user-provided value |
-| Test.java:24:11:24:24 | new File(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
-| Test.java:27:11:27:25 | get(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:27:21:27:24 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
-| Test.java:30:11:30:48 | getPath(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:30:44:30:47 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
-| Test.java:34:12:34:25 | new File(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:34:21:34:24 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
-| Test.java:82:52:82:88 | new FileWriter(...) | Test.java:79:74:79:97 | getInputStream(...) : ServletInputStream | Test.java:82:67:82:81 | ... + ... | This path depends on a $@. | Test.java:79:74:79:97 | getInputStream(...) | user-provided value |
-| Test.java:90:26:90:29 | temp | Test.java:88:17:88:37 | getHostName(...) : String | Test.java:90:26:90:29 | temp | This path depends on a $@. | Test.java:88:17:88:37 | getHostName(...) | user-provided value |
-| Test.java:97:3:97:34 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:97:12:97:33 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value |
-| Test.java:98:3:98:34 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:98:12:98:33 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value |
-| Test.java:99:3:99:34 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:99:12:99:33 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value |
-| Test.java:100:3:100:46 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:100:12:100:45 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value |
-| Test.java:101:3:101:55 | new File(...) | Test.java:95:14:95:34 | getHostName(...) : String | Test.java:101:12:101:54 | new URI(...) | This path depends on a $@. | Test.java:95:14:95:34 | getHostName(...) | user-provided value |
-| mad/Test.java:45:52:45:68 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:45:52:45:68 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:47:32:47:48 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:47:32:47:48 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:49:47:49:63 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:49:47:49:63 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:51:39:51:53 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:51:39:51:53 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:53:31:53:45 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:53:31:53:45 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:55:29:55:43 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:55:29:55:43 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:57:24:57:38 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:57:24:57:38 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:59:9:59:41 | new FileReader(...) | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:59:24:59:40 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:61:20:61:34 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:61:20:61:34 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:63:20:63:34 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:63:20:63:34 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:65:33:65:47 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:65:33:65:47 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:67:40:67:54 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:67:40:67:54 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:69:33:69:47 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:69:33:69:47 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:71:31:71:45 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:71:31:71:45 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:73:26:73:40 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:73:26:73:40 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:75:26:75:40 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:75:26:75:40 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:77:34:77:48 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:77:34:77:48 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:79:35:79:49 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:79:35:79:49 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:81:30:81:44 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:81:30:81:44 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:83:22:83:36 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:83:22:83:36 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:85:30:85:44 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:85:30:85:44 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:87:21:87:35 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:87:21:87:35 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:89:26:89:40 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:89:26:89:40 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:91:33:91:47 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:91:33:91:47 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:93:33:93:47 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:93:33:93:47 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:94:33:94:47 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:94:33:94:47 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:96:31:96:45 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:96:31:96:45 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:98:21:98:35 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:98:21:98:35 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:99:21:99:35 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:99:21:99:35 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:100:21:100:35 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:100:21:100:35 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:102:27:102:41 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:102:27:102:41 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:103:27:103:41 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:103:27:103:41 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:105:26:105:40 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:105:26:105:40 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:107:35:107:49 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:107:35:107:49 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:109:41:109:57 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:109:41:109:57 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:111:45:111:61 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:111:45:111:61 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:113:43:113:57 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:113:43:113:57 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:115:28:115:42 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:115:28:115:42 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:117:41:117:55 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:117:41:117:55 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:119:28:119:44 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:119:28:119:44 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:121:28:121:42 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:121:28:121:42 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:123:46:123:62 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:123:46:123:62 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:125:40:125:54 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:125:40:125:54 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:127:40:127:54 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:127:40:127:54 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:131:26:131:42 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:131:26:131:42 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:135:49:135:63 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:135:49:135:63 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:137:49:137:63 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:137:49:137:63 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:141:52:141:66 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:141:52:141:66 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:143:52:143:66 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:143:52:143:66 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:145:49:145:65 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:145:49:145:65 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:147:25:147:41 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:147:25:147:41 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:149:25:149:41 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:149:25:149:41 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:151:35:151:51 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:151:35:151:51 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:153:24:153:38 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:153:24:153:38 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:155:37:155:53 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:155:37:155:53 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:157:37:157:53 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:157:37:157:53 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:159:58:159:74 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:159:58:159:74 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:161:58:161:74 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:161:58:161:74 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:163:64:163:80 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:163:64:163:80 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:166:28:166:44 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:166:28:166:44 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:168:39:168:55 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:168:39:168:55 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:170:45:170:61 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:170:45:170:61 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:172:60:172:76 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:172:60:172:76 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:174:40:174:54 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:174:40:174:54 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:176:40:176:56 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:176:40:176:56 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:178:55:178:71 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:178:55:178:71 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:180:40:180:56 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:180:40:180:56 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:182:55:182:71 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:182:55:182:71 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:184:54:184:68 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:184:54:184:68 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:190:30:190:44 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:190:30:190:44 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:192:40:192:81 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:192:40:192:81 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:194:34:194:75 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:194:34:194:75 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:196:34:196:75 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:196:34:196:75 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:198:23:198:37 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:198:23:198:37 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:203:23:203:37 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:203:23:203:37 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:208:23:208:40 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:208:23:208:40 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:210:20:210:34 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:210:20:210:34 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:212:21:212:35 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:212:21:212:35 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:214:22:214:36 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:214:22:214:36 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:219:20:219:34 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:219:20:219:34 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:221:19:221:33 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:221:19:221:33 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
-| mad/Test.java:226:20:226:36 | (...)... | mad/Test.java:40:16:40:36 | getHostName(...) : String | mad/Test.java:226:20:226:36 | (...)... | This path depends on a $@. | mad/Test.java:40:16:40:36 | getHostName(...) | user-provided value |
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java
index a2ae561be588..8bfc35c1d969 100644
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java
+++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java
@@ -9,25 +9,27 @@
public class TaintedPath {
public void sendUserFile(Socket sock, String user) throws IOException {
- BufferedReader filenameReader = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
- String filename = filenameReader.readLine();
- // BAD: read from a file without checking its path
- BufferedReader fileReader = new BufferedReader(new FileReader(filename));
+ BufferedReader filenameReader =
+ new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
+ String filename = filenameReader.readLine();
+ // BAD: read from a file without checking its path
+ BufferedReader fileReader = new BufferedReader(new FileReader(filename)); // $ hasTaintFlow
String fileLine = fileReader.readLine();
- while(fileLine != null) {
- sock.getOutputStream().write(fileLine.getBytes());
- fileLine = fileReader.readLine();
+ while (fileLine != null) {
+ sock.getOutputStream().write(fileLine.getBytes());
+ fileLine = fileReader.readLine();
}
}
public void sendUserFileGood(Socket sock, String user) throws IOException {
- BufferedReader filenameReader = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
+ BufferedReader filenameReader =
+ new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
String filename = filenameReader.readLine();
// GOOD: ensure that the file is in a designated folder in the user's home directory
if (!filename.contains("..") && filename.startsWith("/home/" + user + "/public/")) {
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
String fileLine = fileReader.readLine();
- while(fileLine != null) {
+ while (fileLine != null) {
sock.getOutputStream().write(fileLine.getBytes());
fileLine = fileReader.readLine();
}
@@ -35,28 +37,28 @@ public void sendUserFileGood(Socket sock, String user) throws IOException {
}
public void sendUserFileGood2(Socket sock, String user) throws Exception {
- BufferedReader filenameReader = new BufferedReader(
- new InputStreamReader(sock.getInputStream(), "UTF-8"));
+ BufferedReader filenameReader =
+ new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
String filename = filenameReader.readLine();
-
+
Path publicFolder = Paths.get("/home/" + user + "/public").normalize().toAbsolutePath();
- Path filePath = publicFolder.resolve(filename).normalize().toAbsolutePath(); // FP until the path-injection sinks are reworked
-
+ Path filePath = publicFolder.resolve(filename).normalize().toAbsolutePath();
+
// GOOD: ensure that the path stays within the public folder
if (!filePath.startsWith(publicFolder + File.separator)) {
throw new IllegalArgumentException("Invalid filename");
}
BufferedReader fileReader = new BufferedReader(new FileReader(filePath.toString()));
String fileLine = fileReader.readLine();
- while(fileLine != null) {
+ while (fileLine != null) {
sock.getOutputStream().write(fileLine.getBytes());
fileLine = fileReader.readLine();
}
}
public void sendUserFileGood3(Socket sock, String user) throws Exception {
- BufferedReader filenameReader = new BufferedReader(
- new InputStreamReader(sock.getInputStream(), "UTF-8"));
+ BufferedReader filenameReader =
+ new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
String filename = filenameReader.readLine();
// GOOD: ensure that the filename has no path separators or parent directory references
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
@@ -64,9 +66,9 @@ public void sendUserFileGood3(Socket sock, String user) throws Exception {
}
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
String fileLine = fileReader.readLine();
- while(fileLine != null) {
+ while (fileLine != null) {
sock.getOutputStream().write(fileLine.getBytes());
fileLine = fileReader.readLine();
- }
+ }
}
}
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql
index e17123ce7810..25e5bf1df875 100644
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql
+++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.ql
@@ -1,11 +1,4 @@
import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.TaintedPathQuery
-
-class HasFlowTest extends InlineFlowTest {
- override predicate hasTaintFlow(DataFlow::Node src, DataFlow::Node sink) {
- TaintedPathFlow::flow(src, sink)
- }
-
- override predicate hasValueFlow(DataFlow::Node src, DataFlow::Node sink) { none() }
-}
+import TaintFlowTest
diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java
index 872f2a01b65f..a29cf1f620ea 100644
--- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java
+++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java
@@ -60,10 +60,8 @@ void test() throws IOException {
// "java.nio.file;Files;false;copy;;;Argument[0];read-file;manual"
Files.copy((Path) source(), (Path) null); // $ hasTaintFlow
Files.copy((Path) source(), (OutputStream) null); // $ hasTaintFlow
- Files.copy((InputStream) source(), null); // $ hasTaintFlow
// "java.nio.file;Files;false;copy;;;Argument[1];create-file;manual"
Files.copy((Path) null, (Path) source()); // $ hasTaintFlow
- Files.copy((Path) null, (OutputStream) source()); // $ hasTaintFlow
Files.copy((InputStream) null, (Path) source()); // $ hasTaintFlow
// "java.nio.file;Files;false;createDirectories;;;Argument[0];create-file;manual"
Files.createDirectories((Path) source()); // $ hasTaintFlow
From 19a6b7858b12c112056420f0af40cc3dca0a488f Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 26 Jan 2024 12:45:00 +0100
Subject: [PATCH 010/155] Remove reference to PathCreation
ZipSlip no longer needs to make this exclusion, since PathCreation arguments are no longer path-injection sinks
---
.../code/java/security/ZipSlipQuery.qll | 25 +------------------
1 file changed, 1 insertion(+), 24 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/ZipSlipQuery.qll b/java/ql/lib/semmle/code/java/security/ZipSlipQuery.qll
index 10db2997bef1..7ba99a31e268 100644
--- a/java/ql/lib/semmle/code/java/security/ZipSlipQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/ZipSlipQuery.qll
@@ -41,28 +41,5 @@ module ZipSlipFlow = TaintTracking::Global;
* A sink that represents a file creation, such as a file write, copy or move operation.
*/
private class FileCreationSink extends DataFlow::Node {
- FileCreationSink() {
- sinkNode(this, "path-injection") and
- not isPathCreation(this)
- }
-}
-
-/**
- * Holds if `sink` is a path creation node that doesn't imply a read/write filesystem operation.
- * This is to avoid creating new spurious alerts, since `PathCreation` sinks weren't
- * previously part of this query.
- */
-private predicate isPathCreation(DataFlow::Node sink) {
- exists(PathCreation pc |
- pc.getAnInput() = sink.asExpr()
- or
- pc.getAnInput().(Argument).isVararg() and sink.(DataFlow::ImplicitVarargsArray).getCall() = pc
- |
- // exclude actual read/write operations included in `PathCreation`
- not pc.(Call)
- .getCallee()
- .getDeclaringType()
- .hasQualifiedName("java.io",
- ["FileInputStream", "FileOutputStream", "FileReader", "FileWriter"])
- )
+ FileCreationSink() { sinkNode(this, "path-injection") }
}
From b8cb514dc45b29b471ec8b321507a10cc97c464d Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 26 Jan 2024 12:46:51 +0100
Subject: [PATCH 011/155] Rename the other change note
---
...ed-path-creation.md => 2024-01-26-deprecated-path-creation.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename java/ql/lib/change-notes/{2023-04-20-deprecated-path-creation.md => 2024-01-26-deprecated-path-creation.md} (100%)
diff --git a/java/ql/lib/change-notes/2023-04-20-deprecated-path-creation.md b/java/ql/lib/change-notes/2024-01-26-deprecated-path-creation.md
similarity index 100%
rename from java/ql/lib/change-notes/2023-04-20-deprecated-path-creation.md
rename to java/ql/lib/change-notes/2024-01-26-deprecated-path-creation.md
From 6e550d28af7e425d0727d7077316fb676f56a1e6 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 26 Jan 2024 13:33:25 +0100
Subject: [PATCH 012/155] Update more test expectations
---
.../SupportedExternalSinks/SupportedExternalSinks.expected | 1 -
.../ql/test/utils/modeleditor/ApplicationModeEndpoints.expected | 2 --
2 files changed, 3 deletions(-)
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
index 5f0ed7d05df2..6cb849601d5e 100644
--- a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
@@ -1,3 +1,2 @@
-| java.io.File#File(String) | 1 |
| java.io.FileWriter#FileWriter(File) | 1 |
| java.net.URL#openStream() | 1 |
diff --git a/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected b/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected
index 919fc09b2610..4d32cb7e922d 100644
--- a/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected
+++ b/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected
@@ -2,11 +2,9 @@
| com/github/codeql/test/PublicClass.java:8:5:8:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:12:5:12:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:16:5:16:45 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
-| com/github/codeql/test/PublicClass.java:16:24:16:44 | get(...) | java.nio.file | Paths | get | (String,String[]) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:16:24:16:44 | get(...) | java.nio.file | Paths | get | (String,String[]) | true | rt.jar | | summary | source |
| com/github/codeql/test/PublicClass.java:20:5:20:68 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:20:24:20:47 | getDefault(...) | java.nio.file | FileSystems | getDefault | () | false | rt.jar | | | source |
-| com/github/codeql/test/PublicClass.java:20:24:20:67 | getPath(...) | java.nio.file | FileSystem | getPath | (String,String[]) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:20:24:20:67 | getPath(...) | java.nio.file | FileSystem | getPath | (String,String[]) | true | rt.jar | | summary | source |
| com/github/codeql/test/PublicClass.java:24:5:24:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicGenericClass.java:7:5:7:27 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
From d8fe0f5bb83ac713dca767043aff9fc7c2223584 Mon Sep 17 00:00:00 2001
From: Marcono1234
Date: Sun, 28 Jan 2024 18:46:18 +0100
Subject: [PATCH 013/155] Java: Document which assignment type is covered by
which class
---
java/ql/lib/semmle/code/java/Expr.qll | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll
index be3976b84588..74f37a4a4514 100644
--- a/java/ql/lib/semmle/code/java/Expr.qll
+++ b/java/ql/lib/semmle/code/java/Expr.qll
@@ -378,7 +378,17 @@ class ArrayInit extends Expr, @arrayinit {
override string getAPrimaryQlClass() { result = "ArrayInit" }
}
-/** A common super-class that represents all varieties of assignments. */
+/**
+ * A common super-class that represents many varieties of assignments.
+ *
+ * This does not cover unary assignments such as `i++`, and initialization of
+ * local variables at their declaration such as `int i = 0;`.
+ *
+ * To cover more cases of variable updates, see the classes `VariableAssign`,
+ * `VariableUpdate` and `VarWrite`. But consider that they don't cover array
+ * element assignments since there the assignment destination is not directly
+ * the array variable but instead an `ArrayAccess`.
+ */
class Assignment extends Expr, @assignment {
/** Gets the destination (left-hand side) of the assignment. */
Expr getDest() { result.isNthChildOf(this, 0) }
@@ -1781,6 +1791,9 @@ class VariableUpdate extends Expr {
/**
* An assignment to a variable or an initialization of the variable.
+ *
+ * This does not cover compound assignments such as `i += 1`, or unary
+ * assignments such as `i++`; use the class `VariableUpdate` for that.
*/
class VariableAssign extends VariableUpdate {
VariableAssign() {
@@ -1979,6 +1992,9 @@ class ExtensionReceiverAccess extends VarAccess {
/**
* A write access to a variable, which occurs as the destination of an assignment.
+ *
+ * This does not cover the initialization of local variables at their declaration,
+ * use the class `VariableUpdate` if you want to cover that as well.
*/
class VarWrite extends VarAccess {
VarWrite() { this.isVarWrite() }
From 3f0dc2b0228782093efe2efebe1e3dda3e5d4897 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 29 Jan 2024 12:10:46 +0100
Subject: [PATCH 014/155] Python: Model the `psycopg` package
---
python/ql/lib/semmle/python/Frameworks.qll | 1 +
.../lib/semmle/python/frameworks/Psycopg.qll | 32 +++++++++++++++++++
.../frameworks/psycopg/ConceptsTest.expected | 2 ++
.../frameworks/psycopg/ConceptsTest.ql | 2 ++
.../frameworks/psycopg/pep249.py | 14 ++++++++
5 files changed, 51 insertions(+)
create mode 100644 python/ql/lib/semmle/python/frameworks/Psycopg.qll
create mode 100644 python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.expected
create mode 100644 python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.ql
create mode 100644 python/ql/test/library-tests/frameworks/psycopg/pep249.py
diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll
index 801a51008ecc..a6288dadc111 100644
--- a/python/ql/lib/semmle/python/Frameworks.qll
+++ b/python/ql/lib/semmle/python/Frameworks.qll
@@ -48,6 +48,7 @@ private import semmle.python.frameworks.Oracledb
private import semmle.python.frameworks.Pandas
private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Phoenixdb
+private import semmle.python.frameworks.Psycopg
private import semmle.python.frameworks.Psycopg2
private import semmle.python.frameworks.Pycurl
private import semmle.python.frameworks.Pydantic
diff --git a/python/ql/lib/semmle/python/frameworks/Psycopg.qll b/python/ql/lib/semmle/python/frameworks/Psycopg.qll
new file mode 100644
index 000000000000..10d4609aaaff
--- /dev/null
+++ b/python/ql/lib/semmle/python/frameworks/Psycopg.qll
@@ -0,0 +1,32 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `psycopg` PyPI package.
+ * See
+ * - https://www.psycopg.org/psycopg3/docs/
+ * - https://pypi.org/project/psycopg/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.frameworks.PEP249
+
+/**
+ * Provides models for the `psycopg` PyPI package.
+ * See
+ * - https://www.psycopg.org/psycopg3/docs/
+ * - https://pypi.org/project/psycopg/
+ */
+private module Psycopg {
+ // ---------------------------------------------------------------------------
+ // Psycopg
+ // ---------------------------------------------------------------------------
+ /**
+ * A model of `psycopg` as a module that implements PEP 249, providing ways to execute SQL statements
+ * against a database.
+ */
+ class Psycopg extends PEP249::PEP249ModuleApiNode {
+ Psycopg() { this = API::moduleImport("psycopg") }
+ }
+}
diff --git a/python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.expected
new file mode 100644
index 000000000000..8ec8033d086e
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.expected
@@ -0,0 +1,2 @@
+testFailures
+failures
diff --git a/python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.ql
new file mode 100644
index 000000000000..b557a0bccb69
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/psycopg/ConceptsTest.ql
@@ -0,0 +1,2 @@
+import python
+import experimental.meta.ConceptsTest
diff --git a/python/ql/test/library-tests/frameworks/psycopg/pep249.py b/python/ql/test/library-tests/frameworks/psycopg/pep249.py
new file mode 100644
index 000000000000..0336facb079e
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/psycopg/pep249.py
@@ -0,0 +1,14 @@
+import psycopg
+
+conn = psycopg.connect(...)
+conn.execute("some sql", (42,)) # $ getSql="some sql"
+cursor = conn.cursor()
+cursor.execute("some sql", (42,)) # $ getSql="some sql"
+cursor.executemany("some sql", [(42,)]) # $ getSql="some sql"
+
+# as in their examples:
+with psycopg.connect(...) as conn:
+ conn.execute("some sql", (42,)) # $ getSql="some sql"
+ with conn.cursor() as cursor:
+ cursor.execute("some sql", (42,)) # $ getSql="some sql"
+ cursor.executemany("some sql", [(42,)]) # $ getSql="some sql"
From 5867fb3d2925aa25ebe633d7921a1e716a8b217e Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 29 Jan 2024 12:12:07 +0100
Subject: [PATCH 015/155] Python: Add change-note
---
python/ql/lib/change-notes/2024-01-29-psycopg-modeling.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 python/ql/lib/change-notes/2024-01-29-psycopg-modeling.md
diff --git a/python/ql/lib/change-notes/2024-01-29-psycopg-modeling.md b/python/ql/lib/change-notes/2024-01-29-psycopg-modeling.md
new file mode 100644
index 000000000000..007cde7fb347
--- /dev/null
+++ b/python/ql/lib/change-notes/2024-01-29-psycopg-modeling.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* Added modeling of the `psycopg` PyPI package as a SQL database library.
From e441dd472befacec1b47f9df5b42403783408e7f Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 16 Jan 2024 12:34:48 +0100
Subject: [PATCH 016/155] JS: Expose hasBothNamedAndDefaultExports()
---
.../lib/semmle/javascript/ES2015Modules.qll | 27 ++++++++++---------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll
index d32278371c69..1bdfec7ffe9d 100644
--- a/javascript/ql/lib/semmle/javascript/ES2015Modules.qll
+++ b/javascript/ql/lib/semmle/javascript/ES2015Modules.qll
@@ -39,6 +39,20 @@ class ES2015Module extends Module {
// modules are implicitly strict
any()
}
+
+ /**
+ * Holds if this module contains both named and `default` exports.
+ *
+ * This is used to determine whether a default-import of the module should be reinterpreted
+ * as a namespace-import, to accommodate the non-standard behavior implemented by some compilers.
+ *
+ * When a module has both named and `default` exports, the non-standard interpretation can lead to
+ * ambiguities, so we only allow the standard interpretation in that case.
+ */
+ predicate hasBothNamedAndDefaultExports() {
+ hasNamedExports(this) and
+ hasDefaultExport(this)
+ }
}
/**
@@ -64,17 +78,6 @@ private predicate hasDefaultExport(ES2015Module mod) {
mod.getAnExport().(ExportNamedDeclaration).getASpecifier().getExportedName() = "default"
}
-/**
- * Holds if `mod` contains both named and `default` exports.
- *
- * This is used to determine whether a default-import of the module should be reinterpreted
- * as a namespace-import, to accommodate the non-standard behavior implemented by some compilers.
- */
-private predicate hasBothNamedAndDefaultExports(ES2015Module mod) {
- hasNamedExports(mod) and
- hasDefaultExport(mod)
-}
-
/**
* An import declaration.
*
@@ -131,7 +134,7 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
// For compatibility with the non-standard implementation of default imports,
// treat default imports as namespace imports in cases where it can't cause ambiguity
// between named exports and the properties of a default-exported object.
- not hasBothNamedAndDefaultExports(this.getImportedModule()) and
+ not this.getImportedModule().(ES2015Module).hasBothNamedAndDefaultExports() and
is.getImportedName() = "default"
)
or
From 0e0fb0e52dcc7d6dde1f4dae54df825c70052e5c Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 16 Jan 2024 14:29:09 +0100
Subject: [PATCH 017/155] JS: Remove API graph edge causing ambiguity
---
javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
index dc9844bf8bd9..2e4e96aa46ad 100644
--- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
+++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
@@ -1621,6 +1621,7 @@ private predicate exports(string m, DataFlow::Node rhs) {
exists(Module mod | mod = importableModule(m) |
rhs = mod.(AmdModule).getDefine().getModuleExpr().flow()
or
+ not mod.(ES2015Module).hasBothNamedAndDefaultExports() and
exports(m, "default", rhs)
or
exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod |
From 2d8d11fa7840a62dfd3b4e4f6b3380ea22bf1afb Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 17 Jan 2024 15:35:33 +0100
Subject: [PATCH 018/155] JS: Restrict type-only exports in API graphs
---
javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
index 2e4e96aa46ad..c7d911eac63f 100644
--- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
+++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
@@ -1635,6 +1635,7 @@ private predicate exports(string m, DataFlow::Node rhs) {
/** Holds if module `m` exports `rhs` under the name `prop`. */
private predicate exports(string m, string prop, DataFlow::Node rhs) {
exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) |
+ not exp.isTypeOnly() and
rhs = exp.getSourceNode(prop)
or
exists(Variable v |
From 8930ce74afa573cc3c3e3b056c57b9c3aaa754b3 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 16 Jan 2024 12:13:14 +0100
Subject: [PATCH 019/155] JS: Do not view packages as nested in a private
package
---
javascript/ql/lib/semmle/javascript/NPM.qll | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/NPM.qll b/javascript/ql/lib/semmle/javascript/NPM.qll
index 0bf92c5d29af..137641e119ae 100644
--- a/javascript/ql/lib/semmle/javascript/NPM.qll
+++ b/javascript/ql/lib/semmle/javascript/NPM.qll
@@ -29,7 +29,8 @@ class PackageJson extends JsonObject {
parentDir.getAChildContainer+() = currentDir and
pkgNameDiff = currentDir.getAbsolutePath().suffix(parentDir.getAbsolutePath().length()) and
not exists(pkgNameDiff.indexOf("/node_modules/")) and
- result = parentPkgName + pkgNameDiff
+ result = parentPkgName + pkgNameDiff and
+ not parentPkg.isPrivate()
)
}
From 6cfdd7aec463ac4d0a9ef566e53cbcf0a1aa4df5 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 15 Jan 2024 13:53:16 +0100
Subject: [PATCH 020/155] JS: Add InlineExpectationsTest
---
javascript/ql/test/qlpack.yml | 1 +
.../ql/test/testUtilities/InlineExpectationsTest.qll | 8 ++++++++
.../internal/InlineExpectationsTestImpl.qll | 12 ++++++++++++
3 files changed, 21 insertions(+)
create mode 100644 javascript/ql/test/testUtilities/InlineExpectationsTest.qll
create mode 100644 javascript/ql/test/testUtilities/internal/InlineExpectationsTestImpl.qll
diff --git a/javascript/ql/test/qlpack.yml b/javascript/ql/test/qlpack.yml
index 8976782483a8..df7e46f5df76 100644
--- a/javascript/ql/test/qlpack.yml
+++ b/javascript/ql/test/qlpack.yml
@@ -3,6 +3,7 @@ groups: [javascript, test]
dependencies:
codeql/javascript-all: ${workspace}
codeql/javascript-queries: ${workspace}
+ codeql/util: ${workspace}
extractor: javascript
tests: .
warnOnImplicitThis: true
diff --git a/javascript/ql/test/testUtilities/InlineExpectationsTest.qll b/javascript/ql/test/testUtilities/InlineExpectationsTest.qll
new file mode 100644
index 000000000000..b1953314078a
--- /dev/null
+++ b/javascript/ql/test/testUtilities/InlineExpectationsTest.qll
@@ -0,0 +1,8 @@
+/**
+ * Inline expectation tests for JS.
+ * See `shared/util/codeql/util/test/InlineExpectationsTest.qll`
+ */
+
+private import codeql.util.test.InlineExpectationsTest
+private import internal.InlineExpectationsTestImpl
+import Make
diff --git a/javascript/ql/test/testUtilities/internal/InlineExpectationsTestImpl.qll b/javascript/ql/test/testUtilities/internal/InlineExpectationsTestImpl.qll
new file mode 100644
index 000000000000..d1de2866b105
--- /dev/null
+++ b/javascript/ql/test/testUtilities/internal/InlineExpectationsTestImpl.qll
@@ -0,0 +1,12 @@
+private import javascript as JS
+private import codeql.util.test.InlineExpectationsTest
+
+module Impl implements InlineExpectationsTestSig {
+ private import javascript
+
+ class ExpectationComment extends LineComment {
+ string getContents() { result = this.getText() }
+ }
+
+ class Location = JS::Location;
+}
From e2bf9ea2eba40753dd99bd298f97ddf1afce1cb1 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Tue, 30 Jan 2024 10:43:56 +0100
Subject: [PATCH 021/155] Consider File.exists() et al a path-injection sink
---
java/ql/lib/ext/java.io.model.yml | 2 +-
java/ql/lib/ext/java.nio.file.model.yml | 4 ++--
.../query-tests/security/CWE-073/FilePathInjection.expected | 3 +++
java/ql/test/library-tests/neutrals/neutralsinks/Test.java | 3 ---
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/java/ql/lib/ext/java.io.model.yml b/java/ql/lib/ext/java.io.model.yml
index 17dbc1464dc4..1ba027dbbb0b 100644
--- a/java/ql/lib/ext/java.io.model.yml
+++ b/java/ql/lib/ext/java.io.model.yml
@@ -5,6 +5,7 @@ extensions:
data:
- ["java.io", "File", True, "createNewFile", "()", "", "Argument[this]", "path-injection", "ai-manual"]
- ["java.io", "File", True, "createTempFile", "(String,String,File)", "", "Argument[2]", "path-injection", "ai-manual"]
+ - ["java.io", "File", True, "exists", "()", "", "Argument[this]", "path-injection", "manual"]
- ["java.io", "File", True, "renameTo", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileInputStream", True, "FileInputStream", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileInputStream", True, "FileInputStream", "(FileDescriptor)", "", "Argument[0]", "path-injection", "manual"]
@@ -126,7 +127,6 @@ extensions:
- ["java.io", "DataOutput", "writeLong", "(long)", "summary", "manual"] # taint-numeric
# sink neutrals
- ["java.io", "File", "compareTo", "", "sink", "hq-manual"]
- - ["java.io", "File", "exists", "()", "sink", "hq-manual"]
- addsTo:
pack: codeql/java-all
extensible: sourceModel
diff --git a/java/ql/lib/ext/java.nio.file.model.yml b/java/ql/lib/ext/java.nio.file.model.yml
index a35c575e9cb5..946f5653db6f 100644
--- a/java/ql/lib/ext/java.nio.file.model.yml
+++ b/java/ql/lib/ext/java.nio.file.model.yml
@@ -18,6 +18,7 @@ extensions:
- ["java.nio.file", "Files", False, "delete", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "deleteIfExists", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "getFileStore", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"] # the FileStore class is unlikely to be used for later sanitization
+ - ["java.nio.file", "Files", False, "exists", "(Path,LinkOption[])", "", "Argument[0]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "lines", "(Path,Charset)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "lines", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "move", "", "", "Argument[1]", "path-injection", "manual"]
@@ -27,6 +28,7 @@ extensions:
- ["java.nio.file", "Files", False, "newBufferedWriter", "", "", "Argument[0]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "newInputStream", "(Path,OpenOption[])", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "newOutputStream", "", "", "Argument[0]", "path-injection", "manual"]
+ - ["java.nio.file", "Files", False, "notExists", "(Path,LinkOption[])", "", "Argument[0]", "path-injection", "manual"]
- ["java.nio.file", "Files", False, "probeContentType", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"] # accesses the file based on user input, but only reads its content type from it
- ["java.nio.file", "Files", False, "readAllBytes", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.nio.file", "Files", False, "readAllLines", "(Path,Charset)", "", "Argument[0]", "path-injection", "ai-manual"]
@@ -89,7 +91,6 @@ extensions:
# summary neutrals
- ["java.nio.file", "Files", "exists", "(Path,LinkOption[])", "summary", "manual"]
# sink neutrals
- - ["java.nio.file", "Files", "exists", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "getLastModifiedTime", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "getOwner", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "getPosixFilePermissions", "", "sink", "hq-manual"]
@@ -101,6 +102,5 @@ extensions:
- ["java.nio.file", "Files", "isSameFile", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "isSymbolicLink", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "isWritable", "", "sink", "hq-manual"]
- - ["java.nio.file", "Files", "notExists", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "setLastModifiedTime", "", "sink", "hq-manual"]
- ["java.nio.file", "Files", "size", "", "sink", "hq-manual"]
diff --git a/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected
index 07be573dbf8e..c7a79c6328d6 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-073/FilePathInjection.expected
@@ -4,6 +4,7 @@ edges
| FilePathInjection.java:87:21:87:34 | getPara(...) : String | FilePathInjection.java:95:47:95:59 | finalFilePath |
| FilePathInjection.java:177:50:177:58 | file : File | FilePathInjection.java:182:30:182:33 | file |
| FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:209:24:209:31 | filePath : String |
+| FilePathInjection.java:209:15:209:32 | new File(...) : File | FilePathInjection.java:210:23:210:26 | file |
| FilePathInjection.java:209:15:209:32 | new File(...) : File | FilePathInjection.java:217:19:217:22 | file : File |
| FilePathInjection.java:209:24:209:31 | filePath : String | FilePathInjection.java:209:15:209:32 | new File(...) : File |
| FilePathInjection.java:217:19:217:22 | file : File | FilePathInjection.java:177:50:177:58 | file : File |
@@ -19,6 +20,7 @@ nodes
| FilePathInjection.java:205:17:205:44 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| FilePathInjection.java:209:15:209:32 | new File(...) : File | semmle.label | new File(...) : File |
| FilePathInjection.java:209:24:209:31 | filePath : String | semmle.label | filePath : String |
+| FilePathInjection.java:210:23:210:26 | file | semmle.label | file |
| FilePathInjection.java:217:19:217:22 | file : File | semmle.label | file : File |
subpaths
#select
@@ -26,3 +28,4 @@ subpaths
| FilePathInjection.java:72:47:72:59 | finalFilePath | FilePathInjection.java:64:21:64:34 | getPara(...) : String | FilePathInjection.java:72:47:72:59 | finalFilePath | External control of file name or path due to $@. | FilePathInjection.java:64:21:64:34 | getPara(...) | user-provided value |
| FilePathInjection.java:95:47:95:59 | finalFilePath | FilePathInjection.java:87:21:87:34 | getPara(...) : String | FilePathInjection.java:95:47:95:59 | finalFilePath | External control of file name or path due to $@. | FilePathInjection.java:87:21:87:34 | getPara(...) | user-provided value |
| FilePathInjection.java:182:30:182:33 | file | FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:182:30:182:33 | file | External control of file name or path due to $@. | FilePathInjection.java:205:17:205:44 | getParameter(...) | user-provided value |
+| FilePathInjection.java:210:23:210:26 | file | FilePathInjection.java:205:17:205:44 | getParameter(...) : String | FilePathInjection.java:210:23:210:26 | file | External control of file name or path due to $@. | FilePathInjection.java:205:17:205:44 | getParameter(...) | user-provided value |
diff --git a/java/ql/test/library-tests/neutrals/neutralsinks/Test.java b/java/ql/test/library-tests/neutrals/neutralsinks/Test.java
index a234132226f9..a563cb78de7c 100644
--- a/java/ql/test/library-tests/neutrals/neutralsinks/Test.java
+++ b/java/ql/test/library-tests/neutrals/neutralsinks/Test.java
@@ -14,11 +14,9 @@ public void test() throws Exception {
// java.io
File file = null;
- file.exists(); // $ isNeutralSink
file.compareTo(null); // $ isNeutralSink
// java.nio.file
- Files.exists(null, (LinkOption[])null); // $ isNeutralSink
Files.getLastModifiedTime(null, (LinkOption[])null); // $ isNeutralSink
Files.getOwner(null, (LinkOption[])null); // $ isNeutralSink
Files.getPosixFilePermissions(null, (LinkOption[])null); // $ isNeutralSink
@@ -30,7 +28,6 @@ public void test() throws Exception {
Files.isSameFile(null, null); // $ isNeutralSink
Files.isSymbolicLink(null); // $ isNeutralSink
Files.isWritable(null); // $ isNeutralSink
- Files.notExists(null, (LinkOption[])null); // $ isNeutralSink
Files.setLastModifiedTime(null, null); // $ isNeutralSink
Files.size(null); // $ isNeutralSink
From 1737ba1a6bfe1e7dac921c47ca916a062beb351a Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 15 Jan 2024 13:54:41 +0100
Subject: [PATCH 022/155] JS: Add library for naming endpoints
---
.../ql/lib/semmle/javascript/ApiGraphs.qll | 3 +
.../javascript/endpoints/EndpointNaming.qll | 459 ++++++++++++++++++
.../EndpointNaming/EndpointNaming.expected | 7 +
.../EndpointNaming/EndpointNaming.ql | 41 ++
.../EndpointNaming/pack1/main.js | 13 +
.../EndpointNaming/pack1/package.json | 4 +
.../EndpointNaming/pack10/foo.js | 1 +
.../EndpointNaming/pack10/index.js | 3 +
.../EndpointNaming/pack10/package.json | 4 +
.../library-tests/EndpointNaming/pack2/lib.js | 6 +
.../EndpointNaming/pack2/main.js | 9 +
.../EndpointNaming/pack2/package.json | 4 +
.../library-tests/EndpointNaming/pack3/lib.js | 1 +
.../EndpointNaming/pack3/main.js | 7 +
.../EndpointNaming/pack3/package.json | 4 +
.../EndpointNaming/pack4/index.js | 1 +
.../EndpointNaming/pack4/package.json | 4 +
.../EndpointNaming/pack5/package.json | 4 +
.../EndpointNaming/pack5/src/index.js | 1 +
.../EndpointNaming/pack6/index.js | 6 +
.../EndpointNaming/pack6/package.json | 4 +
.../EndpointNaming/pack7/index.js | 6 +
.../EndpointNaming/pack7/package.json | 4 +
.../library-tests/EndpointNaming/pack8/foo.js | 5 +
.../EndpointNaming/pack8/index.js | 5 +
.../EndpointNaming/pack8/package.json | 4 +
.../library-tests/EndpointNaming/pack9/foo.js | 1 +
.../EndpointNaming/pack9/index.ts | 9 +
.../EndpointNaming/pack9/package.json | 4 +
29 files changed, 624 insertions(+)
create mode 100644 javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack1/main.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack1/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack10/index.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack10/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack2/main.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack2/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack3/main.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack3/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack4/index.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack4/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack5/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack6/index.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack6/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack7/index.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack7/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack8/index.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack8/package.json
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts
create mode 100644 javascript/ql/test/library-tests/EndpointNaming/pack9/package.json
diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
index c7d911eac63f..0bd79dd20291 100644
--- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
+++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
@@ -594,6 +594,9 @@ module API {
exportedName = "" and
result = getAModuleImportRaw(moduleName)
}
+
+ /** Gets a sink node that represents instances of `cls`. */
+ Node getClassInstance(DataFlow::ClassNode cls) { result = Impl::MkClassInstance(cls) }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
new file mode 100644
index 000000000000..5d4a067874c7
--- /dev/null
+++ b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
@@ -0,0 +1,459 @@
+/**
+ * Provides predicates for generating names for classes and functions that are part
+ * of the public API of a library.
+ *
+ * When possible, we try to use the qualified name by which a class/function can be accessed
+ * from client code.
+ *
+ * However, there are cases where classes and functions can be exposed to client
+ * code without being accessible as a qualified name. For example;
+ * ```js
+ * // 'Foo' is internal, but clients can reach its methods via `getFoo().m()`
+ * class Foo {
+ * m() {}
+ * }
+ * export function getFoo() {
+ * return new Foo();
+ * }
+ *
+ * // Clients can reach m() via getObj().m()
+ * export function getObj() {
+ * return {
+ * m() {}
+ * }
+ * }
+ * ```
+ *
+ * In these cases, we try to make up human-readable names for the endpoints.
+ * We make an effort to make these unambiguous in practice, though this is not always guaranteed.
+ */
+
+private import javascript
+
+/** Concatenates two access paths. */
+bindingset[x, y]
+private string join(string x, string y) {
+ if x = "" or y = "" then result = x + y else result = x + "." + y
+}
+
+private predicate isPackageExport(API::Node node) { node = API::moduleExport(_) }
+
+private predicate memberEdge(API::Node pred, API::Node succ) { succ = pred.getAMember() }
+
+/** Gets the shortest distance from a packaeg export to `nd` in the API graph. */
+private int distanceFromPackageExport(API::Node nd) =
+ shortestDistances(isPackageExport/1, memberEdge/2)(_, nd, result)
+
+private predicate isExported(API::Node node) {
+ isPackageExport(node)
+ or
+ exists(API::Node pred |
+ isExported(pred) and
+ memberEdge(pred, node)
+ )
+}
+
+/**
+ * Holds if `node` is a default export that can be reinterpreted as a namespace export,
+ * because the enclosing module has no named exports.
+ */
+private predicate defaultExportCanBeInterpretedAsNamespaceExport(API::Node node) {
+ exists(ES2015Module mod |
+ node.asSink() = mod.getAnExportedValue("default") and
+ not mod.hasBothNamedAndDefaultExports()
+ )
+}
+
+private predicate isPrivateAssignment(DataFlow::Node node) {
+ exists(MemberDeclaration decl |
+ node = decl.getInit().flow() and
+ decl.isPrivate()
+ )
+ or
+ exists(DataFlow::PropWrite write |
+ write.isPrivateField() and
+ node = write.getRhs()
+ )
+}
+
+private predicate isPrivateLike(API::Node node) { isPrivateAssignment(node.asSink()) }
+
+private API::Node getASuccessor(API::Node node, string name, int badness) {
+ isExported(node) and
+ exists(string member |
+ result = node.getMember(member) and
+ not isPrivateLike(node) and
+ if member = "default"
+ then
+ if defaultExportCanBeInterpretedAsNamespaceExport(node)
+ then (
+ badness = 5 and name = ""
+ ) else (
+ badness = 10 and name = "default"
+ )
+ else (
+ name = member and badness = 0
+ )
+ )
+}
+
+private API::Node getAPredecessor(API::Node node, string name, int badness) {
+ node = getASuccessor(result, name, badness)
+}
+
+/**
+ * Gets the predecessor of `node` to use when constructing a qualified name for it,
+ * and binds `name` and `badness` corresponding to the label on that edge.
+ */
+private API::Node getPreferredPredecessor(API::Node node, string name, int badness) {
+ // For root nodes, we prefer not having a predecessor, as we use the package name.
+ not isPackageExport(node) and
+ // Rank predecessors by name-badness, export-distance, and name.
+ // Since min() can only return a single value, we need a separate min() call per column.
+ badness = min(int b | exists(getAPredecessor(node, _, b)) | b) and
+ result =
+ min(API::Node pred, string name1 |
+ pred = getAPredecessor(node, name1, badness)
+ |
+ pred order by distanceFromPackageExport(pred), name1
+ ) and
+ name = min(string n | result = getAPredecessor(node, n, badness) | n)
+}
+
+/**
+ * Holds if values escpin
+ */
+private predicate sinkHasNameCandidate(API::Node sink, string package, string name, int badness) {
+ sink = API::moduleExport(package) and
+ name = "" and
+ badness = 0
+ or
+ exists(API::Node baseNode, string baseName, int baseBadness, string step, int stepBadness |
+ sinkHasNameCandidate(baseNode, package, baseName, baseBadness) and
+ baseNode = getPreferredPredecessor(sink, step, stepBadness) and
+ badness = (baseBadness + stepBadness).minimum(20) and
+ name = join(baseName, step)
+ )
+}
+
+/**
+ * Holds if `(package, name)` is the primary name to associate with `node`.
+ *
+ * `badness` is bound to the associated badness of the name.
+ */
+private predicate sinkHasPrimaryName(API::Node sink, string package, string name, int badness) {
+ badness = min(int b | sinkHasNameCandidate(sink, _, _, b) | b) and
+ package = min(string p | sinkHasNameCandidate(sink, p, _, badness) | p) and
+ name = min(string n | sinkHasNameCandidate(sink, package, n, badness) | n order by n.length(), n)
+}
+
+/**
+ * Holds if `(package, name)` is the primary name to associate with `node`.
+ */
+private predicate sinkHasPrimaryName(API::Node sink, string package, string name) {
+ sinkHasPrimaryName(sink, package, name, _)
+}
+
+/**
+ * Holds if `(package, name)` is an alias for `node`.
+ *
+ * This means it is a valid name for it, but was not chosen as the primary name.
+ */
+predicate sinkHasAlias(API::Node sink, string package, string name) {
+ not sinkHasPrimaryName(sink, package, name) and
+ (
+ exists(string baseName, string step |
+ sinkHasPrimaryName(getAPredecessor(sink, step, _), package, baseName) and
+ name = join(baseName, step)
+ )
+ or
+ sink = API::moduleExport(package) and
+ name = ""
+ )
+}
+
+/** Gets a sink node reachable from `node`. */
+bindingset[node]
+private API::Node getASinkNode(DataFlow::SourceNode node) { result.getAValueReachingSink() = node }
+
+bindingset[qualifiedName]
+private int getBadnessOfClassName(string qualifiedName) {
+ if qualifiedName.matches("%.constructor")
+ then result = 10
+ else
+ if qualifiedName = ""
+ then result = 5
+ else result = 0
+}
+
+/** Holds if `(package, name)` is a potential name for `cls`, with the given `badness`. */
+private predicate classObjectHasNameCandidate(
+ DataFlow::ClassNode cls, string package, string name, int badness
+) {
+ // There can be multiple API nodes associated with `cls`.
+ // For example:
+ ///
+ // class C {}
+ // module.exports.A = C; // first sink
+ // module.exports.B = C; // second sink
+ //
+ exists(int baseBadness |
+ sinkHasPrimaryName(getASinkNode(cls), package, name, baseBadness) and
+ badness = baseBadness + getBadnessOfClassName(name)
+ )
+}
+
+private predicate classObjectHasPrimaryName(
+ DataFlow::ClassNode cls, string package, string name, int badness
+) {
+ badness = min(int b | classObjectHasNameCandidate(cls, _, _, b) | b) and
+ package = min(string p | classObjectHasNameCandidate(cls, p, _, badness) | p) and
+ name = min(string n | classObjectHasNameCandidate(cls, package, n, badness) | n)
+}
+
+/** Holds if `(package, name)` is the primary name for the class object of `cls`. */
+predicate classObjectHasPrimaryName(DataFlow::ClassNode cls, string package, string name) {
+ classObjectHasPrimaryName(cls, package, name, _)
+}
+
+/** Holds if `(package, name)` is an alias for the class object of `cls`. */
+predicate classObjectHasAlias(DataFlow::ClassNode cls, string package, string name) {
+ not classObjectHasPrimaryName(cls, package, name) and
+ exists(int badness |
+ classObjectHasNameCandidate(cls, package, name, badness) and
+ badness < 100
+ )
+}
+
+/** Holds if an instance of `cls` can be exposed to client code. */
+private predicate hasEscapingInstance(DataFlow::ClassNode cls) {
+ cls.getAnInstanceReference().flowsTo(any(API::Node n).asSink())
+}
+
+/**
+ * Holds if `(package, name)` is a potential name to use for instances of `cls`, with the given `badness`.
+ */
+private predicate classInstanceHasNameCandidate(
+ DataFlow::ClassNode cls, string package, string name, int badness
+) {
+ exists(string baseName |
+ classObjectHasPrimaryName(cls, package, baseName, badness) and
+ name = join(baseName, "prototype")
+ )
+ or
+ // In case the class itself is unaccessible, but an instance is exposed via an access path,
+ // consider using that access path. For example:
+ //
+ // class InternalClass {}
+ // module.exports.foo = new InternalClass();
+ //
+ exists(int baseBadness |
+ sinkHasPrimaryName(getASinkNode(cls.getAnInstanceReference()), package, name, baseBadness) and
+ badness = baseBadness + 30 // add penalty, as we prefer to base this on the class name
+ )
+ or
+ // If neither the class nor its instances are accessible via an access path, but instances of the
+ // class can still escape via more complex access patterns, resort to a synthesized name.
+ // For example:
+ //
+ // class InternalClass {}
+ // function foo() {
+ // return new InternalClass();
+ // }
+ //
+ hasEscapingInstance(cls) and
+ exists(string baseName |
+ InternalModuleNaming::fallbackModuleName(cls.getTopLevel(), package, baseName, badness - 100) and
+ name = join(baseName, cls.getName()) + ".prototype"
+ )
+}
+
+private predicate classInstanceHasPrimaryName(
+ DataFlow::ClassNode cls, string package, string name, int badness
+) {
+ badness = min(int b | classInstanceHasNameCandidate(cls, _, _, b) | b) and
+ package = min(string p | classInstanceHasNameCandidate(cls, p, _, badness) | p) and
+ name =
+ min(string n |
+ classInstanceHasNameCandidate(cls, package, n, badness)
+ |
+ n order by n.length(), n
+ )
+}
+
+/** Holds if `(package, name)` is the primary name to use for instances of `cls`. */
+predicate classInstanceHasPrimaryName(DataFlow::ClassNode cls, string package, string name) {
+ classInstanceHasPrimaryName(cls, package, name, _)
+}
+
+/** Holds if `(package, name)` is an alias referring to some instance of `cls`. */
+predicate classInstanceHasAlias(DataFlow::ClassNode cls, string package, string name) {
+ not classInstanceHasPrimaryName(cls, package, name) and
+ exists(int badness |
+ classInstanceHasNameCandidate(cls, package, name, badness) and
+ badness < 100 // Badness 100 is when we start to synthesize names. Do not suggest these as aliases.
+ )
+}
+
+private predicate functionHasNameCandidate(
+ DataFlow::FunctionNode function, string package, string name, int badness
+) {
+ sinkHasPrimaryName(getASinkNode(function), package, name, badness)
+ or
+ exists(DataFlow::ClassNode cls |
+ function = cls.getConstructor() and
+ classObjectHasPrimaryName(cls, package, name, badness)
+ or
+ exists(string baseName, string memberName |
+ function = cls.getInstanceMethod(memberName) and
+ classInstanceHasPrimaryName(cls, package, baseName, badness) and
+ name = join(baseName, memberName)
+ or
+ function = cls.getStaticMethod(memberName) and
+ classObjectHasPrimaryName(cls, package, baseName, badness) and
+ name = join(baseName, memberName)
+ )
+ )
+}
+
+private predicate functionHasPrimaryName(
+ DataFlow::FunctionNode function, string package, string name, int badness
+) {
+ badness = min(int b | functionHasNameCandidate(function, _, _, b) | b) and
+ package = min(string p | functionHasNameCandidate(function, p, _, badness) | p) and
+ name =
+ min(string n |
+ functionHasNameCandidate(function, package, n, badness)
+ |
+ n order by n.length(), n
+ )
+}
+
+/**
+ * Holds if `(package, name)` is the primary name for the given `function`.
+ */
+predicate functionHasPrimaryName(DataFlow::FunctionNode function, string package, string name) {
+ functionHasPrimaryName(function, package, name, _)
+}
+
+/**
+ * Holds if `(package, name)` is an alias for the given `function`.
+ */
+predicate functionHasAlias(DataFlow::FunctionNode function, string package, string name) {
+ not functionHasPrimaryName(function, package, name) and
+ exists(int badness |
+ functionHasNameCandidate(function, package, name, badness) and
+ badness < 100
+ )
+}
+
+/**
+ * Converts a `(package, name)` pair to a string of form `(package).name`.
+ */
+bindingset[package, name]
+string renderName(string package, string name) { result = join("(" + package + ")", name) }
+
+/**
+ * Contains predicates for naming individual modules (i.e. files) inside of a package.
+ *
+ * These names are not necessarily part of a package's public API, and so we only used them
+ * as a fallback when a publicly-accessible access path cannot be found.
+ */
+private module InternalModuleNaming {
+ /** Gets the path to `folder` relative to its enclosing non-private `package.json` file. */
+ private string getPackageRelativePathFromFolder(Folder folder) {
+ exists(PackageJson json |
+ json.getFile() = folder.getFile("package.json") and
+ not json.isPrivate() and
+ result = json.getPackageName()
+ )
+ or
+ not exists(folder.getFile("package.json")) and
+ result =
+ getPackageRelativePathFromFolder(folder.getParentContainer()) + "/" + folder.getBaseName()
+ }
+
+ private string getPackageRelativePath(Module mod) {
+ exists(PackageJson json, string relativePath |
+ not json.isPrivate() and
+ json.getExportedModule(relativePath) = mod and
+ if relativePath = "."
+ then result = json.getPackageName()
+ else result = json.getPackageName() + "/" + relativePath.regexpReplaceAll("^\\./", "")
+ )
+ or
+ not mod = any(PackageJson json | not json.isPrivate()).getExportedModule(_) and
+ not mod.isAmbient() and
+ exists(string folderPath |
+ folderPath = getPackageRelativePathFromFolder(mod.getFile().getParentContainer()) and
+ if mod.getName() = "index"
+ then result = folderPath
+ else result = folderPath + "/" + mod.getName()
+ )
+ }
+
+ /** Holds if `(package, name)` should be used to refer to code inside `mod`. */
+ predicate fallbackModuleName(Module mod, string package, string name, int badness) {
+ sinkHasPrimaryName(getASinkNode(mod.getDefaultOrBulkExport()), package, name, badness)
+ or
+ badness = 50 and
+ package = getPackageRelativePath(mod) and
+ name = ""
+ }
+}
+
+/**
+ * Contains query predicates for emitting debugging information about endpoint naming.
+ */
+module Debug {
+ /** Holds if `node` has multiple preferred predecessors. */
+ query predicate ambiguousPreferredPredecessor(API::Node node) {
+ strictcount(API::Node pred, string name, int badness |
+ pred = getPreferredPredecessor(node, name, badness)
+ ) > 1
+ }
+
+ /** Holds if the given `node` has multiple primary names. */
+ query string ambiguousSinkName(API::Node node) {
+ strictcount(string package, string name | sinkHasPrimaryName(node, package, name)) > 1 and
+ result =
+ concat(string package, string name |
+ sinkHasPrimaryName(node, package, name)
+ |
+ renderName(package, name), ", "
+ )
+ }
+
+ /** Holds if the given `node` has multiple primary names. */
+ query string ambiguousClassObjectName(DataFlow::ClassNode node) {
+ strictcount(string package, string name | classObjectHasPrimaryName(node, package, name)) > 1 and
+ result =
+ concat(string package, string name |
+ classObjectHasPrimaryName(node, package, name)
+ |
+ renderName(package, name), ", "
+ )
+ }
+
+ /** Holds if the given `node` has multiple primary names. */
+ query string ambiguousClassInstanceName(DataFlow::ClassNode node) {
+ strictcount(string package, string name | classInstanceHasPrimaryName(node, package, name)) > 1 and
+ result =
+ concat(string package, string name |
+ classInstanceHasPrimaryName(node, package, name)
+ |
+ renderName(package, name), ", "
+ )
+ }
+
+ /** Holds if the given `node` has multiple primary names. */
+ query string ambiguousFunctionName(DataFlow::FunctionNode node) {
+ strictcount(string package, string name | functionHasPrimaryName(node, package, name)) > 1 and
+ result =
+ concat(string package, string name |
+ functionHasPrimaryName(node, package, name)
+ |
+ renderName(package, name), ", "
+ )
+ }
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected
new file mode 100644
index 000000000000..e0cc251f9039
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected
@@ -0,0 +1,7 @@
+testFailures
+ambiguousPreferredPredecessor
+ambiguousSinkName
+ambiguousClassObjectName
+ambiguousClassInstanceName
+ambiguousFunctionName
+failures
diff --git a/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql
new file mode 100644
index 000000000000..102ebb721a86
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql
@@ -0,0 +1,41 @@
+import javascript
+import semmle.javascript.RestrictedLocations
+import semmle.javascript.Lines
+import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
+import testUtilities.InlineExpectationsTest
+import EndpointNaming::Debug
+
+module TestConfig implements TestSig {
+ string getARelevantTag() {
+ result = "instance"
+ or
+ result = "class"
+ or
+ result = "method"
+ }
+
+ predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(string package, string name |
+ element = "" and
+ value = EndpointNaming::renderName(package, name)
+ |
+ exists(DataFlow::ClassNode cls | location = cls.getAstNode().getLocation() |
+ tag = "class" and
+ EndpointNaming::classObjectHasPrimaryName(cls, package, name)
+ or
+ tag = "instance" and
+ EndpointNaming::classInstanceHasPrimaryName(cls, package, name)
+ )
+ or
+ element = "" and
+ exists(DataFlow::FunctionNode function |
+ not function.getFunction() = any(ConstructorDeclaration decl | decl.isSynthetic()).getBody() and
+ location = function.getFunction().getLocation() and
+ tag = "method" and
+ EndpointNaming::functionHasPrimaryName(function, package, name)
+ )
+ )
+ }
+}
+
+import MakeTest
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack1/main.js b/javascript/ql/test/library-tests/EndpointNaming/pack1/main.js
new file mode 100644
index 000000000000..ead8000ff147
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack1/main.js
@@ -0,0 +1,13 @@
+export class PublicClass {} // $ class=(pack1).PublicClass instance=(pack1).PublicClass.prototype
+
+class PrivateClass {}
+
+export const ExportedConst = class ExportedConstClass {} // $ class=(pack1).ExportedConst instance=(pack1).ExportedConst.prototype
+
+class ClassWithEscapingInstance {} // $ instance=(pack1).ClassWithEscapingInstance.prototype
+
+export function getEscapingInstance() {
+ return new ClassWithEscapingInstance();
+} // $ method=(pack1).getEscapingInstance
+
+export function publicFunction() {} // $ method=(pack1).publicFunction
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack1/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack1/package.json
new file mode 100644
index 000000000000..da2dbb94dd1b
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack1/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack1",
+ "main": "./main.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js b/javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js
new file mode 100644
index 000000000000..3495843defeb
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js
@@ -0,0 +1 @@
+export default class FooClass {} // $ class=(pack10).Foo instance=(pack10).Foo.prototype
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack10/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack10/index.js
new file mode 100644
index 000000000000..59bad5c3ecb5
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack10/index.js
@@ -0,0 +1,3 @@
+import { default as Foo } from "./foo";
+
+export { Foo }
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack10/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack10/package.json
new file mode 100644
index 000000000000..977ffaf282b8
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack10/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack10",
+ "main": "./index.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js b/javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js
new file mode 100644
index 000000000000..ed996a3350e6
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js
@@ -0,0 +1,6 @@
+class AmbiguousClass {
+ instanceMethod(foo) {} // $ method=(pack2).lib.LibClass.prototype.instanceMethod
+} // $ class=(pack2).lib.LibClass instance=(pack2).lib.LibClass.prototype
+
+export default AmbiguousClass;
+export { AmbiguousClass as LibClass }
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack2/main.js b/javascript/ql/test/library-tests/EndpointNaming/pack2/main.js
new file mode 100644
index 000000000000..e40015de73fd
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack2/main.js
@@ -0,0 +1,9 @@
+class AmbiguousClass {
+ instanceMethod() {} // $ method=(pack2).MainClass.prototype.instanceMethod
+} // $ class=(pack2).MainClass instance=(pack2).MainClass.prototype
+
+export default AmbiguousClass;
+export { AmbiguousClass as MainClass }
+
+import * as lib from "./lib";
+export { lib }
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack2/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack2/package.json
new file mode 100644
index 000000000000..b359913f6395
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack2/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack2",
+ "main": "./main.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js b/javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js
new file mode 100644
index 000000000000..9ef8c57437db
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js
@@ -0,0 +1 @@
+export default function(x,y,z) {} // $ method=(pack3).libFunction
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack3/main.js b/javascript/ql/test/library-tests/EndpointNaming/pack3/main.js
new file mode 100644
index 000000000000..3f49675b4926
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack3/main.js
@@ -0,0 +1,7 @@
+function ambiguousFunction(x, y, z) {} // $ method=(pack3).namedFunction
+
+export default ambiguousFunction;
+export { ambiguousFunction as namedFunction };
+
+import libFunction from "./lib";
+export { libFunction };
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack3/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack3/package.json
new file mode 100644
index 000000000000..0ca9a6083320
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack3/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack3",
+ "main": "./main.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack4/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack4/index.js
new file mode 100644
index 000000000000..15143e30bf61
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack4/index.js
@@ -0,0 +1 @@
+export default class C {} // $ class=(pack4) instance=(pack4).prototype
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack4/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack4/package.json
new file mode 100644
index 000000000000..fb63d98f8abd
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack4/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack4",
+ "main": "./index.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack5/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack5/package.json
new file mode 100644
index 000000000000..d6f924e72cad
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack5/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack5",
+ "main": "./dist/index.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js
new file mode 100644
index 000000000000..d96538407862
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js
@@ -0,0 +1 @@
+export default class C {} // $ class=(pack5) instance=(pack5).prototype
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack6/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack6/index.js
new file mode 100644
index 000000000000..e15b5319858b
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack6/index.js
@@ -0,0 +1,6 @@
+class C {
+ instanceMethod() {} // $ method=(pack6).instanceMethod
+ static staticMethod() {} // not accessible
+} // $ instance=(pack6)
+
+export default new C();
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack6/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack6/package.json
new file mode 100644
index 000000000000..c4daea408e33
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack6/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack6",
+ "main": "./index.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack7/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack7/index.js
new file mode 100644
index 000000000000..b56b32095d48
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack7/index.js
@@ -0,0 +1,6 @@
+export class D {} // $ class=(pack7).D instance=(pack7).D.prototype
+
+// In this case we are forced to include ".default" to avoid ambiguity with class D above.
+export default {
+ D: class {} // $ class=(pack7).default.D instance=(pack7).default.D.prototype
+};
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack7/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack7/package.json
new file mode 100644
index 000000000000..a43ae4f59034
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack7/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack7",
+ "main": "./index.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js b/javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js
new file mode 100644
index 000000000000..b141c3c81e71
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js
@@ -0,0 +1,5 @@
+class Foo {} // $ class=(pack8).Foo instance=(pack8).Foo.prototype
+
+module.exports = Foo;
+module.exports.default = Foo;
+module.exports.Foo = Foo;
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack8/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack8/index.js
new file mode 100644
index 000000000000..3cf0920f4bf0
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack8/index.js
@@ -0,0 +1,5 @@
+class Main {} // $ class=(pack8) instance=(pack8).prototype
+
+Main.Foo = require('./foo');
+
+module.exports = Main;
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack8/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack8/package.json
new file mode 100644
index 000000000000..09d57a3348e2
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack8/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack8",
+ "main": "./index.js"
+}
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js b/javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js
new file mode 100644
index 000000000000..55d14ad5b523
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js
@@ -0,0 +1 @@
+export class Foo {} // $ instance=(pack9/foo).Foo.prototype
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts b/javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts
new file mode 100644
index 000000000000..65c783aa4991
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts
@@ -0,0 +1,9 @@
+// Only the type is exposed. For the time being we do not consider type-only declarations or .d.ts files
+// when naming classes.
+export type { Foo } from "./foo";
+
+import * as foo from "./foo";
+
+export function expose() {
+ return new foo.Foo(); // expose an instance of Foo but not the class
+} // $ method=(pack9).expose
diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack9/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack9/package.json
new file mode 100644
index 000000000000..4e69ff9e365b
--- /dev/null
+++ b/javascript/ql/test/library-tests/EndpointNaming/pack9/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "pack9",
+ "main": "./index.js"
+}
From 19ba9fed9970ef015e151ac48430b5a549d4e780 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 30 Jan 2024 17:13:02 +0100
Subject: [PATCH 023/155] Handle externs
---
.../javascript/endpoints/EndpointNaming.qll | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
index 5d4a067874c7..95d3d191a709 100644
--- a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
+++ b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
@@ -176,6 +176,18 @@ predicate sinkHasAlias(API::Node sink, string package, string name) {
bindingset[node]
private API::Node getASinkNode(DataFlow::SourceNode node) { result.getAValueReachingSink() = node }
+/**
+ * Holds if `node` is a declaration in an externs file.
+ *
+ * This is to ensure that functions/classes in externs are not named after a re-export in a package.
+ */
+private predicate nameFromExterns(DataFlow::Node node, string package, string name, int badness) {
+ node.getTopLevel().isExterns() and
+ package = "global" and
+ node = AccessPath::getAnAssignmentTo(name) and
+ badness = -10
+}
+
bindingset[qualifiedName]
private int getBadnessOfClassName(string qualifiedName) {
if qualifiedName.matches("%.constructor")
@@ -201,6 +213,8 @@ private predicate classObjectHasNameCandidate(
sinkHasPrimaryName(getASinkNode(cls), package, name, baseBadness) and
badness = baseBadness + getBadnessOfClassName(name)
)
+ or
+ nameFromExterns(cls, package, name, badness)
}
private predicate classObjectHasPrimaryName(
@@ -314,6 +328,8 @@ private predicate functionHasNameCandidate(
name = join(baseName, memberName)
)
)
+ or
+ nameFromExterns(function, package, name, badness)
}
private predicate functionHasPrimaryName(
From 8bd79908a61cefeb9e129a85b1ba1d7d9f51ec53 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Tue, 30 Jan 2024 16:49:55 +0000
Subject: [PATCH 024/155] Implement local auth query
---
.../java/security/AndroidLocalAuthQuery.qll | 40 +++++++++++++++++++
.../AndroidInsecureLocalAuthentication.ql | 18 +++++++++
2 files changed, 58 insertions(+)
create mode 100644 java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll
create mode 100644 java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql
diff --git a/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll b/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll
new file mode 100644
index 000000000000..8c052fc58ee5
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll
@@ -0,0 +1,40 @@
+/** Definitions for the insecure local authentication query. */
+
+import java
+
+/** A base class that is used as a callback for biometric authentication. */
+private class AuthenticationCallbackClass extends Class {
+ AuthenticationCallbackClass() {
+ this.hasQualifiedName("android.hardware.fingerprint",
+ "FingerprintManager$AuthenticationCallback")
+ or
+ this.hasQualifiedName("android.hardware.biometrics", "BiometricPrompt$AuthenticationCallback")
+ }
+}
+
+/** An implementation of the `onAuthenticationSucceeded` method for an authentication callback. */
+class AuthenticationSuccessCallback extends Method {
+ AuthenticationSuccessCallback() {
+ this.getDeclaringType().getASupertype+() instanceof AuthenticationCallbackClass and
+ this.hasName("onAuthenticationSucceeded")
+ }
+
+ /** Gets the parameter containing the `authenticationResult` */
+ Parameter getResultParameter() { result = this.getParameter(0) }
+
+ /** Gets a use of the result parameter that's used in a `super` call to the base `AuthenticationCallback` class. */
+ private VarAccess getASuperResultUse() {
+ exists(SuperMethodCall sup |
+ sup.getEnclosingCallable() = this and
+ result = sup.getArgument(0) and
+ result = this.getResultParameter().getAnAccess() and
+ this.getDeclaringType().getASupertype() instanceof AuthenticationCallbackClass
+ )
+ }
+
+ /** Gets a use of the result parameter, other than one used in a `super` call. */
+ VarAccess getAResultUse() {
+ result = this.getResultParameter().getAnAccess() and
+ not result = this.getASuperResultUse()
+ }
+}
diff --git a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql
new file mode 100644
index 000000000000..22f4582fec33
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Insecure local authentication
+ * @description Local authentication that does not make use of a `CryptoObject` can be bypassed.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity ...TODO
+ * @precision high
+ * @id java/android/insecure-local-authentication
+ * @tags security
+ * external/cwe/cwe-287
+ */
+
+import java
+import semmle.code.java.security.AndroidLocalAuthQuery
+
+from AuthenticationSuccessCallback c
+where not exists(c.getAResultUse())
+select c, "This authentication callback does not use its result for a cryptographic operation."
From ad8038bade1c6bd78c44edb9b8af7bb86c5b9d42 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 31 Jan 2024 11:16:49 +0000
Subject: [PATCH 025/155] Update MaD Declarations after Triage
---
.../lib/change-notes/2024-01-31-new-models.md | 21 +++++++++++++++++++
java/ql/lib/ext/android.app.model.yml | 1 +
java/ql/lib/ext/java.io.model.yml | 3 ++-
java/ql/lib/ext/java.lang.model.yml | 5 +++++
java/ql/lib/ext/java.net.http.model.yml | 1 +
java/ql/lib/ext/java.net.model.yml | 3 +++
java/ql/lib/ext/java.nio.file.model.yml | 2 ++
java/ql/lib/ext/java.util.zip.model.yml | 1 +
java/ql/lib/ext/javax.servlet.model.yml | 6 +++++-
java/ql/lib/ext/javax.xml.parsers.model.yml | 6 ++++++
java/ql/lib/ext/kotlin.io.model.yml | 1 +
.../lib/ext/org.apache.commons.io.model.yml | 1 +
.../ql/lib/ext/org.apache.hadoop.fs.model.yml | 7 +++++++
.../ext/org.apache.hadoop.fs.s3a.model.yml | 6 ++++++
.../ext/org.apache.http.impl.client.model.yml | 1 +
.../ext/org.eclipse.jetty.client.model.yml | 1 +
java/ql/lib/ext/org.gradle.api.file.model.yml | 1 +
17 files changed, 65 insertions(+), 2 deletions(-)
create mode 100644 java/ql/lib/change-notes/2024-01-31-new-models.md
create mode 100644 java/ql/lib/ext/javax.xml.parsers.model.yml
create mode 100644 java/ql/lib/ext/org.apache.hadoop.fs.s3a.model.yml
diff --git a/java/ql/lib/change-notes/2024-01-31-new-models.md b/java/ql/lib/change-notes/2024-01-31-new-models.md
new file mode 100644
index 000000000000..195c1dd99543
--- /dev/null
+++ b/java/ql/lib/change-notes/2024-01-31-new-models.md
@@ -0,0 +1,21 @@
+---
+category: minorAnalysis
+---
+* Added models for the following packages:
+
+ * android.app
+ * java.io
+ * java.lang
+ * java.net
+ * java.net.http
+ * java.nio.file
+ * java.util.zip
+ * javax.servlet
+ * javax.xml.parsers
+ * kotlin.io
+ * org.apache.commons.io
+ * org.apache.hadoop.fs
+ * org.apache.hadoop.fs.s3a
+ * org.apache.http.impl.client
+ * org.eclipse.jetty.client
+ * org.gradle.api.file
diff --git a/java/ql/lib/ext/android.app.model.yml b/java/ql/lib/ext/android.app.model.yml
index d7a236871a7e..bf82aa4cec5a 100644
--- a/java/ql/lib/ext/android.app.model.yml
+++ b/java/ql/lib/ext/android.app.model.yml
@@ -6,6 +6,7 @@ extensions:
- ["android.app", "Activity", True, "bindService", "", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "bindServiceAsUser", "", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "setResult", "(int,Intent)", "", "Argument[1]", "pending-intents", "manual"]
+ - ["android.app", "Activity", True, "startActivity", "(Intent)", "", "Argument[0]", "intent-redirection", "ai-manual"]
- ["android.app", "Activity", True, "startActivityAsCaller", "", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "startActivityForResult", "(Intent,int)", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "startActivityForResult", "(Intent,int,Bundle)", "", "Argument[0]", "intent-redirection", "manual"]
diff --git a/java/ql/lib/ext/java.io.model.yml b/java/ql/lib/ext/java.io.model.yml
index 1bd9251c29d8..0fe99bba86c5 100644
--- a/java/ql/lib/ext/java.io.model.yml
+++ b/java/ql/lib/ext/java.io.model.yml
@@ -10,6 +10,7 @@ extensions:
- ["java.io", "File", True, "createNewFile", "()", "", "Argument[this]", "path-injection", "ai-manual"]
- ["java.io", "File", True, "createTempFile", "(String,String,File)", "", "Argument[2]", "path-injection", "ai-manual"]
- ["java.io", "File", True, "renameTo", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.io", "File", True, "renameTo", "(File)", "", "Argument[this]", "path-injection", "ai-manual"]
- ["java.io", "FileInputStream", True, "FileInputStream", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileInputStream", True, "FileInputStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.io", "FileOutputStream", False, "FileOutputStream", "", "", "Argument[0]", "path-injection", "manual"]
@@ -132,4 +133,4 @@ extensions:
pack: codeql/java-all
extensible: sourceModel
data:
- - ["java.io", "FileInputStream", True, "FileInputStream", "", "", "Argument[this]", "file", "manual"]
\ No newline at end of file
+ - ["java.io", "FileInputStream", True, "FileInputStream", "", "", "Argument[this]", "file", "manual"]
diff --git a/java/ql/lib/ext/java.lang.model.yml b/java/ql/lib/ext/java.lang.model.yml
index e5ee383c5229..8a257e510483 100644
--- a/java/ql/lib/ext/java.lang.model.yml
+++ b/java/ql/lib/ext/java.lang.model.yml
@@ -5,6 +5,10 @@ extensions:
data:
- ["java.lang", "Class", False, "getResource", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.lang", "Class", False, "getResourceAsStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.lang", "ClassLoader", False, "getSystemResources", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.lang", "ClassLoader", True, "getResource", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.lang", "ClassLoader", True, "getResourceAsStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.lang", "ClassLoader", True, "getResources", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.lang", "ClassLoader", True, "getSystemResource", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.lang", "ClassLoader", True, "getSystemResourceAsStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.lang", "Module", True, "getResourceAsStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
@@ -14,6 +18,7 @@ extensions:
- ["java.lang", "ProcessBuilder", False, "ProcessBuilder", "(List)", "", "Argument[0]", "command-injection", "ai-manual"]
- ["java.lang", "ProcessBuilder", False, "ProcessBuilder", "(String[])", "", "Argument[0]", "command-injection", "ai-manual"]
- ["java.lang", "ProcessBuilder", False, "redirectError", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["java.lang", "ProcessBuilder", False, "redirectOutput", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["java.lang", "Runtime", True, "exec", "(String)", "", "Argument[0]", "command-injection", "ai-manual"]
- ["java.lang", "Runtime", True, "exec", "(String[])", "", "Argument[0]", "command-injection", "ai-manual"]
- ["java.lang", "Runtime", True, "exec", "(String[],String[])", "", "Argument[0]", "command-injection", "ai-manual"]
diff --git a/java/ql/lib/ext/java.net.http.model.yml b/java/ql/lib/ext/java.net.http.model.yml
index 9fc18d2eaab3..b920eb3da086 100644
--- a/java/ql/lib/ext/java.net.http.model.yml
+++ b/java/ql/lib/ext/java.net.http.model.yml
@@ -8,5 +8,6 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
+ - ["java.net.http", "HttpClient", True, "send", "(HttpRequest,HttpResponse$BodyHandler)", "", "Argument[0]", "request-forgery", "ai-manual"]
- ["java.net.http", "HttpRequest", False, "newBuilder", "", "", "Argument[0]", "request-forgery", "manual"]
- ["java.net.http", "HttpRequest$Builder", False, "uri", "", "", "Argument[0]", "request-forgery", "manual"]
diff --git a/java/ql/lib/ext/java.net.model.yml b/java/ql/lib/ext/java.net.model.yml
index bdc40590fde0..c33cd0e83a56 100644
--- a/java/ql/lib/ext/java.net.model.yml
+++ b/java/ql/lib/ext/java.net.model.yml
@@ -10,6 +10,7 @@ extensions:
extensible: sinkModel
data:
- ["java.net", "DatagramPacket", False, "DatagramPacket", "(byte[],int,InetAddress,int)", "", "Argument[2]", "request-forgery", "ai-manual"]
+ - ["java.net", "DatagramPacket", False, "DatagramPacket", "(byte[],int,int,InetAddress,int)", "", "Argument[3]", "request-forgery", "ai-manual"]
- ["java.net", "DatagramSocket", True, "connect", "(SocketAddress)", "", "Argument[0]", "request-forgery", "ai-manual"]
- ["java.net", "PasswordAuthentication", False, "PasswordAuthentication", "(String,char[])", "", "Argument[1]", "credentials-password", "hq-generated"]
- ["java.net", "Socket", True, "Socket", "(String,int)", "", "Argument[0]", "request-forgery", "ai-manual"]
@@ -39,6 +40,8 @@ extensions:
- ["java.net", "InetSocketAddress", True, "InetSocketAddress", "(String,int)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
- ["java.net", "URI", False, "resolve", "(URI)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", "Argument[5]", "Argument[this].SyntheticField[java.net.URI.query]", "taint", "ai-manual"]
+ - ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", Argument[4], "ReturnValue", "taint", "ai-manual"]
+ - ["java.net", "URI", False, "URI", "(String,String,String)", "", Argument[1], "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String)", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["java.net", "URI", False, "create", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.net", "URI", False, "resolve", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
diff --git a/java/ql/lib/ext/java.nio.file.model.yml b/java/ql/lib/ext/java.nio.file.model.yml
index 3c77c876eee1..567859f47ae5 100644
--- a/java/ql/lib/ext/java.nio.file.model.yml
+++ b/java/ql/lib/ext/java.nio.file.model.yml
@@ -67,6 +67,7 @@ extensions:
- ["java.nio.file", "FileSystem", True, "getPath", "(String,String[])", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "FileSystem", True, "getPathMatcher", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "FileSystem", True, "getRootDirectories", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
+ - ["java.nio.file", "FileSystems", False, "getFileSystem", "(URI)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Path", True, "getFileName", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "getParent", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "normalize", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
@@ -81,6 +82,7 @@ extensions:
- ["java.nio.file", "Path", False, "toFile", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toUri", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
+ - ["java.nio.file", "Paths", False, "get", "(String,String[])", "", Argument[1], "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Paths", True, "get", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Paths", True, "get", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
# Not supported by current lambda flow
diff --git a/java/ql/lib/ext/java.util.zip.model.yml b/java/ql/lib/ext/java.util.zip.model.yml
index 577e6b357235..29c51d5def7b 100644
--- a/java/ql/lib/ext/java.util.zip.model.yml
+++ b/java/ql/lib/ext/java.util.zip.model.yml
@@ -5,6 +5,7 @@ extensions:
data:
- ["java.util.zip", "GZIPInputStream", False, "GZIPInputStream", "", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["java.util.zip", "ZipEntry", True, "ZipEntry", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
+ - ["java.util.zip", "ZipFile", True, "getInputStream", "(ZipEntry)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
- ["java.util.zip", "ZipInputStream", False, "ZipInputStream", "", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- addsTo:
pack: codeql/java-all
diff --git a/java/ql/lib/ext/javax.servlet.model.yml b/java/ql/lib/ext/javax.servlet.model.yml
index 7d7f432d2bd9..acd9bb6a6d43 100644
--- a/java/ql/lib/ext/javax.servlet.model.yml
+++ b/java/ql/lib/ext/javax.servlet.model.yml
@@ -9,9 +9,13 @@ extensions:
- ["javax.servlet", "ServletRequest", False, "getParameterNames", "()", "", "ReturnValue", "remote", "manual"]
- ["javax.servlet", "ServletRequest", False, "getParameterValues", "(String)", "", "ReturnValue", "remote", "manual"]
- ["javax.servlet", "ServletRequest", False, "getReader", "()", "", "ReturnValue", "remote", "manual"]
-
- addsTo:
pack: codeql/java-all
extensible: sinkModel
data:
- ["javax.servlet", "ServletContext", True, "getResourceAsStream", "(String)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - addsTo:
+ pack: codeql/java-all
+ extensible: summaryModel
+ data:
+ - ["javax.servlet", "ServletRequest", True, "getParameter", "(String)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
diff --git a/java/ql/lib/ext/javax.xml.parsers.model.yml b/java/ql/lib/ext/javax.xml.parsers.model.yml
new file mode 100644
index 000000000000..d39a28f5942c
--- /dev/null
+++ b/java/ql/lib/ext/javax.xml.parsers.model.yml
@@ -0,0 +1,6 @@
+extensions:
+ - addsTo:
+ pack: codeql/java-all
+ extensible: sinkModel
+ data:
+ - ["javax.xml.parsers", "DocumentBuilder", True, "parse", "(InputSource)", "", "Argument[0]", "xxe", "ai-manual"]
diff --git a/java/ql/lib/ext/kotlin.io.model.yml b/java/ql/lib/ext/kotlin.io.model.yml
index b748e04a292d..c65862f6eacc 100644
--- a/java/ql/lib/ext/kotlin.io.model.yml
+++ b/java/ql/lib/ext/kotlin.io.model.yml
@@ -3,6 +3,7 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
+ - ["kotlin.io", "FilesKt", False, "appendText$default", "(File,String,Charset,int,Object)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["kotlin.io", "FilesKt", False, "deleteRecursively", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["kotlin.io", "FilesKt", False, "inputStream", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["kotlin.io", "FilesKt", False, "readBytes", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
diff --git a/java/ql/lib/ext/org.apache.commons.io.model.yml b/java/ql/lib/ext/org.apache.commons.io.model.yml
index 20de13c5366a..fccecd72912b 100644
--- a/java/ql/lib/ext/org.apache.commons.io.model.yml
+++ b/java/ql/lib/ext/org.apache.commons.io.model.yml
@@ -21,6 +21,7 @@ extensions:
- ["org.apache.commons.io", "FileUtils", False, "forceMkdir", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["org.apache.commons.io", "FileUtils", False, "moveDirectory", "(File,File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["org.apache.commons.io", "FileUtils", False, "readFileToByteArray", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["org.apache.commons.io", "FileUtils", False, "readFileToString", "(File,Charset)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["org.apache.commons.io", "FileUtils", False, "writeLines", "(File,String,Collection,String)", "", "Argument[3]", "file-content-store", "ai-manual"]
- ["org.apache.commons.io", "FileUtils", False, "writeStringToFile", "(File,String,Charset,boolean)", "", "Argument[1]", "file-content-store", "ai-manual"]
- ["org.apache.commons.io", "FileUtils", True, "copyInputStreamToFile", "(InputStream,File)", "", "Argument[0]", "file-content-store", "ai-manual"]
diff --git a/java/ql/lib/ext/org.apache.hadoop.fs.model.yml b/java/ql/lib/ext/org.apache.hadoop.fs.model.yml
index ba819b737766..79dab1de37b7 100644
--- a/java/ql/lib/ext/org.apache.hadoop.fs.model.yml
+++ b/java/ql/lib/ext/org.apache.hadoop.fs.model.yml
@@ -13,3 +13,10 @@ extensions:
- ["org.apache.hadoop.fs", "Path", True, "Path", "(String,String)", "", "Argument[1]", "Argument[this]", "taint", "ai-manual"]
- ["org.apache.hadoop.fs", "Path", True, "Path", "(String)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
- ["org.apache.hadoop.fs", "Path", True, "Path", "(URI)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
+ - addsTo:
+ pack: codeql/java-all
+ extensible: sinkModel
+ data:
+ - ["org.apache.hadoop.fs", "FileSystem", True, "makeQualified", "(Path)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["org.apache.hadoop.fs", "FileSystem", True, "rename", "(Path,Path)", "", "Argument[0]", "path-injection", "ai-manual"]
+ - ["org.apache.hadoop.fs", "FileSystem", True, "rename", "(Path,Path)", "", "Argument[1]", "path-injection", "ai-manual"]
diff --git a/java/ql/lib/ext/org.apache.hadoop.fs.s3a.model.yml b/java/ql/lib/ext/org.apache.hadoop.fs.s3a.model.yml
new file mode 100644
index 000000000000..4d5d9484335e
--- /dev/null
+++ b/java/ql/lib/ext/org.apache.hadoop.fs.s3a.model.yml
@@ -0,0 +1,6 @@
+extensions:
+ - addsTo:
+ pack: codeql/java-all
+ extensible: sinkModel
+ data:
+ - ["org.apache.hadoop.fs.s3a", "WriteOperationHelper", True, "createPutObjectRequest", "(String,File)", "", "Argument[1]", "path-injection", "ai-manual"]
diff --git a/java/ql/lib/ext/org.apache.http.impl.client.model.yml b/java/ql/lib/ext/org.apache.http.impl.client.model.yml
index be517e5344f5..6f407ac36825 100644
--- a/java/ql/lib/ext/org.apache.http.impl.client.model.yml
+++ b/java/ql/lib/ext/org.apache.http.impl.client.model.yml
@@ -3,4 +3,5 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
+ - ["org.apache.http.impl.client", "CloseableHttpClient", True, "execute", "(HttpUriRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
- ["org.apache.http.impl.client", "RequestWrapper", True, "setURI", "(URI)", "", "Argument[0]", "request-forgery", "hq-manual"]
diff --git a/java/ql/lib/ext/org.eclipse.jetty.client.model.yml b/java/ql/lib/ext/org.eclipse.jetty.client.model.yml
index 28c3430e8184..bd3b4f58e722 100644
--- a/java/ql/lib/ext/org.eclipse.jetty.client.model.yml
+++ b/java/ql/lib/ext/org.eclipse.jetty.client.model.yml
@@ -3,4 +3,5 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
+ - ["org.eclipse.jetty.client", "HttpClient", True, "GET", "(String)", "", "Argument[0]", "request-forgery", "ai-manual"]
- ["org.eclipse.jetty.client", "HttpClient", True, "newRequest", "(String)", "", "Argument[0]", "request-forgery", "ai-manual"]
diff --git a/java/ql/lib/ext/org.gradle.api.file.model.yml b/java/ql/lib/ext/org.gradle.api.file.model.yml
index 4f492cdbcbc1..7123cd0d0ca6 100644
--- a/java/ql/lib/ext/org.gradle.api.file.model.yml
+++ b/java/ql/lib/ext/org.gradle.api.file.model.yml
@@ -4,4 +4,5 @@ extensions:
extensible: summaryModel
data:
- ["org.gradle.api.file", "Directory", True, "getAsFile", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
+ - ["org.gradle.api.file", "DirectoryProperty", True, "dir", "(String)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
- ["org.gradle.api.file", "DirectoryProperty", True, "file", "(String)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
From ab6cea14c878c2c4b0b2fae5e6ffe58310e4873e Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 31 Jan 2024 11:49:25 +0000
Subject: [PATCH 026/155] Fix missing quotes.
---
java/ql/lib/ext/java.net.model.yml | 4 ++--
java/ql/lib/ext/java.nio.file.model.yml | 4 ++--
java/ql/lib/ext/java.util.zip.model.yml | 2 +-
java/ql/lib/ext/org.gradle.api.file.model.yml | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/java/ql/lib/ext/java.net.model.yml b/java/ql/lib/ext/java.net.model.yml
index 92f1ad7eb057..a6dd7fc5ce84 100644
--- a/java/ql/lib/ext/java.net.model.yml
+++ b/java/ql/lib/ext/java.net.model.yml
@@ -44,8 +44,8 @@ extensions:
- ["java.net", "InetSocketAddress", True, "InetSocketAddress", "(String,int)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
- ["java.net", "URI", False, "resolve", "(URI)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", "Argument[5]", "Argument[this].SyntheticField[java.net.URI.query]", "taint", "ai-manual"]
- - ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", Argument[4], "ReturnValue", "taint", "ai-manual"]
- - ["java.net", "URI", False, "URI", "(String,String,String)", "", Argument[1], "ReturnValue", "taint", "ai-manual"]
+ - ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", "Argument[4]", "ReturnValue", "taint", "ai-manual"]
+ - ["java.net", "URI", False, "URI", "(String,String,String)", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String)", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["java.net", "URI", False, "create", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.net", "URI", False, "resolve", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
diff --git a/java/ql/lib/ext/java.nio.file.model.yml b/java/ql/lib/ext/java.nio.file.model.yml
index 567859f47ae5..ea32fa75fe38 100644
--- a/java/ql/lib/ext/java.nio.file.model.yml
+++ b/java/ql/lib/ext/java.nio.file.model.yml
@@ -67,7 +67,7 @@ extensions:
- ["java.nio.file", "FileSystem", True, "getPath", "(String,String[])", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "FileSystem", True, "getPathMatcher", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "FileSystem", True, "getRootDirectories", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- - ["java.nio.file", "FileSystems", False, "getFileSystem", "(URI)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
+ - ["java.nio.file", "FileSystems", False, "getFileSystem", "(URI)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Path", True, "getFileName", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "getParent", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "normalize", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
@@ -82,7 +82,7 @@ extensions:
- ["java.nio.file", "Path", False, "toFile", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toUri", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- - ["java.nio.file", "Paths", False, "get", "(String,String[])", "", Argument[1], "ReturnValue", "taint", "ai-manual"]
+ - ["java.nio.file", "Paths", False, "get", "(String,String[])", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Paths", True, "get", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Paths", True, "get", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
# Not supported by current lambda flow
diff --git a/java/ql/lib/ext/java.util.zip.model.yml b/java/ql/lib/ext/java.util.zip.model.yml
index 29c51d5def7b..611fc7804ef1 100644
--- a/java/ql/lib/ext/java.util.zip.model.yml
+++ b/java/ql/lib/ext/java.util.zip.model.yml
@@ -5,7 +5,7 @@ extensions:
data:
- ["java.util.zip", "GZIPInputStream", False, "GZIPInputStream", "", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["java.util.zip", "ZipEntry", True, "ZipEntry", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- - ["java.util.zip", "ZipFile", True, "getInputStream", "(ZipEntry)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
+ - ["java.util.zip", "ZipFile", True, "getInputStream", "(ZipEntry)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["java.util.zip", "ZipInputStream", False, "ZipInputStream", "", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- addsTo:
pack: codeql/java-all
diff --git a/java/ql/lib/ext/org.gradle.api.file.model.yml b/java/ql/lib/ext/org.gradle.api.file.model.yml
index 7123cd0d0ca6..c3d202aab39c 100644
--- a/java/ql/lib/ext/org.gradle.api.file.model.yml
+++ b/java/ql/lib/ext/org.gradle.api.file.model.yml
@@ -4,5 +4,5 @@ extensions:
extensible: summaryModel
data:
- ["org.gradle.api.file", "Directory", True, "getAsFile", "()", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
- - ["org.gradle.api.file", "DirectoryProperty", True, "dir", "(String)", "", Argument[0], "ReturnValue", "taint", "ai-manual"]
+ - ["org.gradle.api.file", "DirectoryProperty", True, "dir", "(String)", "", "Argument[0]", "ReturnValue", "taint", "ai-manual"]
- ["org.gradle.api.file", "DirectoryProperty", True, "file", "(String)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
From aa5cccdddd4eaf0989f98af12bd6e1f5d9496e91 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 31 Jan 2024 20:39:25 +0100
Subject: [PATCH 027/155] JS: Make sinkHasPrimaryName public
---
.../ql/lib/semmle/javascript/endpoints/EndpointNaming.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
index 95d3d191a709..791545fe3ce8 100644
--- a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
+++ b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
@@ -150,7 +150,7 @@ private predicate sinkHasPrimaryName(API::Node sink, string package, string name
/**
* Holds if `(package, name)` is the primary name to associate with `node`.
*/
-private predicate sinkHasPrimaryName(API::Node sink, string package, string name) {
+predicate sinkHasPrimaryName(API::Node sink, string package, string name) {
sinkHasPrimaryName(sink, package, name, _)
}
From 817d04c0874ac0fe06ac7fdd8ca7d29aa27c1b5d Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Tue, 30 Jan 2024 14:27:13 +0100
Subject: [PATCH 028/155] C#: Add more delegate flow tests
---
.../dataflow/delegates/DelegateFlow.cs | 26 +++++++++++++++++--
.../dataflow/delegates/DelegateFlow.expected | 8 +++---
2 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
index 15d628aea9a0..1f9c48445dd4 100644
--- a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
+++ b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
@@ -128,9 +128,31 @@ public unsafe void M18()
void M19(Action a, bool b)
{
if (b)
- a = () => {};
+ a = () => { };
a();
}
- void M20(bool b) => M19(() => {}, b);
+ void M20(bool b) => M19(() => { }, b);
+
+ Action Field;
+ Action Prop2 { get; set; }
+
+ DelegateFlow(Action a, Action b)
+ {
+ Field = a;
+ Prop2 = b;
+ }
+
+ void M20()
+ {
+ new DelegateFlow(
+ _ => { },
+ _ => { }
+ );
+
+ this.Field(0);
+ this.Prop2(0);
+ Field(0);
+ Prop2(0);
+ }
}
diff --git a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
index 610ff1f06d95..3197db2cfe7a 100644
--- a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
+++ b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
@@ -22,8 +22,8 @@ delegateCall
| DelegateFlow.cs:89:35:89:37 | delegate call | DelegateFlow.cs:93:13:93:21 | (...) => ... |
| DelegateFlow.cs:114:9:114:16 | function pointer call | DelegateFlow.cs:7:17:7:18 | M2 |
| DelegateFlow.cs:125:9:125:25 | function pointer call | DelegateFlow.cs:7:17:7:18 | M2 |
-| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:131:17:131:24 | (...) => ... |
-| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:29:135:36 | (...) => ... |
+| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:131:17:131:25 | (...) => ... |
+| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:29:135:37 | (...) => ... |
viableLambda
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:16:9:16:20 | call to method M2 | DelegateFlow.cs:16:12:16:19 | (...) => ... |
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:17:9:17:14 | call to method M2 | DelegateFlow.cs:5:10:5:11 | M1 |
@@ -49,7 +49,7 @@ viableLambda
| DelegateFlow.cs:89:35:89:37 | delegate call | DelegateFlow.cs:93:9:93:22 | call to local function M14 | DelegateFlow.cs:93:13:93:21 | (...) => ... |
| DelegateFlow.cs:114:9:114:16 | function pointer call | DelegateFlow.cs:119:9:119:28 | call to method M16 | DelegateFlow.cs:7:17:7:18 | M2 |
| DelegateFlow.cs:125:9:125:25 | function pointer call | file://:0:0:0:0 | (none) | DelegateFlow.cs:7:17:7:18 | M2 |
-| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:25:135:40 | call to method M19 | DelegateFlow.cs:135:29:135:36 | (...) => ... |
-| DelegateFlow.cs:132:9:132:11 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:131:17:131:24 | (...) => ... |
+| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:25:135:41 | call to method M19 | DelegateFlow.cs:135:29:135:37 | (...) => ... |
+| DelegateFlow.cs:132:9:132:11 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:131:17:131:25 | (...) => ... |
| file://:0:0:0:0 | [summary] call to [summary param] position 0 in Lazy in Lazy | DelegateFlow.cs:105:9:105:24 | object creation of type Lazy | DelegateFlow.cs:104:23:104:30 | (...) => ... |
| file://:0:0:0:0 | [summary] call to [summary param] position 0 in Lazy in Lazy | DelegateFlow.cs:107:9:107:24 | object creation of type Lazy | DelegateFlow.cs:106:13:106:20 | (...) => ... |
From bfe4a4bf0b4017e094505130bda0183741e4e0d0 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 31 Jan 2024 11:29:54 +0100
Subject: [PATCH 029/155] C#: Additional tracking of lambdas through fields and
properties
---
.../DataFlowConsistency.ql | 11 +--
.../dataflow/internal/DataFlowDispatch.qll | 24 ++++--
.../dataflow/internal/DataFlowPrivate.qll | 85 +++++++++++++++++--
.../dataflow/delegates/DelegateFlow.expected | 8 ++
4 files changed, 105 insertions(+), 23 deletions(-)
diff --git a/csharp/ql/consistency-queries/DataFlowConsistency.ql b/csharp/ql/consistency-queries/DataFlowConsistency.ql
index e1eb8b15a56a..0f9dead6b77b 100644
--- a/csharp/ql/consistency-queries/DataFlowConsistency.ql
+++ b/csharp/ql/consistency-queries/DataFlowConsistency.ql
@@ -7,15 +7,6 @@ private import codeql.dataflow.internal.DataFlowImplConsistency
private module Input implements InputSig {
private import CsharpDataFlow
- predicate uniqueEnclosingCallableExclude(Node n) {
- // TODO: Remove once static initializers are folded into the
- // static constructors
- exists(ControlFlow::Node cfn |
- cfn.getAstNode() = any(FieldOrProperty f | f.isStatic()).getAChild+() and
- cfn = n.getControlFlowNode()
- )
- }
-
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) {
// TODO: Remove once static initializers are folded into the
// static constructors
@@ -30,6 +21,8 @@ private module Input implements InputSig {
n instanceof ParameterNode
or
missingLocationExclude(n)
+ or
+ n instanceof FlowInsensitiveFieldNode
}
predicate missingLocationExclude(Node n) {
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
index e97823157119..55ad89e03d2f 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
@@ -98,7 +98,8 @@ private module Cached {
cached
newtype TDataFlowCallable =
TDotNetCallable(DotNet::Callable c) { c.isUnboundDeclaration() } or
- TSummarizedCallable(DataFlowSummarizedCallable sc)
+ TSummarizedCallable(DataFlowSummarizedCallable sc) or
+ TFieldOrProperty(FieldOrProperty f)
cached
newtype TDataFlowCall =
@@ -247,22 +248,33 @@ class ImplicitCapturedReturnKind extends ReturnKind, TImplicitCapturedReturnKind
/** A callable used for data flow. */
class DataFlowCallable extends TDataFlowCallable {
- /** Get the underlying source code callable, if any. */
+ /** Gets the underlying source code callable, if any. */
DotNet::Callable asCallable() { this = TDotNetCallable(result) }
- /** Get the underlying summarized callable, if any. */
+ /** Gets the underlying summarized callable, if any. */
FlowSummary::SummarizedCallable asSummarizedCallable() { this = TSummarizedCallable(result) }
- /** Get the underlying callable. */
+ /** Gets the underlying field or property, if any. */
+ FieldOrProperty asFieldOrProperty() { this = TFieldOrProperty(result) }
+
+ /** Gets the underlying callable. */
DotNet::Callable getUnderlyingCallable() {
result = this.asCallable() or result = this.asSummarizedCallable()
}
/** Gets a textual representation of this dataflow callable. */
- string toString() { result = this.getUnderlyingCallable().toString() }
+ string toString() {
+ result = this.getUnderlyingCallable().toString()
+ or
+ result = this.asFieldOrProperty().toString()
+ }
/** Get the location of this dataflow callable. */
- Location getLocation() { result = this.getUnderlyingCallable().getLocation() }
+ Location getLocation() {
+ result = this.getUnderlyingCallable().getLocation()
+ or
+ result = this.asFieldOrProperty().getLocation()
+ }
}
/** A call relevant for data flow. */
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
index 6a1c3c589d32..282465a0a9d0 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
@@ -69,6 +69,17 @@ abstract class NodeImpl extends Node {
abstract string toStringImpl();
}
+// TODO: Remove once static initializers are folded into the
+// static constructors
+private DataFlowCallable getEnclosingStaticFieldOrProperty(Expr e) {
+ result.asFieldOrProperty() =
+ any(FieldOrProperty f |
+ f.isStatic() and
+ e = f.getAChild+() and
+ not exists(e.getEnclosingCallable())
+ )
+}
+
private class ExprNodeImpl extends ExprNode, NodeImpl {
override DataFlowCallable getEnclosingCallableImpl() {
result.asCallable() =
@@ -76,6 +87,8 @@ private class ExprNodeImpl extends ExprNode, NodeImpl {
this.getExpr().(CIL::Expr).getEnclosingCallable().(DotNet::Callable),
this.getControlFlowNodeImpl().getEnclosingCallable()
]
+ or
+ result = getEnclosingStaticFieldOrProperty(this.asExpr())
}
override DotNet::Type getTypeImpl() {
@@ -909,7 +922,8 @@ private module Cached {
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TParamsArgumentNode(ControlFlow::Node callCfn) {
callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode()
- }
+ } or
+ TFlowInsensitiveFieldNode(FieldOrProperty f) { f.isFieldLike() }
/**
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
@@ -1019,6 +1033,8 @@ predicate nodeIsHidden(Node n) {
n instanceof ParamsArgumentNode
or
n.asExpr() = any(WithExpr we).getInitializer()
+ or
+ n instanceof FlowInsensitiveFieldNode
}
/** A CIL SSA definition, viewed as a node in a data flow graph. */
@@ -1344,6 +1360,8 @@ private module ArgumentNodes {
override DataFlowCallable getEnclosingCallableImpl() {
result.asCallable() = cfn.getEnclosingCallable()
+ or
+ result = getEnclosingStaticFieldOrProperty(cfn.getAstNode())
}
override Type getTypeImpl() { result = cfn.getAstNode().(Expr).getType() }
@@ -1383,6 +1401,8 @@ private module ArgumentNodes {
override DataFlowCallable getEnclosingCallableImpl() {
result.asCallable() = callCfn.getEnclosingCallable()
+ or
+ result = getEnclosingStaticFieldOrProperty(callCfn.getAstNode())
}
override Type getTypeImpl() { result = this.getParameter().getType() }
@@ -1782,6 +1802,30 @@ private class FieldOrPropertyRead extends FieldOrPropertyAccess, AssignableRead
}
}
+/**
+ * A data flow node used for control-flow insensitive flow through fields
+ * and properties.
+ *
+ * In global data flow this is used to model flow through static fields and
+ * properties, while for lambda flow we additionally use it to track assignments
+ * in constructors to uses within the same class.
+ */
+class FlowInsensitiveFieldNode extends NodeImpl, TFlowInsensitiveFieldNode {
+ private FieldOrProperty f;
+
+ FlowInsensitiveFieldNode() { this = TFlowInsensitiveFieldNode(f) }
+
+ override DataFlowCallable getEnclosingCallableImpl() { result.asFieldOrProperty() = f }
+
+ override Type getTypeImpl() { result = f.getType() }
+
+ override ControlFlow::Node getControlFlowNodeImpl() { none() }
+
+ override Location getLocationImpl() { result = f.getLocation() }
+
+ override string toStringImpl() { result = "[flow-insensitive] " + f }
+}
+
/**
* Holds if `pred` can flow to `succ`, by jumping from one callable to
* another. Additional steps specified by the configuration are *not*
@@ -1790,13 +1834,16 @@ private class FieldOrPropertyRead extends FieldOrPropertyAccess, AssignableRead
predicate jumpStep(Node pred, Node succ) {
pred.(NonLocalJumpNode).getAJumpSuccessor(true) = succ
or
- exists(FieldOrProperty fl, FieldOrPropertyRead flr |
- fl.isStatic() and
- fl.isFieldLike() and
- fl.getAnAssignedValue() = pred.asExpr() and
- fl.getAnAccess() = flr and
- flr = succ.asExpr() and
- flr.hasNonlocalValue()
+ exists(FieldOrProperty f | f.isStatic() |
+ f.getAnAssignedValue() = pred.asExpr() and
+ succ = TFlowInsensitiveFieldNode(f)
+ or
+ exists(FieldOrPropertyRead fr |
+ pred = TFlowInsensitiveFieldNode(f) and
+ f.getAnAccess() = fr and
+ fr = succ.asExpr() and
+ fr.hasNonlocalValue()
+ )
)
or
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
@@ -2248,6 +2295,8 @@ module PostUpdateNodes {
override DataFlowCallable getEnclosingCallableImpl() {
result.asCallable() = cfn.getEnclosingCallable()
+ or
+ result = getEnclosingStaticFieldOrProperty(oc)
}
override DotNet::Type getTypeImpl() { result = oc.getType() }
@@ -2279,6 +2328,8 @@ module PostUpdateNodes {
override DataFlowCallable getEnclosingCallableImpl() {
result.asCallable() = cfn.getEnclosingCallable()
+ or
+ result = getEnclosingStaticFieldOrProperty(cfn.getAstNode())
}
override Type getTypeImpl() { result = cfn.getAstNode().(Expr).getType() }
@@ -2427,6 +2478,24 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
nodeTo.asExpr().(EventRead).getTarget() = aee.getTarget() and
preservesValue = false
)
+ or
+ preservesValue = true and
+ exists(FieldOrProperty f, FieldOrPropertyAccess fa |
+ fa = f.getAnAccess() and
+ fa.targetIsLocalInstance()
+ |
+ exists(AssignableDefinition def |
+ def.getTargetAccess() = fa and
+ nodeFrom.asExpr() = def.getSource() and
+ nodeTo = TFlowInsensitiveFieldNode(f) and
+ nodeFrom.getEnclosingCallable() instanceof Constructor
+ )
+ or
+ nodeFrom = TFlowInsensitiveFieldNode(f) and
+ f.getAnAccess() = fa and
+ fa = nodeTo.asExpr() and
+ fa.(FieldOrPropertyRead).hasNonlocalValue()
+ )
}
/**
diff --git a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
index 3197db2cfe7a..c16036e751c4 100644
--- a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
+++ b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
@@ -24,6 +24,10 @@ delegateCall
| DelegateFlow.cs:125:9:125:25 | function pointer call | DelegateFlow.cs:7:17:7:18 | M2 |
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:131:17:131:25 | (...) => ... |
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:29:135:37 | (...) => ... |
+| DelegateFlow.cs:153:9:153:21 | delegate call | DelegateFlow.cs:149:13:149:20 | (...) => ... |
+| DelegateFlow.cs:154:9:154:21 | delegate call | DelegateFlow.cs:150:13:150:20 | (...) => ... |
+| DelegateFlow.cs:155:9:155:16 | delegate call | DelegateFlow.cs:149:13:149:20 | (...) => ... |
+| DelegateFlow.cs:156:9:156:16 | delegate call | DelegateFlow.cs:150:13:150:20 | (...) => ... |
viableLambda
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:16:9:16:20 | call to method M2 | DelegateFlow.cs:16:12:16:19 | (...) => ... |
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:17:9:17:14 | call to method M2 | DelegateFlow.cs:5:10:5:11 | M1 |
@@ -51,5 +55,9 @@ viableLambda
| DelegateFlow.cs:125:9:125:25 | function pointer call | file://:0:0:0:0 | (none) | DelegateFlow.cs:7:17:7:18 | M2 |
| DelegateFlow.cs:132:9:132:11 | delegate call | DelegateFlow.cs:135:25:135:41 | call to method M19 | DelegateFlow.cs:135:29:135:37 | (...) => ... |
| DelegateFlow.cs:132:9:132:11 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:131:17:131:25 | (...) => ... |
+| DelegateFlow.cs:153:9:153:21 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:149:13:149:20 | (...) => ... |
+| DelegateFlow.cs:154:9:154:21 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:150:13:150:20 | (...) => ... |
+| DelegateFlow.cs:155:9:155:16 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:149:13:149:20 | (...) => ... |
+| DelegateFlow.cs:156:9:156:16 | delegate call | file://:0:0:0:0 | (none) | DelegateFlow.cs:150:13:150:20 | (...) => ... |
| file://:0:0:0:0 | [summary] call to [summary param] position 0 in Lazy in Lazy | DelegateFlow.cs:105:9:105:24 | object creation of type Lazy | DelegateFlow.cs:104:23:104:30 | (...) => ... |
| file://:0:0:0:0 | [summary] call to [summary param] position 0 in Lazy in Lazy | DelegateFlow.cs:107:9:107:24 | object creation of type Lazy | DelegateFlow.cs:106:13:106:20 | (...) => ... |
From 9098428c2ac5cf83eb0e8e4a7ba13698bbd187aa Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Thu, 1 Feb 2024 14:28:14 +0000
Subject: [PATCH 030/155] Add security severity
---
.../Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql
index 22f4582fec33..57f256bb40d3 100644
--- a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql
+++ b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.ql
@@ -3,7 +3,7 @@
* @description Local authentication that does not make use of a `CryptoObject` can be bypassed.
* @kind problem
* @problem.severity warning
- * @security-severity ...TODO
+ * @security-severity 9.3
* @precision high
* @id java/android/insecure-local-authentication
* @tags security
From 5d1edd45c5191086dd3c6190ae28a3518dd0ea90 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Thu, 1 Feb 2024 16:56:20 +0000
Subject: [PATCH 031/155] Add unit tests
---
.../CWE-287/InsecureLocalAuth.expected | 2 +
.../security/CWE-287/InsecureLocalAuth.ql | 19 ++++
.../query-tests/security/CWE-287/Test.java | 94 +++++++++++++++++++
.../test/query-tests/security/CWE-287/options | 1 +
4 files changed, 116 insertions(+)
create mode 100644 java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.expected
create mode 100644 java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.ql
create mode 100644 java/ql/test/query-tests/security/CWE-287/Test.java
create mode 100644 java/ql/test/query-tests/security/CWE-287/options
diff --git a/java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.expected b/java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.expected
new file mode 100644
index 000000000000..8ec8033d086e
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.expected
@@ -0,0 +1,2 @@
+testFailures
+failures
diff --git a/java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.ql b/java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.ql
new file mode 100644
index 000000000000..36becaff7553
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-287/InsecureLocalAuth.ql
@@ -0,0 +1,19 @@
+import java
+import TestUtilities.InlineExpectationsTest
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.security.AndroidLocalAuthQuery
+
+module InsecureAuthTest implements TestSig {
+ string getARelevantTag() { result = "insecure-auth" }
+
+ predicate hasActualResult(Location location, string element, string tag, string value) {
+ tag = "insecure-auth" and
+ exists(AuthenticationSuccessCallback cb | not exists(cb.getAResultUse()) |
+ cb.getLocation() = location and
+ element = cb.toString() and
+ value = ""
+ )
+ }
+}
+
+import MakeTest
diff --git a/java/ql/test/query-tests/security/CWE-287/Test.java b/java/ql/test/query-tests/security/CWE-287/Test.java
new file mode 100644
index 000000000000..e4317efd615d
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-287/Test.java
@@ -0,0 +1,94 @@
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.fingerprint.FingerprintManager;
+
+class TestA {
+ public static void useKey(BiometricPrompt.CryptoObject key) {}
+
+
+ // GOOD: result is used
+ class Test1 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ TestA.useKey(result.getCryptoObject());
+ }
+ }
+
+ // BAD: result is not used
+ class Test2 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { // $insecure-auth
+
+ }
+ }
+
+ // BAD: result is only used in a super call
+ class Test3 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { // $insecure-auth
+ super.onAuthenticationSucceeded(result);
+ }
+ }
+
+ // GOOD: result is used
+ class Test4 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ TestA.useKey(result.getCryptoObject());
+ }
+ }
+
+ // GOOD: result is used in a super call to a class other than the base class
+ class Test5 extends Test1 {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ }
+ }
+}
+
+class TestB {
+ public static void useKey(FingerprintManager.CryptoObject key) {}
+
+
+ // GOOD: result is used
+ class Test1 extends FingerprintManager.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ TestB.useKey(result.getCryptoObject());
+ }
+ }
+
+ // BAD: result is not used
+ class Test2 extends FingerprintManager.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { // $insecure-auth
+
+ }
+ }
+
+ // BAD: result is only used in a super call
+ class Test3 extends FingerprintManager.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { // $insecure-auth
+ super.onAuthenticationSucceeded(result);
+ }
+ }
+
+ // GOOD: result is used
+ class Test4 extends FingerprintManager.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ TestB.useKey(result.getCryptoObject());
+ }
+ }
+
+ // GOOD: result is used in a super call to a class other than the base class
+ class Test5 extends Test1 {
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/query-tests/security/CWE-287/options b/java/ql/test/query-tests/security/CWE-287/options
new file mode 100644
index 000000000000..dacd3cb21df0
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-287/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/google-android-9.0.0
From 88c2ccbecf3371fa1ff04bb9b747e514f40cc21b Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Thu, 1 Feb 2024 16:59:50 +0000
Subject: [PATCH 032/155] Generate stubs
---
.../hardware/biometrics/BiometricPrompt.java | 69 +++++++++++++++++++
.../fingerprint/FingerprintManager.java | 55 +++++++++++++++
.../android/os/Bundle.java | 4 +-
.../android/os/Parcel.java | 22 +++---
.../security/identity/IdentityCredential.java | 32 +++++++++
.../identity/PersonalizationData.java | 9 +++
.../android/security/identity/ResultData.java | 24 +++++++
.../android/util/ArrayMap.java | 10 +--
8 files changed, 207 insertions(+), 18 deletions(-)
create mode 100644 java/ql/test/stubs/google-android-9.0.0/android/hardware/biometrics/BiometricPrompt.java
create mode 100644 java/ql/test/stubs/google-android-9.0.0/android/hardware/fingerprint/FingerprintManager.java
create mode 100644 java/ql/test/stubs/google-android-9.0.0/android/security/identity/IdentityCredential.java
create mode 100644 java/ql/test/stubs/google-android-9.0.0/android/security/identity/PersonalizationData.java
create mode 100644 java/ql/test/stubs/google-android-9.0.0/android/security/identity/ResultData.java
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/hardware/biometrics/BiometricPrompt.java b/java/ql/test/stubs/google-android-9.0.0/android/hardware/biometrics/BiometricPrompt.java
new file mode 100644
index 000000000000..7bc467401bdc
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/android/hardware/biometrics/BiometricPrompt.java
@@ -0,0 +1,69 @@
+// Generated automatically from android.hardware.biometrics.BiometricPrompt for testing purposes
+
+package android.hardware.biometrics;
+
+import android.os.CancellationSignal;
+import android.security.identity.IdentityCredential;
+import java.security.Signature;
+import java.util.concurrent.Executor;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+public class BiometricPrompt
+{
+ protected BiometricPrompt() {}
+ abstract static public class AuthenticationCallback
+ {
+ public AuthenticationCallback(){}
+ public void onAuthenticationError(int p0, CharSequence p1){}
+ public void onAuthenticationFailed(){}
+ public void onAuthenticationHelp(int p0, CharSequence p1){}
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult p0){}
+ }
+ public CharSequence getDescription(){ return null; }
+ public CharSequence getNegativeButtonText(){ return null; }
+ public CharSequence getSubtitle(){ return null; }
+ public CharSequence getTitle(){ return null; }
+ public boolean isConfirmationRequired(){ return false; }
+ public int getAllowedAuthenticators(){ return 0; }
+ public static int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 0;
+ public static int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 0;
+ public static int BIOMETRIC_ACQUIRED_GOOD = 0;
+ public static int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 0;
+ public static int BIOMETRIC_ACQUIRED_INSUFFICIENT = 0;
+ public static int BIOMETRIC_ACQUIRED_PARTIAL = 0;
+ public static int BIOMETRIC_ACQUIRED_TOO_FAST = 0;
+ public static int BIOMETRIC_ACQUIRED_TOO_SLOW = 0;
+ public static int BIOMETRIC_ERROR_CANCELED = 0;
+ public static int BIOMETRIC_ERROR_HW_NOT_PRESENT = 0;
+ public static int BIOMETRIC_ERROR_HW_UNAVAILABLE = 0;
+ public static int BIOMETRIC_ERROR_LOCKOUT = 0;
+ public static int BIOMETRIC_ERROR_LOCKOUT_PERMANENT = 0;
+ public static int BIOMETRIC_ERROR_NO_BIOMETRICS = 0;
+ public static int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 0;
+ public static int BIOMETRIC_ERROR_NO_SPACE = 0;
+ public static int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 0;
+ public static int BIOMETRIC_ERROR_TIMEOUT = 0;
+ public static int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 0;
+ public static int BIOMETRIC_ERROR_USER_CANCELED = 0;
+ public static int BIOMETRIC_ERROR_VENDOR = 0;
+ public void authenticate(BiometricPrompt.CryptoObject p0, CancellationSignal p1, Executor p2, BiometricPrompt.AuthenticationCallback p3){}
+ public void authenticate(CancellationSignal p0, Executor p1, BiometricPrompt.AuthenticationCallback p2){}
+ static public class AuthenticationResult
+ {
+ public BiometricPrompt.CryptoObject getCryptoObject(){ return null; }
+ public int getAuthenticationType(){ return 0; }
+ }
+ static public class CryptoObject
+ {
+ protected CryptoObject() {}
+ public Cipher getCipher(){ return null; }
+ public CryptoObject(Cipher p0){}
+ public CryptoObject(IdentityCredential p0){}
+ public CryptoObject(Mac p0){}
+ public CryptoObject(Signature p0){}
+ public IdentityCredential getIdentityCredential(){ return null; }
+ public Mac getMac(){ return null; }
+ public Signature getSignature(){ return null; }
+ }
+}
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/hardware/fingerprint/FingerprintManager.java b/java/ql/test/stubs/google-android-9.0.0/android/hardware/fingerprint/FingerprintManager.java
new file mode 100644
index 000000000000..235941fb9f49
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/android/hardware/fingerprint/FingerprintManager.java
@@ -0,0 +1,55 @@
+// Generated automatically from android.hardware.fingerprint.FingerprintManager for testing purposes
+
+package android.hardware.fingerprint;
+
+import android.os.CancellationSignal;
+import android.os.Handler;
+import java.security.Signature;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+public class FingerprintManager
+{
+ abstract static public class AuthenticationCallback
+ {
+ public AuthenticationCallback(){}
+ public void onAuthenticationError(int p0, CharSequence p1){}
+ public void onAuthenticationFailed(){}
+ public void onAuthenticationHelp(int p0, CharSequence p1){}
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult p0){}
+ }
+ public boolean hasEnrolledFingerprints(){ return false; }
+ public boolean isHardwareDetected(){ return false; }
+ public static int FINGERPRINT_ACQUIRED_GOOD = 0;
+ public static int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 0;
+ public static int FINGERPRINT_ACQUIRED_INSUFFICIENT = 0;
+ public static int FINGERPRINT_ACQUIRED_PARTIAL = 0;
+ public static int FINGERPRINT_ACQUIRED_TOO_FAST = 0;
+ public static int FINGERPRINT_ACQUIRED_TOO_SLOW = 0;
+ public static int FINGERPRINT_ERROR_CANCELED = 0;
+ public static int FINGERPRINT_ERROR_HW_NOT_PRESENT = 0;
+ public static int FINGERPRINT_ERROR_HW_UNAVAILABLE = 0;
+ public static int FINGERPRINT_ERROR_LOCKOUT = 0;
+ public static int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 0;
+ public static int FINGERPRINT_ERROR_NO_FINGERPRINTS = 0;
+ public static int FINGERPRINT_ERROR_NO_SPACE = 0;
+ public static int FINGERPRINT_ERROR_TIMEOUT = 0;
+ public static int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 0;
+ public static int FINGERPRINT_ERROR_USER_CANCELED = 0;
+ public static int FINGERPRINT_ERROR_VENDOR = 0;
+ public void authenticate(FingerprintManager.CryptoObject p0, CancellationSignal p1, int p2, FingerprintManager.AuthenticationCallback p3, Handler p4){}
+ static public class AuthenticationResult
+ {
+ public FingerprintManager.CryptoObject getCryptoObject(){ return null; }
+ }
+ static public class CryptoObject
+ {
+ protected CryptoObject() {}
+ public Cipher getCipher(){ return null; }
+ public CryptoObject(Cipher p0){}
+ public CryptoObject(Mac p0){}
+ public CryptoObject(Signature p0){}
+ public Mac getMac(){ return null; }
+ public Signature getSignature(){ return null; }
+ }
+}
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/os/Bundle.java b/java/ql/test/stubs/google-android-9.0.0/android/os/Bundle.java
index 4beb1cf5dee3..d1154c915ce0 100644
--- a/java/ql/test/stubs/google-android-9.0.0/android/os/Bundle.java
+++ b/java/ql/test/stubs/google-android-9.0.0/android/os/Bundle.java
@@ -15,9 +15,9 @@
public class Bundle extends BaseBundle implements Cloneable, Parcelable
{
- public ArrayList getParcelableArrayList(String p0){ return null; }
- public SparseArray getSparseParcelableArray(String p0){ return null; }
public T getParcelable(String p0){ return null; }
+ public android.util.SparseArray getSparseParcelableArray(String p0){ return null; }
+ public java.util.ArrayList getParcelableArrayList(String p0){ return null; }
public ArrayList getCharSequenceArrayList(String p0){ return null; }
public ArrayList getIntegerArrayList(String p0){ return null; }
public ArrayList getStringArrayList(String p0){ return null; }
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/os/Parcel.java b/java/ql/test/stubs/google-android-9.0.0/android/os/Parcel.java
index ef6dcdeb0852..9bf19e322693 100644
--- a/java/ql/test/stubs/google-android-9.0.0/android/os/Parcel.java
+++ b/java/ql/test/stubs/google-android-9.0.0/android/os/Parcel.java
@@ -24,24 +24,24 @@ public class Parcel
{
protected Parcel() {}
protected void finalize(){}
- public ArrayMap createTypedArrayMap(Parcelable.Creator p0){ return null; }
- public List readParcelableList(List p0, ClassLoader p1){ return null; }
- public SparseArray createTypedSparseArray(Parcelable.Creator p0){ return null; }
public T readParcelable(ClassLoader p0){ return null; }
+ public android.util.ArrayMap createTypedArrayMap(Parcelable.Creator p0){ return null; }
+ public android.util.SparseArray createTypedSparseArray(Parcelable.Creator p0){ return null; }
+ public java.util.List readParcelableList(java.util.List p0, ClassLoader p1){ return null; }
public void writeParcelableArray(T[] p0, int p1){}
- public void writeParcelableList(List p0, int p1){}
+ public void writeParcelableList(java.util.List p0, int p1){}
public void writeTypedArray(T[] p0, int p1){}
- public void writeTypedArrayMap(ArrayMap p0, int p1){}
- public void writeTypedList(List p0){}
+ public void writeTypedArrayMap(android.util.ArrayMap p0, int p1){}
+ public void writeTypedList(java.util.List p0){}
public void writeTypedObject(T p0, int p1){}
- public void writeTypedSparseArray(SparseArray p0, int p1){}
- public ArrayList createTypedArrayList(Parcelable.Creator p0){ return null; }
- public SparseArray readSparseArray(ClassLoader p0){ return null; }
+ public void writeTypedSparseArray(android.util.SparseArray p0, int p1){}
public T readTypedObject(Parcelable.Creator p0){ return null; }
public T[] createTypedArray(Parcelable.Creator p0){ return null; }
+ public android.util.SparseArray readSparseArray(ClassLoader p0){ return null; }
+ public java.util.ArrayList createTypedArrayList(Parcelable.Creator p0){ return null; }
public void readTypedArray(T[] p0, Parcelable.Creator p1){}
- public void readTypedList(List p0, Parcelable.Creator p1){}
- public void writeSparseArray(SparseArray p0){}
+ public void readTypedList(java.util.List p0, Parcelable.Creator p1){}
+ public void writeSparseArray(android.util.SparseArray p0){}
public ArrayList readArrayList(ClassLoader p0){ return null; }
public ArrayList createBinderArrayList(){ return null; }
public ArrayList createStringArrayList(){ return null; }
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/security/identity/IdentityCredential.java b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/IdentityCredential.java
new file mode 100644
index 000000000000..12dee91453c9
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/IdentityCredential.java
@@ -0,0 +1,32 @@
+// Generated automatically from android.security.identity.IdentityCredential for testing purposes
+
+package android.security.identity;
+
+import android.security.identity.PersonalizationData;
+import android.security.identity.ResultData;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Map;
+
+abstract public class IdentityCredential
+{
+ public abstract Collection getAuthKeysNeedingCertification();
+ public abstract Collection getCredentialKeyCertificateChain();
+ public abstract KeyPair createEphemeralKeyPair();
+ public abstract ResultData getEntries(byte[] p0, Map> p1, byte[] p2, byte[] p3);
+ public abstract byte[] decryptMessageFromReader(byte[] p0);
+ public abstract byte[] encryptMessageToReader(byte[] p0);
+ public abstract int[] getAuthenticationDataUsageCount();
+ public abstract void setAllowUsingExhaustedKeys(boolean p0);
+ public abstract void setAvailableAuthenticationKeys(int p0, int p1);
+ public abstract void setReaderEphemeralPublicKey(PublicKey p0);
+ public abstract void storeStaticAuthenticationData(X509Certificate p0, byte[] p1);
+ public byte[] delete(byte[] p0){ return null; }
+ public byte[] proveOwnership(byte[] p0){ return null; }
+ public byte[] update(PersonalizationData p0){ return null; }
+ public void setAllowUsingExpiredKeys(boolean p0){}
+ public void storeStaticAuthenticationData(X509Certificate p0, Instant p1, byte[] p2){}
+}
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/security/identity/PersonalizationData.java b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/PersonalizationData.java
new file mode 100644
index 000000000000..eecfc828c56f
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/PersonalizationData.java
@@ -0,0 +1,9 @@
+// Generated automatically from android.security.identity.PersonalizationData for testing purposes
+
+package android.security.identity;
+
+
+public class PersonalizationData
+{
+ protected PersonalizationData() {}
+}
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/security/identity/ResultData.java b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/ResultData.java
new file mode 100644
index 000000000000..e8ed2f130129
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/ResultData.java
@@ -0,0 +1,24 @@
+// Generated automatically from android.security.identity.ResultData for testing purposes
+
+package android.security.identity;
+
+import java.util.Collection;
+
+abstract public class ResultData
+{
+ public abstract Collection getEntryNames(String p0);
+ public abstract Collection getNamespaces();
+ public abstract Collection getRetrievedEntryNames(String p0);
+ public abstract byte[] getAuthenticatedData();
+ public abstract byte[] getEntry(String p0, String p1);
+ public abstract byte[] getMessageAuthenticationCode();
+ public abstract byte[] getStaticAuthenticationData();
+ public abstract int getStatus(String p0, String p1);
+ public static int STATUS_NOT_IN_REQUEST_MESSAGE = 0;
+ public static int STATUS_NOT_REQUESTED = 0;
+ public static int STATUS_NO_ACCESS_CONTROL_PROFILES = 0;
+ public static int STATUS_NO_SUCH_ENTRY = 0;
+ public static int STATUS_OK = 0;
+ public static int STATUS_READER_AUTHENTICATION_FAILED = 0;
+ public static int STATUS_USER_AUTHENTICATION_FAILED = 0;
+}
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/util/ArrayMap.java b/java/ql/test/stubs/google-android-9.0.0/android/util/ArrayMap.java
index 55c7adebea7f..c0b6016c764f 100644
--- a/java/ql/test/stubs/google-android-9.0.0/android/util/ArrayMap.java
+++ b/java/ql/test/stubs/google-android-9.0.0/android/util/ArrayMap.java
@@ -6,15 +6,12 @@
import java.util.Map;
import java.util.Set;
-public class ArrayMap implements Map
+public class ArrayMap implements java.util.Map
{
public ArrayMap(){}
public ArrayMap(ArrayMap p0){}
public ArrayMap(int p0){}
- public Collection values(){ return null; }
public K keyAt(int p0){ return null; }
- public Set keySet(){ return null; }
- public Set> entrySet(){ return null; }
public String toString(){ return null; }
public V get(Object p0){ return null; }
public V put(K p0, V p1){ return null; }
@@ -33,8 +30,11 @@ public ArrayMap(int p0){}
public int indexOfKey(Object p0){ return 0; }
public int indexOfValue(Object p0){ return 0; }
public int size(){ return 0; }
+ public java.util.Collection values(){ return null; }
+ public java.util.Set keySet(){ return null; }
+ public java.util.Set> entrySet(){ return null; }
public void clear(){}
public void ensureCapacity(int p0){}
public void putAll(ArrayMap extends K, ? extends V> p0){}
- public void putAll(Map extends K, ? extends V> p0){}
+ public void putAll(java.util.Map extends K, ? extends V> p0){}
}
From 8a2485a22f7000ca7842547a7b769a3c4782ee34 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 1 Feb 2024 20:54:27 +0100
Subject: [PATCH 033/155] JS: Address some comments
---
.../ql/lib/semmle/javascript/endpoints/EndpointNaming.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
index 791545fe3ce8..c01683535cf2 100644
--- a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
+++ b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
@@ -8,7 +8,7 @@
* However, there are cases where classes and functions can be exposed to client
* code without being accessible as a qualified name. For example;
* ```js
- * // 'Foo' is internal, but clients can reach its methods via `getFoo().m()`
+ * // 'Foo' is internal, but clients can call its methods, e.g. `getFoo().m()`
* class Foo {
* m() {}
* }
@@ -16,7 +16,7 @@
* return new Foo();
* }
*
- * // Clients can reach m() via getObj().m()
+ * // Clients can call m() via getObj().m()
* export function getObj() {
* return {
* m() {}
From 2a00375bb7fdc980b82cb6a03a7273f0998949a3 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Fri, 2 Feb 2024 14:34:43 +0000
Subject: [PATCH 034/155] Add documentation
---
.../AndroidInsecureLocalAuthentication.qhelp | 42 ++++++++++++++++
...AndroidInsecureLocalAuthenticationBad.java | 11 +++++
...ndroidInsecureLocalAuthenticationGood.java | 48 +++++++++++++++++++
3 files changed, 101 insertions(+)
create mode 100644 java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.qhelp
create mode 100644 java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationBad.java
create mode 100644 java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java
diff --git a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.qhelp b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.qhelp
new file mode 100644
index 000000000000..15e6783d2dca
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthentication.qhelp
@@ -0,0 +1,42 @@
+
+
+
+
+
+Biometric local authentication such as fingerprint recognistion can be used to protect sensitive data or actions within an application.
+However, if this authentication does not make use of a KeyStore-backed key, it is able to be bypassed by a privileged malicious application or an attacker with physical access.
+
+
+
+
+
+Generate a secure key in the Android KeyStore and ensure that the onAuthenticaionSuccess callback for a biometric prompt uses it
+in a way that is required for the sensitive parts of the application to function, such as by using it to decrypt sensitive data or credentials.
+
+
+
+
+
In the following (bad) case, no CryptoObject is required for the biometric prompt to grant access, so it can be bypassed.
+
+
In he following (good) case, a secret key is generated in the Android KeyStore that is required for the application to grant access.
+
+
+
diff --git a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationBad.java b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationBad.java
new file mode 100644
index 000000000000..464153ccbee8
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationBad.java
@@ -0,0 +1,11 @@
+biometricPrompt.authenticate(
+ cancellationSignal,
+ executor,
+ new BiometricPrompt.AuthenticationCallback {
+ @Override
+ // BAD: This authentication callback does not make use of a `CryptoObject` from the `result`.
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ grantAccess()
+ }
+ }
+)
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java
new file mode 100644
index 000000000000..0f41b31a2920
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java
@@ -0,0 +1,48 @@
+private void generateSecretKey() {
+ KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
+ "MySecretKey",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ .setUserAuthenticationRequired(true)
+ .setInvalidatedByBiometricEnrollment(true)
+ .build();
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+ keyGenerator.init(keyGenParameterSpec);
+ keyGenerator.generateKey();
+}
+
+
+private SecretKey getSecretKey() {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ return ((SecretKey)keyStore.getKey("MySecretKey", null));
+}
+
+private Cipher getCipher() {
+ return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+}
+
+public prompt() {
+ Cipher cipher = getCipher();
+ SecretKey secretKey = getSecretKey();
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
+
+ biometricPrompt.authenticate(
+ new BiometricPrompt.CryptoObject(cipher);
+ cancellationSignal,
+ executor,
+ new BiometricPrompt.AuthenticationCallback {
+ @Override
+ // GOOD: This authentication callback uses the result to decrypt some data.
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ Cipher cipher = result.getCryptoObject().getCipher();
+ byte[] decryptedData = cipher.doFinal(encryptedData);
+ grantAccessWithData(decryptedData);
+ }
+ }
+ );
+}
\ No newline at end of file
From 514a92d5bd1e24e4b7367d64430762ffd1ffbe7f Mon Sep 17 00:00:00 2001
From: Nick Rolfe
Date: Wed, 1 Nov 2023 22:33:21 +0000
Subject: [PATCH 035/155] Tree-sitter extractors: use fresh IDs for locations
Since locations for any given source file are never referenced in any
TRAP files besides the one for that particular source file, it's not
necessary to use global IDs. Using fresh IDs will reduce the size of the
ID pool (both on disk and in memory) and the speed of multi-threaded
TRAP import.
The one exception is the empty location, which still uses a global ID.
---
.../src/extractor/mod.rs | 128 ++++++++++++------
shared/tree-sitter-extractor/src/trap.rs | 22 +++
2 files changed, 105 insertions(+), 45 deletions(-)
diff --git a/shared/tree-sitter-extractor/src/extractor/mod.rs b/shared/tree-sitter-extractor/src/extractor/mod.rs
index 913e637d92b6..0d493ebd9e1a 100644
--- a/shared/tree-sitter-extractor/src/extractor/mod.rs
+++ b/shared/tree-sitter-extractor/src/extractor/mod.rs
@@ -43,7 +43,16 @@ fn populate_empty_file(writer: &mut trap::Writer) -> trap::Label {
pub fn populate_empty_location(writer: &mut trap::Writer) {
let file_label = populate_empty_file(writer);
- location(writer, file_label, 0, 0, 0, 0);
+ global_location(
+ writer,
+ file_label,
+ trap::Location {
+ start_line: 0,
+ start_column: 0,
+ end_line: 0,
+ end_column: 0,
+ },
+ );
}
pub fn populate_parent_folders(
@@ -85,17 +94,19 @@ pub fn populate_parent_folders(
}
}
-fn location(
+/** Get the label for the given location, defining it a global ID if it doesn't exist yet. */
+fn global_location(
writer: &mut trap::Writer,
file_label: trap::Label,
- start_line: usize,
- start_column: usize,
- end_line: usize,
- end_column: usize,
+ location: trap::Location,
) -> trap::Label {
let (loc_label, fresh) = writer.global_id(&format!(
"loc,{{{}}},{},{},{},{}",
- file_label, start_line, start_column, end_line, end_column
+ file_label,
+ location.start_line,
+ location.start_column,
+ location.end_line,
+ location.end_column
));
if fresh {
writer.add_tuple(
@@ -103,10 +114,34 @@ fn location(
vec![
trap::Arg::Label(loc_label),
trap::Arg::Label(file_label),
- trap::Arg::Int(start_line),
- trap::Arg::Int(start_column),
- trap::Arg::Int(end_line),
- trap::Arg::Int(end_column),
+ trap::Arg::Int(location.start_line),
+ trap::Arg::Int(location.start_column),
+ trap::Arg::Int(location.end_line),
+ trap::Arg::Int(location.end_column),
+ ],
+ );
+ }
+ loc_label
+}
+
+/** Get the label for the given location, creating it as a fresh ID if we haven't seen the location
+ * yet for this file. */
+fn location_label(
+ writer: &mut trap::Writer,
+ file_label: trap::Label,
+ location: trap::Location,
+) -> trap::Label {
+ let (loc_label, fresh) = writer.location_label(location);
+ if fresh {
+ writer.add_tuple(
+ "locations_default",
+ vec![
+ trap::Arg::Label(loc_label),
+ trap::Arg::Label(file_label),
+ trap::Arg::Int(location.start_line),
+ trap::Arg::Int(location.start_column),
+ trap::Arg::Int(location.end_line),
+ trap::Arg::Int(location.end_column),
],
);
}
@@ -245,26 +280,25 @@ impl<'a> Visitor<'a> {
node: Node,
status_page: bool,
) {
- let (start_line, start_column, end_line, end_column) = location_for(self, node);
- let loc = location(
- self.trap_writer,
- self.file_label,
- start_line,
- start_column,
- end_line,
- end_column,
- );
+ let loc = location_for(self, node);
+ let loc_label = location_label(self.trap_writer, self.file_label, loc);
let mut mesg = self.diagnostics_writer.new_entry(
"parse-error",
"Could not process some files due to syntax errors",
);
mesg.severity(diagnostics::Severity::Warning)
- .location(self.path, start_line, start_column, end_line, end_column)
+ .location(
+ self.path,
+ loc.start_line,
+ loc.start_column,
+ loc.end_line,
+ loc.end_column,
+ )
.message(message, args);
if status_page {
mesg.status_page();
}
- self.record_parse_error(loc, &mesg);
+ self.record_parse_error(loc_label, &mesg);
}
fn enter_node(&mut self, node: Node) -> bool {
@@ -298,15 +332,8 @@ impl<'a> Visitor<'a> {
return;
}
let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack");
- let (start_line, start_column, end_line, end_column) = location_for(self, node);
- let loc = location(
- self.trap_writer,
- self.file_label,
- start_line,
- start_column,
- end_line,
- end_column,
- );
+ let loc = location_for(self, node);
+ let loc_label = location_label(self.trap_writer, self.file_label, loc);
let table = self
.schema
.get(&TypeName {
@@ -333,7 +360,7 @@ impl<'a> Visitor<'a> {
trap::Arg::Label(id),
trap::Arg::Label(parent_id),
trap::Arg::Int(parent_index),
- trap::Arg::Label(loc),
+ trap::Arg::Label(loc_label),
],
);
self.trap_writer.add_tuple(
@@ -356,7 +383,7 @@ impl<'a> Visitor<'a> {
trap::Arg::Label(id),
trap::Arg::Label(parent_id),
trap::Arg::Int(parent_index),
- trap::Arg::Label(loc),
+ trap::Arg::Label(loc_label),
],
);
let mut all_args = vec![trap::Arg::Label(id)];
@@ -366,14 +393,20 @@ impl<'a> Visitor<'a> {
}
_ => {
self.record_parse_error(
- loc,
+ loc_label,
self.diagnostics_writer
.new_entry(
"parse-error",
"Could not process some files due to syntax errors",
)
.severity(diagnostics::Severity::Warning)
- .location(self.path, start_line, start_column, end_line, end_column)
+ .location(
+ self.path,
+ loc.start_line,
+ loc.start_column,
+ loc.end_line,
+ loc.end_column,
+ )
.message(
"Unknown table type: {}",
&[diagnostics::MessageArg::Code(node.kind())],
@@ -555,7 +588,7 @@ fn sliced_source_arg(source: &[u8], n: Node) -> trap::Arg {
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
// The first is the location and label definition, and the second is the
// 'Located' entry.
-fn location_for(visitor: &mut Visitor, n: Node) -> (usize, usize, usize, usize) {
+fn location_for(visitor: &mut Visitor, n: Node) -> trap::Location {
// Tree-sitter row, column values are 0-based while CodeQL starts
// counting at 1. In addition Tree-sitter's row and column for the
// end position are exclusive while CodeQL's end positions are inclusive.
@@ -565,16 +598,16 @@ fn location_for(visitor: &mut Visitor, n: Node) -> (usize, usize, usize, usize)
// the end column is 0 (start of a line). In such cases the end position must be
// set to the end of the previous line.
let start_line = n.start_position().row + 1;
- let start_col = n.start_position().column + 1;
+ let start_column = n.start_position().column + 1;
let mut end_line = n.end_position().row + 1;
- let mut end_col = n.end_position().column;
- if start_line > end_line || start_line == end_line && start_col > end_col {
+ let mut end_column = n.end_position().column;
+ if start_line > end_line || start_line == end_line && start_column > end_column {
// the range is empty, clip it to sensible values
end_line = start_line;
- end_col = start_col - 1;
- } else if end_col == 0 {
+ end_column = start_column - 1;
+ } else if end_column == 0 {
let source = visitor.source;
- // end_col = 0 means that we are at the start of a line
+ // end_column = 0 means that we are at the start of a line
// unfortunately 0 is invalid as column number, therefore
// we should update the end location to be the end of the
// previous line
@@ -591,10 +624,10 @@ fn location_for(visitor: &mut Visitor, n: Node) -> (usize, usize, usize, usize)
);
}
end_line -= 1;
- end_col = 1;
+ end_column = 1;
while index > 0 && source[index - 1] != b'\n' {
index -= 1;
- end_col += 1;
+ end_column += 1;
}
} else {
visitor.diagnostics_writer.write(
@@ -612,7 +645,12 @@ fn location_for(visitor: &mut Visitor, n: Node) -> (usize, usize, usize, usize)
);
}
}
- (start_line, start_col, end_line, end_col)
+ trap::Location {
+ start_line,
+ start_column,
+ end_line,
+ end_column,
+ }
}
fn traverse(tree: &Tree, visitor: &mut Visitor) {
diff --git a/shared/tree-sitter-extractor/src/trap.rs b/shared/tree-sitter-extractor/src/trap.rs
index 135e336338f9..64c06539ecbf 100644
--- a/shared/tree-sitter-extractor/src/trap.rs
+++ b/shared/tree-sitter-extractor/src/trap.rs
@@ -5,6 +5,14 @@ use std::path::Path;
use flate2::write::GzEncoder;
+#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
+pub struct Location {
+ pub start_line: usize,
+ pub start_column: usize,
+ pub end_line: usize,
+ pub end_column: usize,
+}
+
pub struct Writer {
/// The accumulated trap entries
trap_output: Vec,
@@ -12,6 +20,8 @@ pub struct Writer {
counter: u32,
/// cache of global keys
global_keys: std::collections::HashMap,
+ /// Labels for locations, which don't use global keys
+ location_labels: std::collections::HashMap,
}
impl Writer {
@@ -20,6 +30,7 @@ impl Writer {
counter: 0,
trap_output: Vec::new(),
global_keys: std::collections::HashMap::new(),
+ location_labels: std::collections::HashMap::new(),
}
}
@@ -50,6 +61,17 @@ impl Writer {
(label, true)
}
+ /// Gets the label for the given location. The first call for a given location will define it as
+ /// a fresh (star) ID.
+ pub fn location_label(&mut self, loc: Location) -> (Label, bool) {
+ if let Some(label) = self.location_labels.get(&loc) {
+ return (*label, false);
+ }
+ let label = self.fresh_id();
+ self.location_labels.insert(loc, label);
+ (label, true)
+ }
+
pub fn add_tuple(&mut self, table_name: &str, args: Vec) {
self.trap_output
.push(Entry::GenericTuple(table_name.to_owned(), args))
From 71852868acda81fb9e321924a0d7d7a2880b4304 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Fri, 2 Feb 2024 17:19:20 +0000
Subject: [PATCH 036/155] Add case for androidx.biometric api
---
.../java/security/AndroidLocalAuthQuery.qll | 2 +
.../query-tests/security/CWE-287/Test2.java | 47 +++++++++++
.../identity/PresentationSession.java | 9 +++
.../androidx/biometric/BiometricPrompt.java | 79 +++++++++++++++++++
4 files changed, 137 insertions(+)
create mode 100644 java/ql/test/query-tests/security/CWE-287/Test2.java
create mode 100644 java/ql/test/stubs/google-android-9.0.0/android/security/identity/PresentationSession.java
create mode 100644 java/ql/test/stubs/google-android-9.0.0/androidx/biometric/BiometricPrompt.java
diff --git a/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll b/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll
index 8c052fc58ee5..46b391559f19 100644
--- a/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/AndroidLocalAuthQuery.qll
@@ -9,6 +9,8 @@ private class AuthenticationCallbackClass extends Class {
"FingerprintManager$AuthenticationCallback")
or
this.hasQualifiedName("android.hardware.biometrics", "BiometricPrompt$AuthenticationCallback")
+ or
+ this.hasQualifiedName("androidx.biometric", "BiometricPrompt$AuthenticationCallback")
}
}
diff --git a/java/ql/test/query-tests/security/CWE-287/Test2.java b/java/ql/test/query-tests/security/CWE-287/Test2.java
new file mode 100644
index 000000000000..10308a2f2d38
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-287/Test2.java
@@ -0,0 +1,47 @@
+import androidx.biometric.BiometricPrompt;
+
+class TestC {
+ public static void useKey(BiometricPrompt.CryptoObject key) {}
+
+
+ // GOOD: result is used
+ class Test1 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ TestC.useKey(result.getCryptoObject());
+ }
+ }
+
+ // BAD: result is not used
+ class Test2 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { // $insecure-auth
+
+ }
+ }
+
+ // BAD: result is only used in a super call
+ class Test3 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { // $insecure-auth
+ super.onAuthenticationSucceeded(result);
+ }
+ }
+
+ // GOOD: result is used
+ class Test4 extends BiometricPrompt.AuthenticationCallback {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ TestC.useKey(result.getCryptoObject());
+ }
+ }
+
+ // GOOD: result is used in a super call to a class other than the base class
+ class Test5 extends Test1 {
+ @Override
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/google-android-9.0.0/android/security/identity/PresentationSession.java b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/PresentationSession.java
new file mode 100644
index 000000000000..9227f8fe5d37
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/android/security/identity/PresentationSession.java
@@ -0,0 +1,9 @@
+// Generated automatically from android.security.identity.PresentationSession for testing purposes
+
+package android.security.identity;
+
+
+public class PresentationSession
+{
+ protected PresentationSession() {}
+}
diff --git a/java/ql/test/stubs/google-android-9.0.0/androidx/biometric/BiometricPrompt.java b/java/ql/test/stubs/google-android-9.0.0/androidx/biometric/BiometricPrompt.java
new file mode 100644
index 000000000000..16bf2e661ee6
--- /dev/null
+++ b/java/ql/test/stubs/google-android-9.0.0/androidx/biometric/BiometricPrompt.java
@@ -0,0 +1,79 @@
+// Generated automatically from androidx.biometric.BiometricPrompt for testing purposes
+
+package androidx.biometric;
+
+import android.security.identity.IdentityCredential;
+import android.security.identity.PresentationSession;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import java.security.Signature;
+import java.util.concurrent.Executor;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
+public class BiometricPrompt
+{
+ protected BiometricPrompt() {}
+ abstract static public class AuthenticationCallback
+ {
+ public AuthenticationCallback(){}
+ public void onAuthenticationError(int p0, CharSequence p1){}
+ public void onAuthenticationFailed(){}
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult p0){}
+ }
+ public BiometricPrompt(Fragment p0, BiometricPrompt.AuthenticationCallback p1){}
+ public BiometricPrompt(Fragment p0, Executor p1, BiometricPrompt.AuthenticationCallback p2){}
+ public BiometricPrompt(FragmentActivity p0, BiometricPrompt.AuthenticationCallback p1){}
+ public BiometricPrompt(FragmentActivity p0, Executor p1, BiometricPrompt.AuthenticationCallback p2){}
+ public static int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 0;
+ public static int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 0;
+ public static int AUTHENTICATION_RESULT_TYPE_UNKNOWN = 0;
+ public static int ERROR_CANCELED = 0;
+ public static int ERROR_HW_NOT_PRESENT = 0;
+ public static int ERROR_HW_UNAVAILABLE = 0;
+ public static int ERROR_LOCKOUT = 0;
+ public static int ERROR_LOCKOUT_PERMANENT = 0;
+ public static int ERROR_NEGATIVE_BUTTON = 0;
+ public static int ERROR_NO_BIOMETRICS = 0;
+ public static int ERROR_NO_DEVICE_CREDENTIAL = 0;
+ public static int ERROR_NO_SPACE = 0;
+ public static int ERROR_SECURITY_UPDATE_REQUIRED = 0;
+ public static int ERROR_TIMEOUT = 0;
+ public static int ERROR_UNABLE_TO_PROCESS = 0;
+ public static int ERROR_USER_CANCELED = 0;
+ public static int ERROR_VENDOR = 0;
+ public void authenticate(BiometricPrompt.PromptInfo p0){}
+ public void authenticate(BiometricPrompt.PromptInfo p0, BiometricPrompt.CryptoObject p1){}
+ public void cancelAuthentication(){}
+ static public class AuthenticationResult
+ {
+ protected AuthenticationResult() {}
+ public BiometricPrompt.CryptoObject getCryptoObject(){ return null; }
+ public int getAuthenticationType(){ return 0; }
+ }
+ static public class CryptoObject
+ {
+ protected CryptoObject() {}
+ public Cipher getCipher(){ return null; }
+ public CryptoObject(Cipher p0){}
+ public CryptoObject(IdentityCredential p0){}
+ public CryptoObject(Mac p0){}
+ public CryptoObject(PresentationSession p0){}
+ public CryptoObject(Signature p0){}
+ public IdentityCredential getIdentityCredential(){ return null; }
+ public Mac getMac(){ return null; }
+ public PresentationSession getPresentationSession(){ return null; }
+ public Signature getSignature(){ return null; }
+ }
+ static public class PromptInfo
+ {
+ protected PromptInfo() {}
+ public CharSequence getDescription(){ return null; }
+ public CharSequence getNegativeButtonText(){ return null; }
+ public CharSequence getSubtitle(){ return null; }
+ public CharSequence getTitle(){ return null; }
+ public boolean isConfirmationRequired(){ return false; }
+ public boolean isDeviceCredentialAllowed(){ return false; }
+ public int getAllowedAuthenticators(){ return 0; }
+ }
+}
From 5022adba562db5543b5d8de8fea512e7d2020029 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Fri, 2 Feb 2024 17:26:00 +0000
Subject: [PATCH 037/155] Fixes to qhelp example
---
.../CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java
index 0f41b31a2920..2ffcbbb6e261 100644
--- a/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java
+++ b/java/ql/src/Security/CWE/CWE-287/AndroidInsecureLocalAuthenticationGood.java
@@ -26,16 +26,16 @@ private Cipher getCipher() {
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
}
-public prompt() {
+public prompt(byte[] encryptedData) {
Cipher cipher = getCipher();
SecretKey secretKey = getSecretKey();
cipher.init(Cipher.DECRYPT_MODE, secretKey);
biometricPrompt.authenticate(
- new BiometricPrompt.CryptoObject(cipher);
+ new BiometricPrompt.CryptoObject(cipher),
cancellationSignal,
executor,
- new BiometricPrompt.AuthenticationCallback {
+ new BiometricPrompt.AuthenticationCallback() {
@Override
// GOOD: This authentication callback uses the result to decrypt some data.
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
From 596f48ca951f54c5e3eda6b7793450e04dc2cf98 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Fri, 2 Feb 2024 17:35:07 +0000
Subject: [PATCH 038/155] Add change note
---
.../change-notes/2024-02-02-android-insecure-local-auth.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 java/ql/src/change-notes/2024-02-02-android-insecure-local-auth.md
diff --git a/java/ql/src/change-notes/2024-02-02-android-insecure-local-auth.md b/java/ql/src/change-notes/2024-02-02-android-insecure-local-auth.md
new file mode 100644
index 000000000000..dc7ebcaade3b
--- /dev/null
+++ b/java/ql/src/change-notes/2024-02-02-android-insecure-local-auth.md
@@ -0,0 +1,5 @@
+
+---
+category: newQuery
+---
+* Added a new query `java/android/insecure-local-authentication` for finding uses of biometric authentication APIs that do not make use of a `KeyStore`-backed key and thus may be bypassed.
\ No newline at end of file
From b8dc6338646935c32f6a69aeb573ccce0acb8b9b Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 5 Feb 2024 11:16:16 +0100
Subject: [PATCH 039/155] add cs/path-injection as markdown to make nicer diffs
---
.../Security Features/CWE-022/TaintedPath.md | 50 +++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 csharp/ql/src/Security Features/CWE-022/TaintedPath.md
diff --git a/csharp/ql/src/Security Features/CWE-022/TaintedPath.md b/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
new file mode 100644
index 000000000000..ddd80d920515
--- /dev/null
+++ b/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
@@ -0,0 +1,50 @@
+# Uncontrolled data used in path expression
+Accessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.
+
+Paths that are naively constructed from data controlled by a user may contain unexpected special characters, such as "..". Such a path may potentially point to any directory on the file system.
+
+
+## Recommendation
+Validate user input before using it to construct a file path. Ideally, follow these rules:
+
+* Do not allow more than a single "." character.
+* Do not allow directory separators such as "/" or "\\" (depending on the file system).
+* Do not rely on simply replacing problematic sequences such as "../". For example, after applying this filter to ".../...//" the resulting string would still be "../".
+* Use a whitelist of known good patterns.
+* Sanitize potentially tainted paths using `HttpRequest.MapPath`.
+
+## Example
+In the first example, a file name is read from a `HttpRequest` and then used to access a file. However, a malicious user could enter a file name which is an absolute path - for example, "/etc/passwd". In the second example, it appears that the user is restricted to opening a file within the "user" home directory. However, a malicious user could enter a filename which contains special characters. For example, the string "../../etc/passwd" will result in the code reading the file located at "/home/\[user\]/../../etc/passwd", which is the system's password file. This file would then be sent back to the user, giving them access to all the system's passwords.
+
+
+```csharp
+using System;
+using System.IO;
+using System.Web;
+
+public class TaintedPathHandler : IHttpHandler
+{
+ public void ProcessRequest(HttpContext ctx)
+ {
+ String path = ctx.Request.QueryString["path"];
+ // BAD: This could read any file on the filesystem.
+ ctx.Response.Write(File.ReadAllText(path));
+
+ // BAD: This could still read any file on the filesystem.
+ ctx.Response.Write(File.ReadAllText("/home/user/" + path));
+
+ // GOOD: MapPath ensures the path is safe to read from.
+ string safePath = ctx.Request.MapPath(path, ctx.Request.ApplicationPath, false);
+ ctx.Response.Write(File.ReadAllText(safePath));
+ }
+}
+
+```
+
+## References
+* OWASP: [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal).
+* Common Weakness Enumeration: [CWE-22](https://cwe.mitre.org/data/definitions/22.html).
+* Common Weakness Enumeration: [CWE-23](https://cwe.mitre.org/data/definitions/23.html).
+* Common Weakness Enumeration: [CWE-36](https://cwe.mitre.org/data/definitions/36.html).
+* Common Weakness Enumeration: [CWE-73](https://cwe.mitre.org/data/definitions/73.html).
+* Common Weakness Enumeration: [CWE-99](https://cwe.mitre.org/data/definitions/99.html).
From 9dfac3a4ccf14fb304ca8b13243a9dc8dec07174 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 5 Feb 2024 11:20:24 +0100
Subject: [PATCH 040/155] move qhelp samples to an `examples` folder
---
csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp | 2 +-
csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp | 4 ++--
.../Security Features/CWE-022/{ => examples}/TaintedPath.cs | 0
.../Security Features/CWE-022/{ => examples}/ZipSlipBad.cs | 0
.../Security Features/CWE-022/{ => examples}/ZipSlipGood.cs | 0
5 files changed, 3 insertions(+), 3 deletions(-)
rename csharp/ql/src/Security Features/CWE-022/{ => examples}/TaintedPath.cs (100%)
rename csharp/ql/src/Security Features/CWE-022/{ => examples}/ZipSlipBad.cs (100%)
rename csharp/ql/src/Security Features/CWE-022/{ => examples}/ZipSlipGood.cs (100%)
diff --git a/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp b/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp
index e838d8c56a4d..3ff4e5447cd8 100644
--- a/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp
+++ b/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp
@@ -34,7 +34,7 @@ enter a filename which contains special characters. For example, the string "../
reading the file located at "/home/[user]/../../etc/passwd", which is the system's password file. This file would then be
sent back to the user, giving them access to all the system's passwords.
-
+
diff --git a/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp b/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp
index a1f39d27b8ce..d75ababa6a8b 100644
--- a/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp
+++ b/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp
@@ -50,7 +50,7 @@ the result is within the destination directory. If provided with a zip file cont
path like ..\sneaky-file, then this file would be written outside the destination
directory.
-
+
To fix this vulnerability, we need to make three changes. Firstly, we need to resolve any
directory traversal or other special characters in the path by using Path.GetFullPath.
@@ -59,7 +59,7 @@ Secondly, we need to identify the destination output directory, again using
the resolved output starts with the resolved destination directory, and throw an exception if this
is not the case.
-
+
diff --git a/csharp/ql/src/Security Features/CWE-022/TaintedPath.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
similarity index 100%
rename from csharp/ql/src/Security Features/CWE-022/TaintedPath.cs
rename to csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
diff --git a/csharp/ql/src/Security Features/CWE-022/ZipSlipBad.cs b/csharp/ql/src/Security Features/CWE-022/examples/ZipSlipBad.cs
similarity index 100%
rename from csharp/ql/src/Security Features/CWE-022/ZipSlipBad.cs
rename to csharp/ql/src/Security Features/CWE-022/examples/ZipSlipBad.cs
diff --git a/csharp/ql/src/Security Features/CWE-022/ZipSlipGood.cs b/csharp/ql/src/Security Features/CWE-022/examples/ZipSlipGood.cs
similarity index 100%
rename from csharp/ql/src/Security Features/CWE-022/ZipSlipGood.cs
rename to csharp/ql/src/Security Features/CWE-022/examples/ZipSlipGood.cs
From 8160291be1e4ca30795b1a61f3e22e3b49cbe370 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 5 Feb 2024 13:08:21 +0100
Subject: [PATCH 041/155] copy (and adjust) the path-injection QHelp from Java
to C#
---
.../CWE-022/TaintedPath.qhelp | 53 +++++++++++++------
.../CWE-022/examples/TaintedPath.cs | 11 +---
.../CWE-022/examples/TaintedPathGoodFolder.cs | 24 +++++++++
.../examples/TaintedPathGoodNormalize.cs | 20 +++++++
4 files changed, 82 insertions(+), 26 deletions(-)
create mode 100644 csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
create mode 100644 csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs
diff --git a/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp b/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp
index 3ff4e5447cd8..bf3132a9719f 100644
--- a/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp
+++ b/csharp/ql/src/Security Features/CWE-022/TaintedPath.qhelp
@@ -7,35 +7,54 @@
can result in sensitive information being revealed or deleted, or an attacker being able to influence
behavior by modifying unexpected files.
-
Paths that are naively constructed from data controlled by a user may contain unexpected special characters,
-such as "..". Such a path may potentially point to any directory on the file system.
+
Paths that are naively constructed from data controlled by a user may be absolute paths, or may contain
+unexpected special characters such as "..". Such a path could point anywhere on the file system.
-
Validate user input before using it to construct a file path. Ideally, follow these rules:
+
Validate user input before using it to construct a file path.
-
-
Do not allow more than a single "." character.
-
Do not allow directory separators such as "/" or "\" (depending on the file system).
-
Do not rely on simply replacing problematic sequences such as "../". For example, after applying this filter to
-".../...//" the resulting string would still be "../".
-
Use a whitelist of known good patterns.
-
Sanitize potentially tainted paths using HttpRequest.MapPath.
-
+
Common validation methods include checking that the normalized path is relative and does not contain
+any ".." components, or checking that the path is contained within a safe folder. The method you should use depends
+on how the path is used in the application, and whether the path should be a single path component.
+
+
+
If the path should be a single path component (such as a file name), you can check for the existence
+of any path separators ("/" or "\"), or ".." sequences in the input, and reject the input if any are found.
+
+
+
+Note that removing "../" sequences is not sufficient, since the input could still contain a path separator
+followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences
+are removed.
+
+
+
Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that
+the user input matches one of these patterns.
-
In the first example, a file name is read from a HttpRequest and then used to access a file. However, a
-malicious user could enter a file name which is an absolute path - for example, "/etc/passwd". In the second example, it
-appears that the user is restricted to opening a file within the "user" home directory. However, a malicious user could
-enter a filename which contains special characters. For example, the string "../../etc/passwd" will result in the code
-reading the file located at "/home/[user]/../../etc/passwd", which is the system's password file. This file would then be
-sent back to the user, giving them access to all the system's passwords.
+
In this example, a user-provided file name is read from a HTTP request and then used to access a file
+and send it back to the user. However, a malicious user could enter a file name anywhere on the file system,
+such as "/etc/passwd" or "../../../etc/passwd".
+
+If the input should only be a file name, you can check that it doesn't contain any path separators or ".." sequences.
+
+
+
+
+
+If the input should be within a specific directory, you can check that the resolved path
+is still contained within that directory.
+
+
+
+
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
index ac2add1b9b0c..c185267a0386 100644
--- a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
@@ -6,15 +6,8 @@ public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
- String path = ctx.Request.QueryString["path"];
+ String filename = ctx.Request.QueryString["path"];
// BAD: This could read any file on the filesystem.
- ctx.Response.Write(File.ReadAllText(path));
-
- // BAD: This could still read any file on the filesystem.
- ctx.Response.Write(File.ReadAllText("/home/user/" + path));
-
- // GOOD: MapPath ensures the path is safe to read from.
- string safePath = ctx.Request.MapPath(path, ctx.Request.ApplicationPath, false);
- ctx.Response.Write(File.ReadAllText(safePath));
+ ctx.Response.Write(File.ReadAllText(filename));
}
}
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
new file mode 100644
index 000000000000..33443abb7173
--- /dev/null
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+using System.Web;
+
+public class TaintedPathHandler : IHttpHandler
+{
+ public void ProcessRequest(HttpContext ctx)
+ {
+ String filename = ctx.Request.QueryString["path"];
+
+ string publicFolder = Path.GetFullPath("/home/" + user + "/public");
+ string filePath = Path.GetFullPath(Path.Combine(publicFolder, filename));
+
+ // GOOD: ensure that the path stays within the public folder
+ if (!filePath.StartsWith(publicFolder + Path.DirectorySeparatorChar))
+ {
+ ctx.Response.StatusCode = 400;
+ ctx.Response.StatusDescription = "Bad Request";
+ ctx.Response.Write("Invalid path");
+ return;
+ }
+ ctx.Response.Write(File.ReadAllText(filename));
+ }
+}
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs
new file mode 100644
index 000000000000..939ceffff238
--- /dev/null
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs
@@ -0,0 +1,20 @@
+using System;
+using System.IO;
+using System.Web;
+
+public class TaintedPathHandler : IHttpHandler
+{
+ public void ProcessRequest(HttpContext ctx)
+ {
+ String filename = ctx.Request.QueryString["path"];
+ // GOOD: ensure that the filename has no path separators or parent directory references
+ if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
+ {
+ ctx.Response.StatusCode = 400;
+ ctx.Response.StatusDescription = "Bad Request";
+ ctx.Response.Write("Invalid path");
+ return;
+ }
+ ctx.Response.Write(File.ReadAllText(filename));
+ }
+}
From a240618ae490a88225b190f1f728ff476a614603 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 5 Feb 2024 13:09:02 +0100
Subject: [PATCH 042/155] generate the new rendered markdown
---
.../Security Features/CWE-022/TaintedPath.md | 82 +++++++++++++++----
1 file changed, 67 insertions(+), 15 deletions(-)
diff --git a/csharp/ql/src/Security Features/CWE-022/TaintedPath.md b/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
index ddd80d920515..c6204c2914ee 100644
--- a/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
+++ b/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
@@ -1,20 +1,23 @@
# Uncontrolled data used in path expression
Accessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.
-Paths that are naively constructed from data controlled by a user may contain unexpected special characters, such as "..". Such a path may potentially point to any directory on the file system.
+Paths that are naively constructed from data controlled by a user may be absolute paths, or may contain unexpected special characters such as "..". Such a path could point anywhere on the file system.
## Recommendation
-Validate user input before using it to construct a file path. Ideally, follow these rules:
+Validate user input before using it to construct a file path.
+
+Common validation methods include checking that the normalized path is relative and does not contain any ".." components, or checking that the path is contained within a safe folder. The method you should use depends on how the path is used in the application, and whether the path should be a single path component.
+
+If the path should be a single path component (such as a file name), you can check for the existence of any path separators ("/" or "\\"), or ".." sequences in the input, and reject the input if any are found.
+
+Note that removing "../" sequences is *not* sufficient, since the input could still contain a path separator followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences are removed.
+
+Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns.
-* Do not allow more than a single "." character.
-* Do not allow directory separators such as "/" or "\\" (depending on the file system).
-* Do not rely on simply replacing problematic sequences such as "../". For example, after applying this filter to ".../...//" the resulting string would still be "../".
-* Use a whitelist of known good patterns.
-* Sanitize potentially tainted paths using `HttpRequest.MapPath`.
## Example
-In the first example, a file name is read from a `HttpRequest` and then used to access a file. However, a malicious user could enter a file name which is an absolute path - for example, "/etc/passwd". In the second example, it appears that the user is restricted to opening a file within the "user" home directory. However, a malicious user could enter a filename which contains special characters. For example, the string "../../etc/passwd" will result in the code reading the file located at "/home/\[user\]/../../etc/passwd", which is the system's password file. This file would then be sent back to the user, giving them access to all the system's passwords.
+In this example, a user-provided file name is read from a HTTP request and then used to access a file and send it back to the user. However, a malicious user could enter a file name anywhere on the file system, such as "/etc/passwd" or "../../../etc/passwd".
```csharp
@@ -26,16 +29,65 @@ public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
- String path = ctx.Request.QueryString["path"];
+ String filename = ctx.Request.QueryString["path"];
// BAD: This could read any file on the filesystem.
- ctx.Response.Write(File.ReadAllText(path));
+ ctx.Response.Write(File.ReadAllText(filename));
+ }
+}
+
+```
+If the input should only be a file name, you can check that it doesn't contain any path separators or ".." sequences.
+
- // BAD: This could still read any file on the filesystem.
- ctx.Response.Write(File.ReadAllText("/home/user/" + path));
+```csharp
+using System;
+using System.IO;
+using System.Web;
+
+public class TaintedPathHandler : IHttpHandler
+{
+ public void ProcessRequest(HttpContext ctx)
+ {
+ String filename = ctx.Request.QueryString["path"];
+ // GOOD: ensure that the filename has no path separators or parent directory references
+ if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
+ {
+ ctx.Response.StatusCode = 400;
+ ctx.Response.StatusDescription = "Bad Request";
+ ctx.Response.Write("Invalid path");
+ return;
+ }
+ ctx.Response.Write(File.ReadAllText(filename));
+ }
+}
+
+```
+If the input should be within a specific directory, you can check that the resolved path is still contained within that directory.
+
+
+```csharp
+using System;
+using System.IO;
+using System.Web;
+
+public class TaintedPathHandler : IHttpHandler
+{
+ public void ProcessRequest(HttpContext ctx)
+ {
+ String filename = ctx.Request.QueryString["path"];
+
+ string publicFolder = Path.GetFullPath("/home/" + user + "/public");
+ string filePath = Path.GetFullPath(Path.Combine(publicFolder, filename));
- // GOOD: MapPath ensures the path is safe to read from.
- string safePath = ctx.Request.MapPath(path, ctx.Request.ApplicationPath, false);
- ctx.Response.Write(File.ReadAllText(safePath));
+ // GOOD: ensure that the path stays within the public folder
+ if (!filePath.StartsWith(publicFolder + Path.DirectorySeparatorChar))
+ {
+ ctx.Response.StatusCode = 400;
+ ctx.Response.StatusDescription = "Bad Request";
+ ctx.Response.Write("Invalid path");
+ return;
+ }
+ ctx.Response.Write(File.ReadAllText(filename));
}
}
From a6b094cf533075e48aed6107ef7c26600751826e Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 5 Feb 2024 13:54:13 +0100
Subject: [PATCH 043/155] delete the rendered markdown again
---
.../Security Features/CWE-022/TaintedPath.md | 102 ------------------
1 file changed, 102 deletions(-)
delete mode 100644 csharp/ql/src/Security Features/CWE-022/TaintedPath.md
diff --git a/csharp/ql/src/Security Features/CWE-022/TaintedPath.md b/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
deleted file mode 100644
index c6204c2914ee..000000000000
--- a/csharp/ql/src/Security Features/CWE-022/TaintedPath.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Uncontrolled data used in path expression
-Accessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.
-
-Paths that are naively constructed from data controlled by a user may be absolute paths, or may contain unexpected special characters such as "..". Such a path could point anywhere on the file system.
-
-
-## Recommendation
-Validate user input before using it to construct a file path.
-
-Common validation methods include checking that the normalized path is relative and does not contain any ".." components, or checking that the path is contained within a safe folder. The method you should use depends on how the path is used in the application, and whether the path should be a single path component.
-
-If the path should be a single path component (such as a file name), you can check for the existence of any path separators ("/" or "\\"), or ".." sequences in the input, and reject the input if any are found.
-
-Note that removing "../" sequences is *not* sufficient, since the input could still contain a path separator followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences are removed.
-
-Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns.
-
-
-## Example
-In this example, a user-provided file name is read from a HTTP request and then used to access a file and send it back to the user. However, a malicious user could enter a file name anywhere on the file system, such as "/etc/passwd" or "../../../etc/passwd".
-
-
-```csharp
-using System;
-using System.IO;
-using System.Web;
-
-public class TaintedPathHandler : IHttpHandler
-{
- public void ProcessRequest(HttpContext ctx)
- {
- String filename = ctx.Request.QueryString["path"];
- // BAD: This could read any file on the filesystem.
- ctx.Response.Write(File.ReadAllText(filename));
- }
-}
-
-```
-If the input should only be a file name, you can check that it doesn't contain any path separators or ".." sequences.
-
-
-```csharp
-using System;
-using System.IO;
-using System.Web;
-
-public class TaintedPathHandler : IHttpHandler
-{
- public void ProcessRequest(HttpContext ctx)
- {
- String filename = ctx.Request.QueryString["path"];
- // GOOD: ensure that the filename has no path separators or parent directory references
- if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
- {
- ctx.Response.StatusCode = 400;
- ctx.Response.StatusDescription = "Bad Request";
- ctx.Response.Write("Invalid path");
- return;
- }
- ctx.Response.Write(File.ReadAllText(filename));
- }
-}
-
-```
-If the input should be within a specific directory, you can check that the resolved path is still contained within that directory.
-
-
-```csharp
-using System;
-using System.IO;
-using System.Web;
-
-public class TaintedPathHandler : IHttpHandler
-{
- public void ProcessRequest(HttpContext ctx)
- {
- String filename = ctx.Request.QueryString["path"];
-
- string publicFolder = Path.GetFullPath("/home/" + user + "/public");
- string filePath = Path.GetFullPath(Path.Combine(publicFolder, filename));
-
- // GOOD: ensure that the path stays within the public folder
- if (!filePath.StartsWith(publicFolder + Path.DirectorySeparatorChar))
- {
- ctx.Response.StatusCode = 400;
- ctx.Response.StatusDescription = "Bad Request";
- ctx.Response.Write("Invalid path");
- return;
- }
- ctx.Response.Write(File.ReadAllText(filename));
- }
-}
-
-```
-
-## References
-* OWASP: [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal).
-* Common Weakness Enumeration: [CWE-22](https://cwe.mitre.org/data/definitions/22.html).
-* Common Weakness Enumeration: [CWE-23](https://cwe.mitre.org/data/definitions/23.html).
-* Common Weakness Enumeration: [CWE-36](https://cwe.mitre.org/data/definitions/36.html).
-* Common Weakness Enumeration: [CWE-73](https://cwe.mitre.org/data/definitions/73.html).
-* Common Weakness Enumeration: [CWE-99](https://cwe.mitre.org/data/definitions/99.html).
From f792b5842125560303ecc0019017262e0b3e3400 Mon Sep 17 00:00:00 2001
From: Harry Maclean
Date: Mon, 5 Feb 2024 16:45:59 +0000
Subject: [PATCH 044/155] Ruby: Recognise more ActiveRecord connections
---
.../codeql/ruby/frameworks/ActiveRecord.qll | 6 +-
.../active_record/ActiveRecord.expected | 227 +++++++++---------
.../frameworks/active_record/ActiveRecord.rb | 4 +
3 files changed, 125 insertions(+), 112 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
index 843eb4f8d6e5..4596c4320701 100644
--- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
+++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
@@ -77,7 +77,11 @@ private predicate isUnlikelyExternalCall(API::MethodAccessNode node) {
}
private API::Node activeRecordConnectionInstance() {
- result = activeRecordBaseClass().getReturn("connection")
+ result =
+ [
+ activeRecordBaseClass().getReturn("connection"),
+ activeRecordBaseClass().getInstance().getReturn("connection")
+ ]
}
/**
diff --git a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected
index d7195d11ad7c..b273bddbee64 100644
--- a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected
+++ b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expected
@@ -1,7 +1,7 @@
activeRecordModelClasses
| ActiveRecord.rb:1:1:3:3 | UserGroup |
-| ActiveRecord.rb:5:1:15:3 | User |
-| ActiveRecord.rb:17:1:21:3 | Admin |
+| ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:21:1:25:3 | Admin |
| associations.rb:1:1:3:3 | Author |
| associations.rb:5:1:9:3 | Post |
| associations.rb:11:1:13:3 | Tag |
@@ -10,17 +10,20 @@ activeRecordInstances
| ActiveRecord.rb:9:5:9:68 | call to find |
| ActiveRecord.rb:13:5:13:40 | call to find_by |
| ActiveRecord.rb:13:5:13:46 | call to users |
-| ActiveRecord.rb:35:5:35:51 | call to authenticate |
-| ActiveRecord.rb:36:5:36:30 | call to find_by_name |
-| ActiveRecord.rb:55:5:57:7 | if ... |
-| ActiveRecord.rb:55:43:56:40 | then ... |
-| ActiveRecord.rb:56:7:56:40 | call to find_by |
-| ActiveRecord.rb:60:5:60:33 | call to find_by |
-| ActiveRecord.rb:62:5:62:34 | call to find |
-| ActiveRecord.rb:72:5:72:24 | call to create |
-| ActiveRecord.rb:76:5:76:66 | call to create |
-| ActiveRecord.rb:80:5:80:68 | call to create |
-| ActiveRecord.rb:84:5:84:16 | call to create |
+| ActiveRecord.rb:16:3:18:5 | self (exec) |
+| ActiveRecord.rb:16:3:18:5 | self in exec |
+| ActiveRecord.rb:17:5:17:14 | self |
+| ActiveRecord.rb:39:5:39:51 | call to authenticate |
+| ActiveRecord.rb:40:5:40:30 | call to find_by_name |
+| ActiveRecord.rb:59:5:61:7 | if ... |
+| ActiveRecord.rb:59:43:60:40 | then ... |
+| ActiveRecord.rb:60:7:60:40 | call to find_by |
+| ActiveRecord.rb:64:5:64:33 | call to find_by |
+| ActiveRecord.rb:66:5:66:34 | call to find |
+| ActiveRecord.rb:76:5:76:24 | call to create |
+| ActiveRecord.rb:80:5:80:66 | call to create |
+| ActiveRecord.rb:84:5:84:68 | call to create |
+| ActiveRecord.rb:88:5:88:16 | call to create |
| associations.rb:19:1:19:7 | author1 |
| associations.rb:19:1:19:20 | ... = ... |
| associations.rb:19:11:19:20 | call to new |
@@ -105,46 +108,47 @@ activeRecordInstances
| associations.rb:53:1:53:34 | call to find |
activeRecordSqlExecutionRanges
| ActiveRecord.rb:9:33:9:67 | "name='#{...}' and pass='#{...}'" |
-| ActiveRecord.rb:19:16:19:24 | condition |
-| ActiveRecord.rb:28:30:28:44 | ...[...] |
-| ActiveRecord.rb:29:20:29:42 | "id = '#{...}'" |
-| ActiveRecord.rb:30:21:30:45 | call to [] |
-| ActiveRecord.rb:31:16:31:21 | <<-SQL |
-| ActiveRecord.rb:34:20:34:47 | "user.id = '#{...}'" |
-| ActiveRecord.rb:46:20:46:32 | ... + ... |
-| ActiveRecord.rb:52:16:52:28 | "name #{...}" |
-| ActiveRecord.rb:56:20:56:39 | "username = #{...}" |
-| ActiveRecord.rb:68:21:68:44 | ...[...] |
-| ActiveRecord.rb:106:27:106:76 | "this is an unsafe annotation:..." |
+| ActiveRecord.rb:17:24:17:24 | q |
+| ActiveRecord.rb:23:16:23:24 | condition |
+| ActiveRecord.rb:32:30:32:44 | ...[...] |
+| ActiveRecord.rb:33:20:33:42 | "id = '#{...}'" |
+| ActiveRecord.rb:34:21:34:45 | call to [] |
+| ActiveRecord.rb:35:16:35:21 | <<-SQL |
+| ActiveRecord.rb:38:20:38:47 | "user.id = '#{...}'" |
+| ActiveRecord.rb:50:20:50:32 | ... + ... |
+| ActiveRecord.rb:56:16:56:28 | "name #{...}" |
+| ActiveRecord.rb:60:20:60:39 | "username = #{...}" |
+| ActiveRecord.rb:72:21:72:44 | ...[...] |
+| ActiveRecord.rb:110:27:110:76 | "this is an unsafe annotation:..." |
activeRecordModelClassMethodCalls
| ActiveRecord.rb:2:3:2:17 | call to has_many |
| ActiveRecord.rb:6:3:6:24 | call to belongs_to |
| ActiveRecord.rb:9:5:9:68 | call to find |
| ActiveRecord.rb:13:5:13:40 | call to find_by |
| ActiveRecord.rb:13:5:13:46 | call to users |
-| ActiveRecord.rb:19:5:19:25 | call to destroy_by |
-| ActiveRecord.rb:28:5:28:45 | call to calculate |
-| ActiveRecord.rb:29:5:29:43 | call to delete_by |
-| ActiveRecord.rb:30:5:30:46 | call to destroy_by |
-| ActiveRecord.rb:31:5:31:35 | call to where |
-| ActiveRecord.rb:34:5:34:14 | call to where |
-| ActiveRecord.rb:34:5:34:48 | call to not |
-| ActiveRecord.rb:36:5:36:30 | call to find_by_name |
-| ActiveRecord.rb:37:5:37:36 | call to not_a_find_by_method |
-| ActiveRecord.rb:46:5:46:33 | call to delete_by |
-| ActiveRecord.rb:52:5:52:29 | call to order |
-| ActiveRecord.rb:56:7:56:40 | call to find_by |
-| ActiveRecord.rb:60:5:60:33 | call to find_by |
-| ActiveRecord.rb:62:5:62:34 | call to find |
-| ActiveRecord.rb:72:5:72:24 | call to create |
-| ActiveRecord.rb:76:5:76:66 | call to create |
-| ActiveRecord.rb:80:5:80:68 | call to create |
-| ActiveRecord.rb:84:5:84:16 | call to create |
-| ActiveRecord.rb:88:5:88:27 | call to update |
-| ActiveRecord.rb:92:5:92:69 | call to update |
-| ActiveRecord.rb:96:5:96:71 | call to update |
-| ActiveRecord.rb:102:13:102:54 | call to annotate |
-| ActiveRecord.rb:106:13:106:77 | call to annotate |
+| ActiveRecord.rb:23:5:23:25 | call to destroy_by |
+| ActiveRecord.rb:32:5:32:45 | call to calculate |
+| ActiveRecord.rb:33:5:33:43 | call to delete_by |
+| ActiveRecord.rb:34:5:34:46 | call to destroy_by |
+| ActiveRecord.rb:35:5:35:35 | call to where |
+| ActiveRecord.rb:38:5:38:14 | call to where |
+| ActiveRecord.rb:38:5:38:48 | call to not |
+| ActiveRecord.rb:40:5:40:30 | call to find_by_name |
+| ActiveRecord.rb:41:5:41:36 | call to not_a_find_by_method |
+| ActiveRecord.rb:50:5:50:33 | call to delete_by |
+| ActiveRecord.rb:56:5:56:29 | call to order |
+| ActiveRecord.rb:60:7:60:40 | call to find_by |
+| ActiveRecord.rb:64:5:64:33 | call to find_by |
+| ActiveRecord.rb:66:5:66:34 | call to find |
+| ActiveRecord.rb:76:5:76:24 | call to create |
+| ActiveRecord.rb:80:5:80:66 | call to create |
+| ActiveRecord.rb:84:5:84:68 | call to create |
+| ActiveRecord.rb:88:5:88:16 | call to create |
+| ActiveRecord.rb:92:5:92:27 | call to update |
+| ActiveRecord.rb:96:5:96:69 | call to update |
+| ActiveRecord.rb:100:5:100:71 | call to update |
+| ActiveRecord.rb:106:13:106:54 | call to annotate |
+| ActiveRecord.rb:110:13:110:77 | call to annotate |
| associations.rb:2:3:2:17 | call to has_many |
| associations.rb:6:3:6:20 | call to belongs_to |
| associations.rb:7:3:7:20 | call to has_many |
@@ -200,41 +204,41 @@ activeRecordModelClassMethodCalls
activeRecordModelClassMethodCallsReplacement
| ActiveRecord.rb:1:1:3:3 | UserGroup | ActiveRecord.rb:2:3:2:17 | call to has_many |
| ActiveRecord.rb:1:1:3:3 | UserGroup | ActiveRecord.rb:13:5:13:40 | call to find_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:6:3:6:24 | call to belongs_to |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:9:5:9:68 | call to find |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:19:5:19:25 | call to destroy_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:28:5:28:45 | call to calculate |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:29:5:29:43 | call to delete_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:30:5:30:46 | call to destroy_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:31:5:31:35 | call to where |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:34:5:34:14 | call to where |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:35:5:35:51 | call to authenticate |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:36:5:36:30 | call to find_by_name |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:37:5:37:36 | call to not_a_find_by_method |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:46:5:46:33 | call to delete_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:52:5:52:29 | call to order |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:56:7:56:40 | call to find_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:60:5:60:33 | call to find_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:62:5:62:34 | call to find |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:68:5:68:45 | call to delete_by |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:72:5:72:24 | call to create |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:76:5:76:66 | call to create |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:80:5:80:68 | call to create |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:84:5:84:16 | call to create |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:88:5:88:27 | call to update |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:92:5:92:69 | call to update |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:96:5:96:71 | call to update |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:102:13:102:54 | call to annotate |
-| ActiveRecord.rb:5:1:15:3 | User | ActiveRecord.rb:106:13:106:77 | call to annotate |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:19:5:19:25 | call to destroy_by |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:68:5:68:45 | call to delete_by |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:72:5:72:24 | call to create |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:76:5:76:66 | call to create |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:80:5:80:68 | call to create |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:84:5:84:16 | call to create |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:88:5:88:27 | call to update |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:92:5:92:69 | call to update |
-| ActiveRecord.rb:17:1:21:3 | Admin | ActiveRecord.rb:96:5:96:71 | call to update |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:6:3:6:24 | call to belongs_to |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:9:5:9:68 | call to find |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:23:5:23:25 | call to destroy_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:32:5:32:45 | call to calculate |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:33:5:33:43 | call to delete_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:34:5:34:46 | call to destroy_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:35:5:35:35 | call to where |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:38:5:38:14 | call to where |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:39:5:39:51 | call to authenticate |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:40:5:40:30 | call to find_by_name |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:41:5:41:36 | call to not_a_find_by_method |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:50:5:50:33 | call to delete_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:56:5:56:29 | call to order |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:60:7:60:40 | call to find_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:64:5:64:33 | call to find_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:66:5:66:34 | call to find |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:72:5:72:45 | call to delete_by |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:76:5:76:24 | call to create |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:80:5:80:66 | call to create |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:84:5:84:68 | call to create |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:88:5:88:16 | call to create |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:92:5:92:27 | call to update |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:96:5:96:69 | call to update |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:100:5:100:71 | call to update |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:106:13:106:54 | call to annotate |
+| ActiveRecord.rb:5:1:19:3 | User | ActiveRecord.rb:110:13:110:77 | call to annotate |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:23:5:23:25 | call to destroy_by |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:72:5:72:45 | call to delete_by |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:76:5:76:24 | call to create |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:80:5:80:66 | call to create |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:84:5:84:68 | call to create |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:88:5:88:16 | call to create |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:92:5:92:27 | call to update |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:96:5:96:69 | call to update |
+| ActiveRecord.rb:21:1:25:3 | Admin | ActiveRecord.rb:100:5:100:71 | call to update |
| associations.rb:1:1:3:3 | Author | associations.rb:2:3:2:17 | call to has_many |
| associations.rb:1:1:3:3 | Author | associations.rb:19:11:19:20 | call to new |
| associations.rb:5:1:9:3 | Post | associations.rb:6:3:6:20 | call to belongs_to |
@@ -244,28 +248,29 @@ activeRecordModelClassMethodCallsReplacement
| associations.rb:15:1:17:3 | Comment | associations.rb:16:3:16:18 | call to belongs_to |
potentiallyUnsafeSqlExecutingMethodCall
| ActiveRecord.rb:9:5:9:68 | call to find |
-| ActiveRecord.rb:19:5:19:25 | call to destroy_by |
-| ActiveRecord.rb:28:5:28:45 | call to calculate |
-| ActiveRecord.rb:29:5:29:43 | call to delete_by |
-| ActiveRecord.rb:30:5:30:46 | call to destroy_by |
-| ActiveRecord.rb:31:5:31:35 | call to where |
-| ActiveRecord.rb:34:5:34:48 | call to not |
-| ActiveRecord.rb:46:5:46:33 | call to delete_by |
-| ActiveRecord.rb:52:5:52:29 | call to order |
-| ActiveRecord.rb:56:7:56:40 | call to find_by |
-| ActiveRecord.rb:106:13:106:77 | call to annotate |
+| ActiveRecord.rb:23:5:23:25 | call to destroy_by |
+| ActiveRecord.rb:32:5:32:45 | call to calculate |
+| ActiveRecord.rb:33:5:33:43 | call to delete_by |
+| ActiveRecord.rb:34:5:34:46 | call to destroy_by |
+| ActiveRecord.rb:35:5:35:35 | call to where |
+| ActiveRecord.rb:38:5:38:48 | call to not |
+| ActiveRecord.rb:50:5:50:33 | call to delete_by |
+| ActiveRecord.rb:56:5:56:29 | call to order |
+| ActiveRecord.rb:60:7:60:40 | call to find_by |
+| ActiveRecord.rb:110:13:110:77 | call to annotate |
activeRecordModelInstantiations
-| ActiveRecord.rb:9:5:9:68 | call to find | ActiveRecord.rb:5:1:15:3 | User |
+| ActiveRecord.rb:9:5:9:68 | call to find | ActiveRecord.rb:5:1:19:3 | User |
| ActiveRecord.rb:13:5:13:40 | call to find_by | ActiveRecord.rb:1:1:3:3 | UserGroup |
-| ActiveRecord.rb:13:5:13:46 | call to users | ActiveRecord.rb:5:1:15:3 | User |
-| ActiveRecord.rb:36:5:36:30 | call to find_by_name | ActiveRecord.rb:5:1:15:3 | User |
-| ActiveRecord.rb:56:7:56:40 | call to find_by | ActiveRecord.rb:5:1:15:3 | User |
-| ActiveRecord.rb:60:5:60:33 | call to find_by | ActiveRecord.rb:5:1:15:3 | User |
-| ActiveRecord.rb:62:5:62:34 | call to find | ActiveRecord.rb:5:1:15:3 | User |
-| ActiveRecord.rb:72:5:72:24 | call to create | ActiveRecord.rb:17:1:21:3 | Admin |
-| ActiveRecord.rb:76:5:76:66 | call to create | ActiveRecord.rb:17:1:21:3 | Admin |
-| ActiveRecord.rb:80:5:80:68 | call to create | ActiveRecord.rb:17:1:21:3 | Admin |
-| ActiveRecord.rb:84:5:84:16 | call to create | ActiveRecord.rb:17:1:21:3 | Admin |
+| ActiveRecord.rb:13:5:13:46 | call to users | ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:16:3:18:5 | self in exec | ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:40:5:40:30 | call to find_by_name | ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:60:7:60:40 | call to find_by | ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:64:5:64:33 | call to find_by | ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:66:5:66:34 | call to find | ActiveRecord.rb:5:1:19:3 | User |
+| ActiveRecord.rb:76:5:76:24 | call to create | ActiveRecord.rb:21:1:25:3 | Admin |
+| ActiveRecord.rb:80:5:80:66 | call to create | ActiveRecord.rb:21:1:25:3 | Admin |
+| ActiveRecord.rb:84:5:84:68 | call to create | ActiveRecord.rb:21:1:25:3 | Admin |
+| ActiveRecord.rb:88:5:88:16 | call to create | ActiveRecord.rb:21:1:25:3 | Admin |
| associations.rb:19:11:19:20 | call to new | associations.rb:1:1:3:3 | Author |
| associations.rb:21:9:21:21 | call to posts | associations.rb:5:1:9:3 | Post |
| associations.rb:21:9:21:28 | call to create | associations.rb:5:1:9:3 | Post |
@@ -307,13 +312,13 @@ activeRecordModelInstantiations
| associations.rb:53:1:53:13 | call to posts | associations.rb:5:1:9:3 | Post |
| associations.rb:53:1:53:20 | call to reload | associations.rb:5:1:9:3 | Post |
persistentWriteAccesses
-| ActiveRecord.rb:72:5:72:24 | call to create | ActiveRecord.rb:72:18:72:23 | call to params |
-| ActiveRecord.rb:76:5:76:66 | call to create | ActiveRecord.rb:76:24:76:36 | ...[...] |
-| ActiveRecord.rb:76:5:76:66 | call to create | ActiveRecord.rb:76:49:76:65 | ...[...] |
-| ActiveRecord.rb:80:5:80:68 | call to create | ActiveRecord.rb:80:25:80:37 | ...[...] |
-| ActiveRecord.rb:80:5:80:68 | call to create | ActiveRecord.rb:80:50:80:66 | ...[...] |
-| ActiveRecord.rb:88:5:88:27 | call to update | ActiveRecord.rb:88:21:88:26 | call to params |
-| ActiveRecord.rb:92:5:92:69 | call to update | ActiveRecord.rb:92:27:92:39 | ...[...] |
-| ActiveRecord.rb:92:5:92:69 | call to update | ActiveRecord.rb:92:52:92:68 | ...[...] |
-| ActiveRecord.rb:96:5:96:71 | call to update | ActiveRecord.rb:96:21:96:70 | call to [] |
+| ActiveRecord.rb:76:5:76:24 | call to create | ActiveRecord.rb:76:18:76:23 | call to params |
+| ActiveRecord.rb:80:5:80:66 | call to create | ActiveRecord.rb:80:24:80:36 | ...[...] |
+| ActiveRecord.rb:80:5:80:66 | call to create | ActiveRecord.rb:80:49:80:65 | ...[...] |
+| ActiveRecord.rb:84:5:84:68 | call to create | ActiveRecord.rb:84:25:84:37 | ...[...] |
+| ActiveRecord.rb:84:5:84:68 | call to create | ActiveRecord.rb:84:50:84:66 | ...[...] |
+| ActiveRecord.rb:92:5:92:27 | call to update | ActiveRecord.rb:92:21:92:26 | call to params |
+| ActiveRecord.rb:96:5:96:69 | call to update | ActiveRecord.rb:96:27:96:39 | ...[...] |
+| ActiveRecord.rb:96:5:96:69 | call to update | ActiveRecord.rb:96:52:96:68 | ...[...] |
+| ActiveRecord.rb:100:5:100:71 | call to update | ActiveRecord.rb:100:21:100:70 | call to [] |
| associations.rb:31:16:31:22 | ... = ... | associations.rb:31:16:31:22 | author2 |
diff --git a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.rb b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.rb
index 8e5961c87710..dca8f3c43d36 100644
--- a/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.rb
+++ b/ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.rb
@@ -12,6 +12,10 @@ def self.authenticate(name, pass)
def self.from(user_group_id)
UserGroup.find_by(id: user_group_id).users
end
+
+ def exec(q)
+ connection.execute(q)
+ end
end
class Admin < User
From 44fe34a37d32c1eb9403cb09c151bb9bcd3aec1b Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 6 Feb 2024 09:20:27 +0100
Subject: [PATCH 045/155] use the correct string type in the tainted-path
examples
---
csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs | 2 +-
.../Security Features/CWE-022/examples/TaintedPathGoodFolder.cs | 2 +-
.../CWE-022/examples/TaintedPathGoodNormalize.cs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
index c185267a0386..4539aed8b883 100644
--- a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPath.cs
@@ -6,7 +6,7 @@ public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
- String filename = ctx.Request.QueryString["path"];
+ string filename = ctx.Request.QueryString["path"];
// BAD: This could read any file on the filesystem.
ctx.Response.Write(File.ReadAllText(filename));
}
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
index 33443abb7173..6a3991ac7ad0 100644
--- a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
@@ -6,7 +6,7 @@ public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
- String filename = ctx.Request.QueryString["path"];
+ string filename = ctx.Request.QueryString["path"];
string publicFolder = Path.GetFullPath("/home/" + user + "/public");
string filePath = Path.GetFullPath(Path.Combine(publicFolder, filename));
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs
index 939ceffff238..0e31e8b68c90 100644
--- a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodNormalize.cs
@@ -6,7 +6,7 @@ public class TaintedPathHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
- String filename = ctx.Request.QueryString["path"];
+ string filename = ctx.Request.QueryString["path"];
// GOOD: ensure that the filename has no path separators or parent directory references
if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
{
From 4e176236e77b1fa4fd19d742cde7567e3bb2037e Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 6 Feb 2024 09:21:35 +0100
Subject: [PATCH 046/155] add a definition of user
---
.../Security Features/CWE-022/examples/TaintedPathGoodFolder.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
index 6a3991ac7ad0..19af394b1c74 100644
--- a/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
+++ b/csharp/ql/src/Security Features/CWE-022/examples/TaintedPathGoodFolder.cs
@@ -8,6 +8,7 @@ public void ProcessRequest(HttpContext ctx)
{
string filename = ctx.Request.QueryString["path"];
+ string user = ctx.User.Identity.Name;
string publicFolder = Path.GetFullPath("/home/" + user + "/public");
string filePath = Path.GetFullPath(Path.Combine(publicFolder, filename));
From 94b7bda3dcbec3beec45d8dc7ef4eb7834fbb50d Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 6 Feb 2024 09:36:30 +0100
Subject: [PATCH 047/155] exclude tagged template literals from
`js/superfluous-trailing-arguments`
---
.../ql/src/LanguageFeatures/SpuriousArguments.ql | 3 ++-
.../LanguageFeatures/SpuriousArguments/tst.js | 11 ++++++++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql
index de8f248d2d4f..fd3914c90232 100644
--- a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql
+++ b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql
@@ -46,7 +46,8 @@ class SpuriousArguments extends Expr {
SpuriousArguments() {
this = invk.getArgument(maxArity(invk)).asExpr() and
- not invk.isIncomplete()
+ not invk.isIncomplete() and
+ not invk.getAstNode() instanceof TaggedTemplateExpr
}
/**
diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SpuriousArguments/tst.js b/javascript/ql/test/query-tests/LanguageFeatures/SpuriousArguments/tst.js
index 13877ff1ddac..1caa88564a1a 100644
--- a/javascript/ql/test/query-tests/LanguageFeatures/SpuriousArguments/tst.js
+++ b/javascript/ql/test/query-tests/LanguageFeatures/SpuriousArguments/tst.js
@@ -129,4 +129,13 @@ function sum2() {
}
// OK
-sum2(1, 2, 3);
\ No newline at end of file
+sum2(1, 2, 3);
+
+const $ = function (x, arr) {
+ console.log(x, arr);
+};
+
+// OK
+async function tagThing(repoUrl, directory) {
+ await $`git clone ${repoUrl} ${directory}`;
+}
From 705a37706019c2ba531aa613d34ce42757eacd55 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Mon, 5 Feb 2024 19:52:30 +0000
Subject: [PATCH 048/155] Address review comments.
---
java/ql/lib/change-notes/2024-01-31-new-models.md | 3 ---
java/ql/lib/ext/android.app.model.yml | 1 -
java/ql/lib/ext/java.net.model.yml | 1 -
java/ql/lib/ext/java.nio.file.model.yml | 1 -
java/ql/lib/ext/javax.xml.parsers.model.yml | 6 ------
java/ql/lib/ext/org.apache.http.impl.client.model.yml | 1 -
6 files changed, 13 deletions(-)
delete mode 100644 java/ql/lib/ext/javax.xml.parsers.model.yml
diff --git a/java/ql/lib/change-notes/2024-01-31-new-models.md b/java/ql/lib/change-notes/2024-01-31-new-models.md
index 195c1dd99543..4fbc1b595712 100644
--- a/java/ql/lib/change-notes/2024-01-31-new-models.md
+++ b/java/ql/lib/change-notes/2024-01-31-new-models.md
@@ -3,7 +3,6 @@ category: minorAnalysis
---
* Added models for the following packages:
- * android.app
* java.io
* java.lang
* java.net
@@ -11,11 +10,9 @@ category: minorAnalysis
* java.nio.file
* java.util.zip
* javax.servlet
- * javax.xml.parsers
* kotlin.io
* org.apache.commons.io
* org.apache.hadoop.fs
* org.apache.hadoop.fs.s3a
- * org.apache.http.impl.client
* org.eclipse.jetty.client
* org.gradle.api.file
diff --git a/java/ql/lib/ext/android.app.model.yml b/java/ql/lib/ext/android.app.model.yml
index f70ea2d238ca..28b5171c0d73 100644
--- a/java/ql/lib/ext/android.app.model.yml
+++ b/java/ql/lib/ext/android.app.model.yml
@@ -6,7 +6,6 @@ extensions:
- ["android.app", "Activity", True, "bindService", "", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "bindServiceAsUser", "", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "setResult", "(int,Intent)", "", "Argument[1]", "pending-intents", "manual"]
- - ["android.app", "Activity", True, "startActivity", "(Intent)", "", "Argument[0]", "intent-redirection", "ai-manual"]
- ["android.app", "Activity", True, "startActivityAsCaller", "", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "startActivityForResult", "(Intent,int)", "", "Argument[0]", "intent-redirection", "manual"]
- ["android.app", "Activity", True, "startActivityForResult", "(Intent,int,Bundle)", "", "Argument[0]", "intent-redirection", "manual"]
diff --git a/java/ql/lib/ext/java.net.model.yml b/java/ql/lib/ext/java.net.model.yml
index a6dd7fc5ce84..afdf3320b088 100644
--- a/java/ql/lib/ext/java.net.model.yml
+++ b/java/ql/lib/ext/java.net.model.yml
@@ -44,7 +44,6 @@ extensions:
- ["java.net", "InetSocketAddress", True, "InetSocketAddress", "(String,int)", "", "Argument[0]", "Argument[this]", "taint", "ai-manual"]
- ["java.net", "URI", False, "resolve", "(URI)", "", "Argument[this]", "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", "Argument[5]", "Argument[this].SyntheticField[java.net.URI.query]", "taint", "ai-manual"]
- - ["java.net", "URI", False, "URI", "(String,String,String,int,String,String,String)", "", "Argument[4]", "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String,String,String)", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.net", "URI", False, "URI", "(String)", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["java.net", "URI", False, "create", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
diff --git a/java/ql/lib/ext/java.nio.file.model.yml b/java/ql/lib/ext/java.nio.file.model.yml
index ea32fa75fe38..f41cbf3a3e99 100644
--- a/java/ql/lib/ext/java.nio.file.model.yml
+++ b/java/ql/lib/ext/java.nio.file.model.yml
@@ -82,7 +82,6 @@ extensions:
- ["java.nio.file", "Path", False, "toFile", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toString", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Path", True, "toUri", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- - ["java.nio.file", "Paths", False, "get", "(String,String[])", "", "Argument[1]", "ReturnValue", "taint", "ai-manual"]
- ["java.nio.file", "Paths", True, "get", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["java.nio.file", "Paths", True, "get", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
# Not supported by current lambda flow
diff --git a/java/ql/lib/ext/javax.xml.parsers.model.yml b/java/ql/lib/ext/javax.xml.parsers.model.yml
deleted file mode 100644
index d39a28f5942c..000000000000
--- a/java/ql/lib/ext/javax.xml.parsers.model.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-extensions:
- - addsTo:
- pack: codeql/java-all
- extensible: sinkModel
- data:
- - ["javax.xml.parsers", "DocumentBuilder", True, "parse", "(InputSource)", "", "Argument[0]", "xxe", "ai-manual"]
diff --git a/java/ql/lib/ext/org.apache.http.impl.client.model.yml b/java/ql/lib/ext/org.apache.http.impl.client.model.yml
index 6f407ac36825..be517e5344f5 100644
--- a/java/ql/lib/ext/org.apache.http.impl.client.model.yml
+++ b/java/ql/lib/ext/org.apache.http.impl.client.model.yml
@@ -3,5 +3,4 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
- - ["org.apache.http.impl.client", "CloseableHttpClient", True, "execute", "(HttpUriRequest)", "", "Argument[0]", "request-forgery", "ai-manual"]
- ["org.apache.http.impl.client", "RequestWrapper", True, "setURI", "(URI)", "", "Argument[0]", "request-forgery", "hq-manual"]
From 1484a169d743bec1bf3d08b390ddc5c1efad16c5 Mon Sep 17 00:00:00 2001
From: Jonathan Leitschuh
Date: Tue, 6 Feb 2024 15:43:19 -0500
Subject: [PATCH 049/155] Reduce severity of `java/relative-path-command`
Significantly reduces the severity of `java/relative-path-command` from 9.8 to 5.4
https://www.first.org/cvss/calculator/4.0#CVSS:4.0/AV:L/AC:L/AT:P/PR:H/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
---
java/ql/src/Security/CWE/CWE-078/ExecRelative.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql b/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql
index 501826c6426f..533980a3f0a4 100644
--- a/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql
+++ b/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql
@@ -4,7 +4,7 @@
* malicious changes in the PATH environment variable.
* @kind problem
* @problem.severity warning
- * @security-severity 9.8
+ * @security-severity 5.4
* @precision medium
* @id java/relative-path-command
* @tags security
From 082754a3d8dd3a3a8d550bc5e1b150ea83f51717 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 7 Feb 2024 13:21:59 +0000
Subject: [PATCH 050/155] Remove problematic Kotlin model.
---
java/ql/lib/change-notes/2024-01-31-new-models.md | 1 -
java/ql/lib/ext/kotlin.io.model.yml | 1 -
2 files changed, 2 deletions(-)
diff --git a/java/ql/lib/change-notes/2024-01-31-new-models.md b/java/ql/lib/change-notes/2024-01-31-new-models.md
index 4fbc1b595712..bdb588f3bc38 100644
--- a/java/ql/lib/change-notes/2024-01-31-new-models.md
+++ b/java/ql/lib/change-notes/2024-01-31-new-models.md
@@ -10,7 +10,6 @@ category: minorAnalysis
* java.nio.file
* java.util.zip
* javax.servlet
- * kotlin.io
* org.apache.commons.io
* org.apache.hadoop.fs
* org.apache.hadoop.fs.s3a
diff --git a/java/ql/lib/ext/kotlin.io.model.yml b/java/ql/lib/ext/kotlin.io.model.yml
index c65862f6eacc..b748e04a292d 100644
--- a/java/ql/lib/ext/kotlin.io.model.yml
+++ b/java/ql/lib/ext/kotlin.io.model.yml
@@ -3,7 +3,6 @@ extensions:
pack: codeql/java-all
extensible: sinkModel
data:
- - ["kotlin.io", "FilesKt", False, "appendText$default", "(File,String,Charset,int,Object)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["kotlin.io", "FilesKt", False, "deleteRecursively", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["kotlin.io", "FilesKt", False, "inputStream", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
- ["kotlin.io", "FilesKt", False, "readBytes", "(File)", "", "Argument[0]", "path-injection", "ai-manual"]
From 9ce75dac0e540545d61dbb9c1af9bf1dab9d5971 Mon Sep 17 00:00:00 2001
From: Maiky <76447395+maikypedia@users.noreply.github.com>
Date: Wed, 7 Feb 2024 14:26:56 +0100
Subject: [PATCH 051/155] Update UnsafeUnpackQuery.qll
---
swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll | 1 -
1 file changed, 1 deletion(-)
diff --git a/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll b/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll
index dbc0f733b526..59be3a7eb31e 100644
--- a/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll
+++ b/swift/ql/lib/codeql/swift/security/UnsafeUnpackQuery.qll
@@ -5,7 +5,6 @@
*/
import swift
-import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.TaintTracking
import codeql.swift.dataflow.FlowSources
import codeql.swift.security.UnsafeUnpackExtensions
From c6fb303d63fa6ba3fef9efcc1a312e0d86ecbe4b Mon Sep 17 00:00:00 2001
From: Maiky <76447395+maikypedia@users.noreply.github.com>
Date: Wed, 7 Feb 2024 14:27:40 +0100
Subject: [PATCH 052/155] Suggested changes
Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
---
.../src/experimental/Security/CWE-022/UnsafeUnpack.qhelp | 2 +-
.../ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp
index df162180c536..2f65296b9a8a 100644
--- a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp
+++ b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.qhelp
@@ -31,7 +31,7 @@ The following examples unpacks a remote zip using `fileManager.unzipItem()` whic
Consider using a safer module, such as: ZIPArchive
-
+
diff --git a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
index c50cc6c3b4f9..e455a1b2d16c 100644
--- a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
+++ b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
@@ -1,11 +1,11 @@
/**
* @name Arbitrary file write during a zip extraction from a user controlled source
- * @description Unpacking user controlled zips without validating if destination path file
- * is within the destination directory can cause files outside
- * the destination directory to be overwritten.
+ * @description Unpacking user controlled zips without validating whether the
+ * destination file path is within the destination directory can cause files
+ * outside the destination directory to be overwritten.
* @kind path-problem
* @problem.severity error
- * @security-severity 9.8
+ * @security-severity 7.5
* @precision high
* @id swift/unsafe-unpacking
* @tags security
From 7fb72ea81f4397063530b519f6f99e2844719703 Mon Sep 17 00:00:00 2001
From: Maiky <76447395+maikypedia@users.noreply.github.com>
Date: Wed, 7 Feb 2024 14:30:16 +0100
Subject: [PATCH 053/155] Redundant import
---
swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql | 1 -
1 file changed, 1 deletion(-)
diff --git a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
index e455a1b2d16c..be7f4cfd0844 100644
--- a/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
+++ b/swift/ql/src/experimental/Security/CWE-022/UnsafeUnpack.ql
@@ -14,7 +14,6 @@
*/
import swift
-import codeql.swift.dataflow.DataFlow
import codeql.swift.security.UnsafeUnpackQuery
import UnsafeUnpackFlow::PathGraph
From 7c0f80ff7d4d01908c3a20d30154a61a46cd898b Mon Sep 17 00:00:00 2001
From: Maiky <76447395+maikypedia@users.noreply.github.com>
Date: Wed, 7 Feb 2024 14:32:42 +0100
Subject: [PATCH 054/155] Apply suggestions from code review
Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
---
.../Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift
index 2f599b891502..5d7dc6c58b44 100644
--- a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift
+++ b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.swift
@@ -52,15 +52,15 @@ extension String {
// --- tests ---
func testCommandInjectionQhelpExamples() {
- guard let remoteURL = URL(string: "https://example.com/") else {
- return
- }
+ guard let remoteURL = URL(string: "https://example.com/") else {
+ return
+ }
let source = URL(fileURLWithPath: "/sourcePath")
let destination = URL(fileURLWithPath: "/destination")
try Data(contentsOf: remoteURL, options: []).write(to: source)
- do {
+ do {
try Zip.unzipFile(source, destination: destination, overwrite: true, password: nil) // BAD
let fileManager = FileManager()
From 1a499cf388d695cd70a93eb4dfa4da0c129903a0 Mon Sep 17 00:00:00 2001
From: maikypedia
Date: Wed, 7 Feb 2024 14:38:21 +0100
Subject: [PATCH 055/155] Update `expected`
---
.../UnsafeUnpack.expected | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected
index 4e79ee8b503e..09fc20545b00 100644
--- a/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected
+++ b/swift/ql/test/query-tests/Security/CWE-022-Unsafe-Unpack/UnsafeUnpack.expected
@@ -1,13 +1,13 @@
edges
-| UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:60:60:60:60 | source |
-| UnsafeUnpack.swift:60:60:60:60 | source | UnsafeUnpack.swift:62:27:62:27 | source |
-| UnsafeUnpack.swift:60:60:60:60 | source | UnsafeUnpack.swift:65:39:65:39 | source |
+| UnsafeUnpack.swift:62:9:62:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:62:60:62:60 | source |
+| UnsafeUnpack.swift:62:60:62:60 | source | UnsafeUnpack.swift:64:27:64:27 | source |
+| UnsafeUnpack.swift:62:60:62:60 | source | UnsafeUnpack.swift:67:39:67:39 | source |
nodes
-| UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | semmle.label | call to Data.init(contentsOf:options:) |
-| UnsafeUnpack.swift:60:60:60:60 | source | semmle.label | source |
-| UnsafeUnpack.swift:62:27:62:27 | source | semmle.label | source |
-| UnsafeUnpack.swift:65:39:65:39 | source | semmle.label | source |
+| UnsafeUnpack.swift:62:9:62:48 | call to Data.init(contentsOf:options:) | semmle.label | call to Data.init(contentsOf:options:) |
+| UnsafeUnpack.swift:62:60:62:60 | source | semmle.label | source |
+| UnsafeUnpack.swift:64:27:64:27 | source | semmle.label | source |
+| UnsafeUnpack.swift:67:39:67:39 | source | semmle.label | source |
subpaths
#select
-| UnsafeUnpack.swift:62:27:62:27 | source | UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:62:27:62:27 | source | Unsafe unpacking from a malicious zip retrieved from a remote location. |
-| UnsafeUnpack.swift:65:39:65:39 | source | UnsafeUnpack.swift:60:9:60:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:65:39:65:39 | source | Unsafe unpacking from a malicious zip retrieved from a remote location. |
+| UnsafeUnpack.swift:64:27:64:27 | source | UnsafeUnpack.swift:62:9:62:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:64:27:64:27 | source | Unsafe unpacking from a malicious zip retrieved from a remote location. |
+| UnsafeUnpack.swift:67:39:67:39 | source | UnsafeUnpack.swift:62:9:62:48 | call to Data.init(contentsOf:options:) | UnsafeUnpack.swift:67:39:67:39 | source | Unsafe unpacking from a malicious zip retrieved from a remote location. |
From ed052ccc2607607a352f29540a316e71dfa45b71 Mon Sep 17 00:00:00 2001
From: maikypedia
Date: Wed, 7 Feb 2024 15:58:10 +0100
Subject: [PATCH 056/155] Change note
---
swift/ql/src/change-notes/2024-02-07-unsafe-unpacking.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 swift/ql/src/change-notes/2024-02-07-unsafe-unpacking.md
diff --git a/swift/ql/src/change-notes/2024-02-07-unsafe-unpacking.md b/swift/ql/src/change-notes/2024-02-07-unsafe-unpacking.md
new file mode 100644
index 000000000000..e3c6f79bc480
--- /dev/null
+++ b/swift/ql/src/change-notes/2024-02-07-unsafe-unpacking.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* Added a new query, `swift/unsafe-unpacking`, that detects unpacking user controlled zips without validating the destination file path is within the destination directory.
\ No newline at end of file
From 1c7e6e769beb0b7ecb160853d11f3256a3a43e87 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 7 Feb 2024 15:51:11 +0100
Subject: [PATCH 057/155] C#: Try resolve relative paths in line mappings
---
.../Entities/NonGeneratedSourceLocation.cs | 25 ++++++++++++++++++-
.../LineOrSpanDirective.cs | 6 +++--
.../PragmaChecksumDirective.cs | 3 ++-
...s_TestArea_Views_Shared_Test18.cshtml.g.cs | 6 ++---
...as_TestArea_Views_Test4_Test17.cshtml.g.cs | 6 ++---
.../MyAreas_Test4_Test22.cshtml.g.cs | 6 ++---
.../Generated/Pages_Shared_Test21.cshtml.g.cs | 6 ++---
.../XSSRazorPages/Generated/Template.g | 6 ++---
.../Views_Custom2_Test16.cshtml.g.cs | 6 ++---
.../Views_Custom_Test3_Test15.cshtml.g.cs | 6 ++---
.../Generated/Views_Other_Test13.cshtml.g.cs | 6 ++---
.../Generated/Views_Other_Test5.cshtml.g.cs | 6 ++---
.../Generated/Views_Other_Test6.cshtml.g.cs | 6 ++---
.../Generated/Views_Other_Test8.cshtml.g.cs | 6 ++---
.../Generated/Views_Other_Test9.cshtml.g.cs | 6 ++---
.../Generated/Views_Shared_Test12.cshtml.g.cs | 6 ++---
.../Generated/Views_Shared_Test14.cshtml.g.cs | 6 ++---
.../Generated/Views_Shared_Test19.cshtml.g.cs | 6 ++---
.../Generated/Views_Shared_Test2.cshtml.g.cs | 6 ++---
.../Generated/Views_Shared_Test23.cshtml.g.cs | 6 ++---
.../Generated/Views_Shared_Test3.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test1.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test10.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test11.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test12.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test14.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test2.cshtml.g.cs | 6 ++---
.../Generated/Views_Test2_Test3.cshtml.g.cs | 6 ++---
.../Generated/Views_Test4_Test20.cshtml.g.cs | 6 ++---
.../Generated/Views_Test_Test1.cshtml.g.cs | 6 ++---
.../Generated/Views_Test_Test3.cshtml.g.cs | 6 ++---
.../Generated/Views_Test_Test4.cshtml.g.cs | 6 ++---
.../Generated/Views_Test_Test7.cshtml.g.cs | 6 ++---
33 files changed, 120 insertions(+), 94 deletions(-)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs
index e3e3c0c6ae4a..a3b7877af4e7 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs
@@ -1,5 +1,7 @@
+using System;
using System.IO;
using Microsoft.CodeAnalysis;
+using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.Entities
{
@@ -25,7 +27,8 @@ public override void Populate(TextWriter trapFile)
var mapped = Symbol.GetMappedLineSpan();
if (mapped.HasMappedPath && mapped.IsValid)
{
- var mappedLoc = Create(Context, Location.Create(mapped.Path, default, mapped.Span));
+ var path = TryAdjustRelativeMappedFilePath(mapped.Path, Position.Path, Context.Extractor.Logger);
+ var mappedLoc = Create(Context, Location.Create(path, default, mapped.Span));
trapFile.locations_mapped(this, mappedLoc);
}
@@ -61,5 +64,25 @@ private class SourceLocationFactory : CachedEntityFactory new NonGeneratedSourceLocation(cx, init);
}
+
+ public static string TryAdjustRelativeMappedFilePath(string mappedToPath, string mappedFromPath, ILogger logger)
+ {
+ if (!Path.IsPathRooted(mappedToPath))
+ {
+ try
+ {
+ var fullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(mappedFromPath)!, mappedToPath));
+ logger.LogDebug($"Found relative path in line mapping: '{mappedToPath}', interpreting it as '{fullPath}'");
+
+ mappedToPath = fullPath;
+ }
+ catch (Exception e)
+ {
+ logger.LogDebug($"Failed to compute absolute path for relative path in line mapping: '{mappedToPath}': {e}");
+ }
+ }
+
+ return mappedToPath;
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineOrSpanDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineOrSpanDirective.cs
index 9e8c4c557dc7..6d2362203316 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineOrSpanDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineOrSpanDirective.cs
@@ -25,9 +25,11 @@ protected override void PopulatePreprocessor(TextWriter trapFile)
{
trapFile.directive_lines(this, kind);
- if (!string.IsNullOrWhiteSpace(Symbol.File.ValueText))
+ var path = Symbol.File.ValueText;
+ if (!string.IsNullOrWhiteSpace(path))
{
- var file = File.Create(Context, Symbol.File.ValueText);
+ path = NonGeneratedSourceLocation.TryAdjustRelativeMappedFilePath(path, Symbol.SyntaxTree.FilePath, Context.Extractor.Logger);
+ var file = File.Create(Context, path);
trapFile.directive_line_file(this, file);
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
index 7706118cb6f2..3e0c468d85bc 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
@@ -12,7 +12,8 @@ private PragmaChecksumDirective(Context cx, PragmaChecksumDirectiveTriviaSyntax
protected override void PopulatePreprocessor(TextWriter trapFile)
{
- var file = File.Create(Context, Symbol.File.ValueText);
+ var path = NonGeneratedSourceLocation.TryAdjustRelativeMappedFilePath(Symbol.File.ValueText, Symbol.SyntaxTree.FilePath, Context.Extractor.Logger);
+ var file = File.Create(Context, path);
trapFile.pragma_checksums(this, file, Symbol.Guid.ToString(), Symbol.Bytes.ToString());
}
diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSSRazorPages/Generated/Areas_TestArea_Views_Shared_Test18.cshtml.g.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/XSSRazorPages/Generated/Areas_TestArea_Views_Shared_Test18.cshtml.g.cs
index eecb00361d68..52b0510994e6 100644
--- a/csharp/ql/test/query-tests/Security Features/CWE-079/XSSRazorPages/Generated/Areas_TestArea_Views_Shared_Test18.cshtml.g.cs
+++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSSRazorPages/Generated/Areas_TestArea_Views_Shared_Test18.cshtml.g.cs
@@ -24,7 +24,7 @@ public class Areas_TestArea_Views_Shared_Test18 : global::Microsoft.AspNetCore.M
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
-#line 6 "Areas/TestArea/Views/Shared/Test18.cshtml"
+#line 6 "../Areas/TestArea/Views/Shared/Test18.cshtml"
if (Model != null)
{
@@ -33,7 +33,7 @@ public class Areas_TestArea_Views_Shared_Test18 : global::Microsoft.AspNetCore.M
#nullable disable
WriteLiteral("
\n");
#nullable restore
-#line 9 "Views/Test/Test7.cshtml"
+#line 9 "../Views/Test/Test7.cshtml"
}
#line default
From f50dab3d934ddcd3924d45ab4e65acd8882d598b Mon Sep 17 00:00:00 2001
From: Ian Lynagh
Date: Thu, 8 Feb 2024 14:45:47 +0000
Subject: [PATCH 058/155] Kotlin 2: Accept loc changes in
library-tests/interface-delegate
---
.../test-kotlin2/library-tests/interface-delegate/test.expected | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected b/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected
index a5576a6c0837..8fef9ecf8ff2 100644
--- a/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected
+++ b/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected
@@ -1,8 +1,8 @@
fields
| intfDelegate.kt:7:18:9:1 | $$delegate_0 | intfDelegate.kt:7:26:9:1 | |
#select
-| intfDelegate.kt:0:0:0:0 | f | intfDelegate.kt:7:1:10:1 | Concrete |
| intfDelegate.kt:3:3:3:15 | f | intfDelegate.kt:1:1:5:1 | Intf |
| intfDelegate.kt:7:1:10:1 | Concrete | intfDelegate.kt:7:1:10:1 | Concrete |
+| intfDelegate.kt:7:1:10:1 | f | intfDelegate.kt:7:1:10:1 | Concrete |
| intfDelegate.kt:7:26:9:1 | | intfDelegate.kt:7:26:9:1 | new Intf(...) { ... } |
| intfDelegate.kt:8:3:8:28 | f | intfDelegate.kt:7:26:9:1 | new Intf(...) { ... } |
From 78ce857ef2e22d64a37ebf5431b576ac0b2cb53a Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Thu, 8 Feb 2024 15:27:53 +0000
Subject: [PATCH 059/155] C++: Add consistency test and accept consistency
failures.
---
.../dataflow-tests/type-bugs.expected | 119 ++++++++++++++++++
.../dataflow/dataflow-tests/type-bugs.ql | 11 ++
2 files changed, 130 insertions(+)
diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
index c63c723118bc..99e74b0a06b8 100644
--- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
+++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
@@ -1,3 +1,122 @@
astTypeBugs
irTypeBugs
+incorrectBaseType
+| BarrierGuard.cpp:75:15:75:17 | *buf | Expected 'Node.getType()' to be const int, but it was int |
+| clang.cpp:18:8:18:19 | *sourceArray1 | Expected 'Node.getType()' to be const int, but it was int |
+| clang.cpp:22:8:22:20 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| clang.cpp:23:17:23:29 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| clang.cpp:52:8:52:17 | *stackArray | Expected 'Node.getType()' to be const int, but it was int |
+| dispatch.cpp:60:3:60:14 | *globalBottom | Expected 'Node.getType()' to be Top, but it was Top * |
+| dispatch.cpp:61:3:61:14 | *globalMiddle | Expected 'Node.getType()' to be Top, but it was Top * |
+| example.c:19:6:19:6 | *b | Expected 'Node.getType()' to be MyBool, but it was (unnamed class/struct/union) |
+| example.c:26:18:26:24 | *& ... | Expected 'Node.getType()' to be MyCoords, but it was (unnamed class/struct/union) |
+| file://:0:0:0:0 | *this | Expected 'Node.getType()' to be const lambda [] type at line 13, col. 11, but it was decltype([...](...){...}) |
+| flowOut.cpp:50:14:50:15 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| flowOut.cpp:67:21:67:21 | *p | Expected 'Node.getType()' to be const char, but it was char |
+| flowOut.cpp:84:9:84:10 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| flowOut.cpp:101:13:101:14 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| flowOut.cpp:111:34:111:34 | *p | Expected 'Node.getType()' to be const void, but it was void |
+| flowOut.cpp:139:30:139:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
+| flowOut.cpp:154:30:154:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
+| flowOut.cpp:168:3:168:10 | ** ... | Expected 'Node.getType()' to be char, but it was char * |
+| flowOut.cpp:176:30:176:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
+| flowOut.cpp:193:30:193:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
+| lambdas.cpp:14:3:14:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 13, col. 11, but it was decltype([...](...){...}) |
+| lambdas.cpp:15:3:15:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 13, col. 11, but it was decltype([...](...){...}) |
+| lambdas.cpp:21:3:21:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 20, col. 11, but it was decltype([...](...){...}) |
+| lambdas.cpp:22:3:22:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 20, col. 11, but it was decltype([...](...){...}) |
+| lambdas.cpp:23:3:23:14 | *this | Expected 'Node.getType()' to be const lambda [] type at line 20, col. 11, but it was decltype([...](...){...}) |
+| lambdas.cpp:29:3:29:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 28, col. 11, but it was decltype([...](...){...}) |
+| lambdas.cpp:30:3:30:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 28, col. 11, but it was decltype([...](...){...}) |
+| self_parameter_flow.cpp:8:8:8:9 | *& ... | Expected 'Node.getType()' to be unsigned char, but it was unsigned char * |
+| test.cpp:67:28:67:37 | (reference dereference) | Expected 'Node.getType()' to be const int, but it was int * |
+| test.cpp:67:28:67:37 | *call to move | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:70:19:70:33 | *x3 | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:71:8:71:9 | *x4 | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:384:16:384:23 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
+| test.cpp:391:16:391:23 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
+| test.cpp:400:16:400:22 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
+| test.cpp:407:16:407:22 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
+| test.cpp:526:3:526:4 | ** ... | Expected 'Node.getType()' to be const int *, but it was int * |
+| test.cpp:526:3:526:4 | ** ... | Expected 'Node.getType()' to be const int, but it was int * |
+| test.cpp:526:8:526:9 | *& ... | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:531:39:531:40 | *& ... | Expected 'Node.getType()' to be const int *, but it was int * |
+| test.cpp:531:39:531:40 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:562:5:562:13 | *globalInt | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:576:5:576:13 | *globalInt | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:584:3:584:3 | *x | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:596:3:596:7 | *access to array | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:615:13:615:21 | *& ... | Expected 'Node.getType()' to be int, but it was void |
+| test.cpp:704:22:704:25 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:715:24:715:25 | *& ... | Expected 'Node.getType()' to be unsigned char, but it was unsigned char * |
+| test.cpp:727:3:727:3 | *p | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:797:31:797:39 | *content | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:808:5:808:21 | ** ... | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:832:5:832:17 | *global_direct | Expected 'Node.getType()' to be int *, but it was int ** |
+| test.cpp:848:23:848:25 | (reference dereference) | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:854:10:854:36 | * ... | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:860:54:860:59 | *call to source | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:861:10:861:37 | *static_local_pointer_dynamic | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:867:10:867:30 | * ... | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:872:46:872:51 | *call to source | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:875:10:875:31 | *global_pointer_dynamic | Expected 'Node.getType()' to be const int, but it was int |
+| test.cpp:882:10:882:34 | *static_local_array_static | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:883:10:883:45 | *static_local_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:884:19:884:54 | *static_local_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:885:10:885:45 | *static_local_array_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:886:19:886:54 | *static_local_array_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:890:54:890:61 | *source | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:891:65:891:84 | *indirect_source(1) | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:892:65:892:84 | *indirect_source(2) | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:893:10:893:36 | *static_local_pointer_static | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:894:10:894:47 | *static_local_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:895:19:895:56 | *static_local_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:896:10:896:47 | *static_local_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:897:19:897:56 | *static_local_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:905:10:905:28 | *global_array_static | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:907:10:907:39 | *global_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:909:19:909:37 | *global_array_static | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:910:19:910:48 | *global_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:911:19:911:48 | *global_array_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:914:46:914:53 | *source | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:915:57:915:76 | *indirect_source(1) | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:916:57:916:76 | *indirect_source(2) | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:919:10:919:30 | *global_pointer_static | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:920:10:920:41 | *global_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:921:19:921:50 | *global_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:922:10:922:41 | *global_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:923:19:923:50 | *global_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:931:5:931:18 | *global_pointer | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:952:32:952:35 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:959:32:959:35 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:967:33:967:38 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:967:41:967:44 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:975:33:975:38 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:975:41:975:44 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:984:33:984:36 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:984:39:984:40 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:988:5:988:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
+| test.cpp:988:27:988:28 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:988:31:988:34 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:997:33:997:36 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:997:39:997:40 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1001:5:1001:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
+| test.cpp:1001:27:1001:28 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1001:31:1001:34 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1011:34:1011:39 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1011:42:1011:45 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1011:48:1011:49 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1015:5:1015:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
+| test.cpp:1015:28:1015:33 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1015:36:1015:37 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1015:40:1015:43 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1025:34:1025:39 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1025:42:1025:45 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1025:48:1025:49 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1029:5:1029:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
+| test.cpp:1029:28:1029:33 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1029:36:1029:37 | *np | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1029:40:1029:43 | *data | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1036:33:1036:38 | *domain | Expected 'Node.getType()' to be const char, but it was char |
+| test.cpp:1036:41:1036:47 | *0 | Expected 'Node.getType()' to be const char, but it was char |
failures
diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.ql b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.ql
index 5ff9204f3057..b246f392a8d3 100644
--- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.ql
+++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.ql
@@ -25,6 +25,17 @@ module IrTest {
n != 1
)
}
+
+ query predicate incorrectBaseType(Node n, string msg) {
+ exists(PointerType pointerType, Type nodeType, Type baseType |
+ not n.isGLValue() and
+ pointerType = n.asIndirectExpr(1).getActualType() and
+ baseType = pointerType.getBaseType() and
+ nodeType = n.getType() and
+ nodeType != baseType and
+ msg = "Expected 'Node.getType()' to be " + baseType + ", but it was " + nodeType
+ )
+ }
}
import IrTest
From 4d01a931079911ccb7763f8aa0b2b252b4ed86bf Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Thu, 8 Feb 2024 16:49:15 +0000
Subject: [PATCH 060/155] C++: Use 'getUnderlyingType' instead of
'getUnspecifiedType'.
---
.../cpp/ir/dataflow/internal/DataFlowUtil.qll | 25 ++---
.../cpp/ir/dataflow/internal/SsaInternals.qll | 19 +++-
.../dataflow-tests/type-bugs.expected | 92 +------------------
3 files changed, 33 insertions(+), 103 deletions(-)
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index 1af95d1bd698..003155fd4517 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -709,7 +709,7 @@ class FinalGlobalValue extends Node, TFinalGlobalValue {
override DataFlowType getType() {
exists(int indirectionIndex |
indirectionIndex = globalUse.getIndirectionIndex() and
- result = getTypeImpl(globalUse.getUnspecifiedType(), indirectionIndex - 1)
+ result = getTypeImpl(globalUse.getUnderlyingType(), indirectionIndex - 1)
)
}
@@ -740,7 +740,7 @@ class InitialGlobalValue extends Node, TInitialGlobalValue {
override DataFlowType getType() {
exists(DataFlowType type |
- type = globalDef.getUnspecifiedType() and
+ type = globalDef.getUnderlyingType() and
if this.isGLValue()
then result = type
else result = getTypeImpl(type, globalDef.getIndirectionIndex() - 1)
@@ -942,11 +942,14 @@ private Type getTypeImpl0(Type t, int indirectionIndex) {
or
indirectionIndex > 0 and
exists(Type stripped |
- stripped = stripPointer(t.stripTopLevelSpecifiers()) and
- // We need to avoid the case where `stripPointer(t) = t` (which can happen on
- // iterators that specify a `value_type` that is the iterator itself). Such a type
- // would create an infinite loop otherwise. For these cases we simply don't produce
- // a result for `getTypeImpl`.
+ stripped = stripPointer(t) and
+ // We need to avoid the case where `stripPointer(t) = t` (which can happen
+ // on iterators that specify a `value_type` that is the iterator itself).
+ // Such a type would create an infinite loop otherwise. For these cases we
+ // simply don't produce a result for `getTypeImpl`.
+ // To be on the safe side, we check whether the _unspecified_ type has
+ // changed since this also prevents an infinite loop for occuring when
+ // `stripped` and `t` only differ by const'ness or volatile'ness.
stripped.getUnspecifiedType() != t.getUnspecifiedType() and
result = getTypeImpl0(stripped, indirectionIndex - 1)
)
@@ -1001,7 +1004,7 @@ private module RawIndirectNodes {
type = getOperandType(this.getOperand(), isGLValue) and
if isGLValue = true then sub = 1 else sub = 0
|
- result = getTypeImpl(type.getUnspecifiedType(), indirectionIndex - sub)
+ result = getTypeImpl(type.getUnderlyingType(), indirectionIndex - sub)
)
}
@@ -1043,7 +1046,7 @@ private module RawIndirectNodes {
type = getInstructionType(this.getInstruction(), isGLValue) and
if isGLValue = true then sub = 1 else sub = 0
|
- result = getTypeImpl(type.getUnspecifiedType(), indirectionIndex - sub)
+ result = getTypeImpl(type.getUnderlyingType(), indirectionIndex - sub)
)
}
@@ -1136,7 +1139,7 @@ class FinalParameterNode extends Node, TFinalParameterNode {
override Declaration getEnclosingCallable() { result = this.getFunction() }
- override DataFlowType getType() { result = getTypeImpl(p.getUnspecifiedType(), indirectionIndex) }
+ override DataFlowType getType() { result = getTypeImpl(p.getUnderlyingType(), indirectionIndex) }
final override Location getLocationImpl() {
// Parameters can have multiple locations. When there's a unique location we use
@@ -1789,7 +1792,7 @@ class VariableNode extends Node, TVariableNode {
}
override DataFlowType getType() {
- result = getTypeImpl(v.getUnspecifiedType(), indirectionIndex - 1)
+ result = getTypeImpl(v.getUnderlyingType(), indirectionIndex - 1)
}
final override Location getLocationImpl() {
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
index 3e7cfbe9e114..7c2d92fee99b 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
@@ -548,6 +548,11 @@ class GlobalUse extends UseImpl, TGlobalUse {
*/
Type getUnspecifiedType() { result = global.getUnspecifiedType() }
+ /**
+ * Gets the type of this use, after typedefs have been resolved.
+ */
+ Type getUnderlyingType() { result = global.getUnderlyingType() }
+
override predicate isCertain() { any() }
override BaseSourceVariableInstruction getBase() { none() }
@@ -591,11 +596,16 @@ class GlobalDefImpl extends DefOrUseImpl, TGlobalDefImpl {
int getIndirection() { result = indirectionIndex }
/**
- * Gets the type of this use after specifiers have been deeply stripped
- * and typedefs have been resolved.
+ * Gets the type of this definition after specifiers have been deeply
+ * stripped and typedefs have been resolved.
*/
Type getUnspecifiedType() { result = global.getUnspecifiedType() }
+ /**
+ * Gets the type of this definition, after typedefs have been resolved.
+ */
+ Type getUnderlyingType() { result = global.getUnderlyingType() }
+
override string toString() { result = "Def of " + this.getSourceVariable() }
override Location getLocation() { result = f.getLocation() }
@@ -1115,6 +1125,11 @@ class GlobalDef extends TGlobalDef, SsaDefOrUse {
*/
DataFlowType getUnspecifiedType() { result = global.getUnspecifiedType() }
+ /**
+ * Gets the type of this definition, after typedefs have been resolved.
+ */
+ DataFlowType getUnderlyingType() { result = global.getUnderlyingType() }
+
/** Gets the `IRFunction` whose body is evaluated after this definition. */
IRFunction getIRFunction() { result = global.getIRFunction() }
diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
index 99e74b0a06b8..29457971989f 100644
--- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
+++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
@@ -1,47 +1,18 @@
astTypeBugs
irTypeBugs
incorrectBaseType
-| BarrierGuard.cpp:75:15:75:17 | *buf | Expected 'Node.getType()' to be const int, but it was int |
-| clang.cpp:18:8:18:19 | *sourceArray1 | Expected 'Node.getType()' to be const int, but it was int |
| clang.cpp:22:8:22:20 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| clang.cpp:23:17:23:29 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
-| clang.cpp:52:8:52:17 | *stackArray | Expected 'Node.getType()' to be const int, but it was int |
| dispatch.cpp:60:3:60:14 | *globalBottom | Expected 'Node.getType()' to be Top, but it was Top * |
| dispatch.cpp:61:3:61:14 | *globalMiddle | Expected 'Node.getType()' to be Top, but it was Top * |
-| example.c:19:6:19:6 | *b | Expected 'Node.getType()' to be MyBool, but it was (unnamed class/struct/union) |
-| example.c:26:18:26:24 | *& ... | Expected 'Node.getType()' to be MyCoords, but it was (unnamed class/struct/union) |
-| file://:0:0:0:0 | *this | Expected 'Node.getType()' to be const lambda [] type at line 13, col. 11, but it was decltype([...](...){...}) |
| flowOut.cpp:50:14:50:15 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
-| flowOut.cpp:67:21:67:21 | *p | Expected 'Node.getType()' to be const char, but it was char |
| flowOut.cpp:84:9:84:10 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| flowOut.cpp:101:13:101:14 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
-| flowOut.cpp:111:34:111:34 | *p | Expected 'Node.getType()' to be const void, but it was void |
-| flowOut.cpp:139:30:139:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
-| flowOut.cpp:154:30:154:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
| flowOut.cpp:168:3:168:10 | ** ... | Expected 'Node.getType()' to be char, but it was char * |
-| flowOut.cpp:176:30:176:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
-| flowOut.cpp:193:30:193:30 | *p | Expected 'Node.getType()' to be const char *, but it was char * |
-| lambdas.cpp:14:3:14:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 13, col. 11, but it was decltype([...](...){...}) |
-| lambdas.cpp:15:3:15:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 13, col. 11, but it was decltype([...](...){...}) |
-| lambdas.cpp:21:3:21:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 20, col. 11, but it was decltype([...](...){...}) |
-| lambdas.cpp:22:3:22:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 20, col. 11, but it was decltype([...](...){...}) |
-| lambdas.cpp:23:3:23:14 | *this | Expected 'Node.getType()' to be const lambda [] type at line 20, col. 11, but it was decltype([...](...){...}) |
-| lambdas.cpp:29:3:29:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 28, col. 11, but it was decltype([...](...){...}) |
-| lambdas.cpp:30:3:30:6 | *this | Expected 'Node.getType()' to be const lambda [] type at line 28, col. 11, but it was decltype([...](...){...}) |
| self_parameter_flow.cpp:8:8:8:9 | *& ... | Expected 'Node.getType()' to be unsigned char, but it was unsigned char * |
| test.cpp:67:28:67:37 | (reference dereference) | Expected 'Node.getType()' to be const int, but it was int * |
-| test.cpp:67:28:67:37 | *call to move | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:70:19:70:33 | *x3 | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:71:8:71:9 | *x4 | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:384:16:384:23 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
-| test.cpp:391:16:391:23 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
-| test.cpp:400:16:400:22 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
-| test.cpp:407:16:407:22 | *& ... | Expected 'Node.getType()' to be const void, but it was void |
-| test.cpp:526:3:526:4 | ** ... | Expected 'Node.getType()' to be const int *, but it was int * |
-| test.cpp:526:3:526:4 | ** ... | Expected 'Node.getType()' to be const int, but it was int * |
-| test.cpp:526:8:526:9 | *& ... | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:531:39:531:40 | *& ... | Expected 'Node.getType()' to be const int *, but it was int * |
-| test.cpp:531:39:531:40 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
+| test.cpp:526:3:526:4 | ** ... | Expected 'Node.getType()' to be const int, but it was const int * |
+| test.cpp:531:39:531:40 | *& ... | Expected 'Node.getType()' to be int, but it was const int * |
| test.cpp:562:5:562:13 | *globalInt | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:576:5:576:13 | *globalInt | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:584:3:584:3 | *x | Expected 'Node.getType()' to be int, but it was int * |
@@ -50,73 +21,14 @@ incorrectBaseType
| test.cpp:704:22:704:25 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:715:24:715:25 | *& ... | Expected 'Node.getType()' to be unsigned char, but it was unsigned char * |
| test.cpp:727:3:727:3 | *p | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:797:31:797:39 | *content | Expected 'Node.getType()' to be const int, but it was int |
| test.cpp:808:5:808:21 | ** ... | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:832:5:832:17 | *global_direct | Expected 'Node.getType()' to be int *, but it was int ** |
| test.cpp:848:23:848:25 | (reference dereference) | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:854:10:854:36 | * ... | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:860:54:860:59 | *call to source | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:861:10:861:37 | *static_local_pointer_dynamic | Expected 'Node.getType()' to be const int, but it was int |
| test.cpp:867:10:867:30 | * ... | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:872:46:872:51 | *call to source | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:875:10:875:31 | *global_pointer_dynamic | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:882:10:882:34 | *static_local_array_static | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:883:10:883:45 | *static_local_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:884:19:884:54 | *static_local_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:885:10:885:45 | *static_local_array_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:886:19:886:54 | *static_local_array_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:890:54:890:61 | *source | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:891:65:891:84 | *indirect_source(1) | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:892:65:892:84 | *indirect_source(2) | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:893:10:893:36 | *static_local_pointer_static | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:894:10:894:47 | *static_local_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:895:19:895:56 | *static_local_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:896:10:896:47 | *static_local_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:897:19:897:56 | *static_local_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:905:10:905:28 | *global_array_static | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:907:10:907:39 | *global_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:909:19:909:37 | *global_array_static | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:910:19:910:48 | *global_array_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:911:19:911:48 | *global_array_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:914:46:914:53 | *source | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:915:57:915:76 | *indirect_source(1) | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:916:57:916:76 | *indirect_source(2) | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:919:10:919:30 | *global_pointer_static | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:920:10:920:41 | *global_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:921:19:921:50 | *global_pointer_static_indirect_1 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:922:10:922:41 | *global_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:923:19:923:50 | *global_pointer_static_indirect_2 | Expected 'Node.getType()' to be const char, but it was char |
| test.cpp:931:5:931:18 | *global_pointer | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:952:32:952:35 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:959:32:959:35 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:967:33:967:38 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:967:41:967:44 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:975:33:975:38 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:975:41:975:44 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:984:33:984:36 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:984:39:984:40 | *np | Expected 'Node.getType()' to be const char, but it was char |
| test.cpp:988:5:988:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:988:27:988:28 | *np | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:988:31:988:34 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:997:33:997:36 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:997:39:997:40 | *np | Expected 'Node.getType()' to be const char, but it was char |
| test.cpp:1001:5:1001:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:1001:27:1001:28 | *np | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1001:31:1001:34 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1011:34:1011:39 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1011:42:1011:45 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1011:48:1011:49 | *np | Expected 'Node.getType()' to be const char, but it was char |
| test.cpp:1015:5:1015:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:1015:28:1015:33 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1015:36:1015:37 | *np | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1015:40:1015:43 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1025:34:1025:39 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1025:42:1025:45 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1025:48:1025:49 | *np | Expected 'Node.getType()' to be const char, but it was char |
| test.cpp:1029:5:1029:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:1029:28:1029:33 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1029:36:1029:37 | *np | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1029:40:1029:43 | *data | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1036:33:1036:38 | *domain | Expected 'Node.getType()' to be const char, but it was char |
-| test.cpp:1036:41:1036:47 | *0 | Expected 'Node.getType()' to be const char, but it was char |
failures
From 1dfddaf9ab8370e9df28db554034b2d62586151a Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Thu, 8 Feb 2024 16:52:09 +0000
Subject: [PATCH 061/155] C++: Also mark indirections of glvalue instructions
as glvalue nodes.
---
.../cpp/ir/dataflow/internal/DataFlowUtil.qll | 4 ++++
.../dataflow/dataflow-tests/type-bugs.expected | 16 ----------------
2 files changed, 4 insertions(+), 16 deletions(-)
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index 003155fd4517..32998d2e9be1 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -999,6 +999,8 @@ private module RawIndirectNodes {
override Declaration getEnclosingCallable() { result = this.getFunction() }
+ override predicate isGLValue() { this.getOperand().isGLValue() }
+
override DataFlowType getType() {
exists(int sub, DataFlowType type, boolean isGLValue |
type = getOperandType(this.getOperand(), isGLValue) and
@@ -1041,6 +1043,8 @@ private module RawIndirectNodes {
override Declaration getEnclosingCallable() { result = this.getFunction() }
+ override predicate isGLValue() { this.getInstruction().isGLValue() }
+
override DataFlowType getType() {
exists(int sub, DataFlowType type, boolean isGLValue |
type = getInstructionType(this.getInstruction(), isGLValue) and
diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
index 29457971989f..6706d79e902b 100644
--- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
+++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/type-bugs.expected
@@ -3,32 +3,16 @@ irTypeBugs
incorrectBaseType
| clang.cpp:22:8:22:20 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| clang.cpp:23:17:23:29 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
-| dispatch.cpp:60:3:60:14 | *globalBottom | Expected 'Node.getType()' to be Top, but it was Top * |
-| dispatch.cpp:61:3:61:14 | *globalMiddle | Expected 'Node.getType()' to be Top, but it was Top * |
| flowOut.cpp:50:14:50:15 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| flowOut.cpp:84:9:84:10 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| flowOut.cpp:101:13:101:14 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
-| flowOut.cpp:168:3:168:10 | ** ... | Expected 'Node.getType()' to be char, but it was char * |
| self_parameter_flow.cpp:8:8:8:9 | *& ... | Expected 'Node.getType()' to be unsigned char, but it was unsigned char * |
| test.cpp:67:28:67:37 | (reference dereference) | Expected 'Node.getType()' to be const int, but it was int * |
-| test.cpp:526:3:526:4 | ** ... | Expected 'Node.getType()' to be const int, but it was const int * |
| test.cpp:531:39:531:40 | *& ... | Expected 'Node.getType()' to be int, but it was const int * |
-| test.cpp:562:5:562:13 | *globalInt | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:576:5:576:13 | *globalInt | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:584:3:584:3 | *x | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:596:3:596:7 | *access to array | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:615:13:615:21 | *& ... | Expected 'Node.getType()' to be int, but it was void |
| test.cpp:704:22:704:25 | *& ... | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:715:24:715:25 | *& ... | Expected 'Node.getType()' to be unsigned char, but it was unsigned char * |
-| test.cpp:727:3:727:3 | *p | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:808:5:808:21 | ** ... | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:832:5:832:17 | *global_direct | Expected 'Node.getType()' to be int *, but it was int ** |
| test.cpp:848:23:848:25 | (reference dereference) | Expected 'Node.getType()' to be int, but it was int * |
| test.cpp:854:10:854:36 | * ... | Expected 'Node.getType()' to be const int, but it was int |
| test.cpp:867:10:867:30 | * ... | Expected 'Node.getType()' to be const int, but it was int |
-| test.cpp:931:5:931:18 | *global_pointer | Expected 'Node.getType()' to be int, but it was int * |
-| test.cpp:988:5:988:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:1001:5:1001:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:1015:5:1015:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
-| test.cpp:1029:5:1029:14 | *translated | Expected 'Node.getType()' to be char, but it was char * |
failures
From f7d1544ccff78cfb7dfb5c6b57435b3b95f4bda9 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Thu, 8 Feb 2024 17:01:07 +0000
Subject: [PATCH 062/155] C++: Fix Code Scanning errors.
---
.../lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index 32998d2e9be1..e49d519061f5 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -948,8 +948,8 @@ private Type getTypeImpl0(Type t, int indirectionIndex) {
// Such a type would create an infinite loop otherwise. For these cases we
// simply don't produce a result for `getTypeImpl`.
// To be on the safe side, we check whether the _unspecified_ type has
- // changed since this also prevents an infinite loop for occuring when
- // `stripped` and `t` only differ by const'ness or volatile'ness.
+ // changed since this also prevents an infinite loop when `stripped` and
+ // `t` only differ by const'ness or volatile'ness.
stripped.getUnspecifiedType() != t.getUnspecifiedType() and
result = getTypeImpl0(stripped, indirectionIndex - 1)
)
From 2852f09a1acb0e1bcbb29ce4204ea219f4e77b5e Mon Sep 17 00:00:00 2001
From: Ian Lynagh
Date: Thu, 8 Feb 2024 17:44:38 +0000
Subject: [PATCH 063/155] Kotlin: Accept test changes in
library-tests/java-kotlin-collection-type-generic-methods
I'm not sure exactly what's going on here in general, but I've made a
ticket to remind us to come back and look at this whole area.
---
.../test.expected | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/java/ql/test-kotlin2/library-tests/java-kotlin-collection-type-generic-methods/test.expected b/java/ql/test-kotlin2/library-tests/java-kotlin-collection-type-generic-methods/test.expected
index 4ccb82a3d0dd..0fe94ff8a25a 100644
--- a/java/ql/test-kotlin2/library-tests/java-kotlin-collection-type-generic-methods/test.expected
+++ b/java/ql/test-kotlin2/library-tests/java-kotlin-collection-type-generic-methods/test.expected
@@ -54,7 +54,7 @@ methodWithDuplicate
| AbstractList | set | int |
| AbstractList | subList | int |
| AbstractList | subListRangeCheck | int |
-| AbstractMap | containsEntry$kotlin_stdlib | Entry,?> |
+| AbstractMap | containsEntry$main | Entry,?> |
| AbstractMap | containsKey | Object |
| AbstractMap | containsValue | Object |
| AbstractMap | equals | Object |
@@ -79,7 +79,7 @@ methodWithDuplicate
| AbstractMap | put | V |
| AbstractMap | putAll | Map extends K,? extends V> |
| AbstractMap | remove | Object |
-| AbstractMap | containsEntry$kotlin_stdlib | Entry,?> |
+| AbstractMap | containsEntry$main | Entry,?> |
| AbstractMap | containsKey | Object |
| AbstractMap | containsValue | Object |
| AbstractMap | equals | Object |
@@ -121,7 +121,6 @@ methodWithDuplicate
| Collection> | addAll | Collection extends Entry> |
| Collection> | contains | Object |
| Collection> | containsAll | Collection> |
-| Collection> | equals | Object |
| Collection> | remove | Object |
| Collection> | removeAll | Collection> |
| Collection> | removeIf | Predicate super Entry> |
@@ -132,7 +131,6 @@ methodWithDuplicate
| Collection | addAll | Collection extends K> |
| Collection | contains | Object |
| Collection | containsAll | Collection> |
-| Collection | equals | Object |
| Collection | remove | Object |
| Collection | removeAll | Collection> |
| Collection | removeIf | Predicate super K> |
@@ -154,7 +152,6 @@ methodWithDuplicate
| Collection | addAll | Collection extends V> |
| Collection | contains | Object |
| Collection | containsAll | Collection> |
-| Collection | equals | Object |
| Collection | remove | Object |
| Collection | removeAll | Collection> |
| Collection | removeIf | Predicate super V> |
@@ -194,7 +191,6 @@ methodWithDuplicate
| List | contains | Object |
| List | containsAll | Collection> |
| List | copyOf | Collection extends E> |
-| List | equals | Object |
| List | get | int |
| List | indexOf | Object |
| List | lastIndexOf | Object |
@@ -279,7 +275,6 @@ methodWithDuplicate
| Map> | copyOf | Map extends K,? extends V> |
| Map> | entry | K |
| Map> | entry | V |
-| Map> | equals | Object |
| Map> | forEach | BiConsumer super Identity,? super Entry>> |
| Map> | get | Object |
| Map> | getOrDefault | Entry> |
@@ -310,7 +305,6 @@ methodWithDuplicate
| Map | copyOf | Map extends K,? extends V> |
| Map | entry | K |
| Map | entry | V |
-| Map | equals | Object |
| Map | forEach | BiConsumer super K,? super V> |
| Map | get | Object |
| Map | getOrDefault | Object |
@@ -341,7 +335,6 @@ methodWithDuplicate
| Map