From ddc544aa0741ad600ae0389e1987841e48878951 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Fri, 18 Sep 2020 16:54:11 +0530 Subject: [PATCH 001/757] Initial support for Play Framework > 2.6.x --- .../semmle/code/java/dataflow/FlowSources.qll | 22 + .../semmle/code/java/frameworks/play/Play.qll | 9 + .../java/frameworks/play/PlayAddCSRFToken.qll | 13 + .../java/frameworks/play/PlayAsyncResult.qll | 30 + .../java/frameworks/play/PlayBodyParser.qll | 11 + .../java/frameworks/play/PlayController.qll | 55 + .../frameworks/play/PlayHTTPRequestHeader.qll | 20 + .../java/frameworks/play/PlayMVCResult.qll | 11 + .../java/frameworks/play/PlayMVCResults.qll | 35 + .../dataflow/taintsources/PlayResource.java | 14 + .../dataflow/taintsources/options | 2 +- .../dataflow/taintsources/remote.expected | 4 + .../akka-2.6.x/akka/util/ByteString.java | 6 + .../fasterxml/jackson/core/JsonEncoding.java | 3 + .../fasterxml/jackson/databind/JsonNode.java | 6 + .../playframework-2.6.x/play/Application.java | 88 + .../stubs/playframework-2.6.x/play/Play.java | 20 + .../play/api/mvc/Request.java | 6 + .../play/api/mvc/RequestHeader.java | 6 + .../play/api/mvc/StatusHeader.java | 6 + .../play/core/j/JavaContextComponents.java | 6 + .../play/core/j/RequestImpl.java | 6 + .../play/filters/csrf/AddCSRFToken.java | 11 + .../play/http/HttpEntity.java | 6 + .../playframework-2.6.x/play/i18n/Lang.java | 18 + .../playframework-2.6.x/play/i18n/Langs.java | 5 + .../play/i18n/Messages.java | 13 + .../play/i18n/MessagesApi.java | 17 + .../playframework-2.6.x/play/libs/F.java | 16 + .../playframework-2.6.x/play/libs/Files.java | 109 + .../playframework-2.6.x/play/libs/XML.java | 6 + .../play/libs/typedmap/TypedKey.java | 16 + .../play/libs/typedmap/TypedMap.java | 25 + .../play/mvc/BodyParser.java | 38 + .../playframework-2.6.x/play/mvc/Call.java | 15 + .../play/mvc/Controller.java | 110 + .../playframework-2.6.x/play/mvc/Http.java | 2074 +++++++++++++++++ .../playframework-2.6.x/play/mvc/Result.java | 10 + .../playframework-2.6.x/play/mvc/Results.java | 1805 ++++++++++++++ .../play/twirl/api/Content.java | 11 + 40 files changed, 4683 insertions(+), 1 deletion(-) create mode 100644 java/ql/src/semmle/code/java/frameworks/play/Play.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayController.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll create mode 100644 java/ql/test/library-tests/dataflow/taintsources/PlayResource.java create mode 100644 java/ql/test/stubs/akka-2.6.x/akka/util/ByteString.java create mode 100644 java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonEncoding.java create mode 100644 java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/Application.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/Play.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/api/mvc/Request.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/api/mvc/RequestHeader.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/api/mvc/StatusHeader.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/core/j/JavaContextComponents.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/core/j/RequestImpl.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/filters/csrf/AddCSRFToken.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/http/HttpEntity.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/i18n/Lang.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/i18n/Langs.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/i18n/Messages.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/i18n/MessagesApi.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/libs/F.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/libs/Files.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/libs/XML.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedKey.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedMap.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/mvc/BodyParser.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/mvc/Call.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/mvc/Controller.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/mvc/Http.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/mvc/Result.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/mvc/Results.java create mode 100644 java/ql/test/stubs/playframework-2.6.x/play/twirl/api/Content.java diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index fb2a0345b8b..33fac24cdbd 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -17,6 +17,8 @@ import semmle.code.java.frameworks.android.WebView import semmle.code.java.frameworks.JaxWS import semmle.code.java.frameworks.javase.WebSocket import semmle.code.java.frameworks.android.Intent +import semmle.code.java.frameworks.play.PlayController +import semmle.code.java.frameworks.play.PlayHTTPRequestHeader import semmle.code.java.frameworks.spring.SpringWeb import semmle.code.java.frameworks.spring.SpringController import semmle.code.java.frameworks.spring.SpringWebClient @@ -122,6 +124,18 @@ private class SpringMultipartRequestSource extends RemoteFlowSource { override string getSourceType() { result = "Spring MultipartRequest getter" } } +class PlayParameterSource extends RemoteFlowSource { + PlayParameterSource() { + exists(PlayActionQueryParameter p | p = this.asParameter()) + or + exists(PlayHTTPRequestHeaderMethods m | + m.hasName("getQueryString") and m.getAParameter() = this.asParameter() + ) + } + + override string getSourceType() { result = "Play Query Parameters" } +} + private class SpringMultipartFileSource extends RemoteFlowSource { SpringMultipartFileSource() { exists(MethodAccess ma, Method m | @@ -245,6 +259,7 @@ private class RemoteTaintedMethod extends Method { this instanceof HttpServletRequestGetRequestURIMethod or this instanceof HttpServletRequestGetRequestURLMethod or this instanceof HttpServletRequestGetRemoteUserMethod or + this instanceof PlayRequestGetMethod or this instanceof SpringWebRequestGetMethod or this instanceof SpringRestTemplateResponseEntityMethod or this instanceof ServletRequestGetBodyMethod or @@ -264,6 +279,13 @@ private class RemoteTaintedMethod extends Method { } } +private class PlayRequestGetMethod extends PlayHTTPRequestHeaderMethods { + PlayRequestGetMethod() { + this.hasName("Header") or + this.hasName("getQueryString") + } +} + private class SpringWebRequestGetMethod extends Method { SpringWebRequestGetMethod() { exists(SpringWebRequest swr | this = swr.getAMethod() | diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll new file mode 100644 index 00000000000..18f6dabf468 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -0,0 +1,9 @@ +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.frameworks.play.PlayController +import semmle.code.java.frameworks.play.PlayAddCSRFToken +import semmle.code.java.frameworks.play.PlayAsyncResult +import semmle.code.java.frameworks.play.PlayBodyParser +import semmle.code.java.frameworks.play.PlayHTTPRequestHeader +import semmle.code.java.frameworks.play.PlayMVCResult +import semmle.code.java.frameworks.play.PlayMVCResults diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll new file mode 100644 index 00000000000..391106aadc0 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll @@ -0,0 +1,13 @@ +import java + +/** + * Play Framework AddCSRFToken + * + * @description Gets the methods using AddCSRFToken annotation. + * (https://www.playframework.com/documentation/2.6.x/JavaBodyParsers#Choosing-an-explicit-body-parser) + */ +class PlayAddCSRFTokenAnnotation extends Annotation { + PlayAddCSRFTokenAnnotation() { + this.getType().hasQualifiedName("play.filters.csrf", "AddCSRFToken") + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll new file mode 100644 index 00000000000..1eb0108f0d3 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll @@ -0,0 +1,30 @@ +import java + +/** + * Play Framework Async Promise of Generic Result + * + * @description Gets the Promise Generic Type of (play.libs.F), This is async in 2.6x and below. + * (https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html) + */ +class PlayAsyncResultPromise extends Member { + PlayAsyncResultPromise() { + exists(Class c | + c.hasQualifiedName("play.libs", "F") and + this = c.getAMember() and + this.getQualifiedName() = "F.Promise" + ) + } +} + +/** + * Play Framework Async Generic Result extending generic promise API called CompletionStage. + * + * @description Gets the CompletionStage Generic Type of (java.util.concurrent) + * (https://www.playframework.com/documentation/2.6.x/JavaAsync) + */ +class PlayAsyncResultCompletionStage extends Type { + PlayAsyncResultCompletionStage() { + this.hasName("CompletionStage") and + this.getCompilationUnit().getPackage().hasName("java.util.concurrent") + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll new file mode 100644 index 00000000000..df16c4d137e --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll @@ -0,0 +1,11 @@ +import java + +/** + * Play Framework Explicit Body Parser + * + * @description Gets the methods using the explicit body parser annotation. The methods are usually controller action methods + * (https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser) + */ +class PlayBodyParserAnnotation extends Annotation { + PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll new file mode 100644 index 00000000000..5592f05ca69 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll @@ -0,0 +1,55 @@ +import java +import semmle.code.java.frameworks.play.PlayAsyncResult +import semmle.code.java.frameworks.play.PlayMVCResult + +/** + * Play MVC Framework Controller + * + * @description Gets the play.mvc.Controller class + */ +class PlayMVCControllerClass extends Class { + PlayMVCControllerClass() { this.hasQualifiedName("play.mvc", "Controller") } +} + +/** + * Play Framework Controller which extends/implements + * + * @description Gets the classes which extends play.mvc.controller rescursively. + */ +class PlayController extends Class { + PlayController() { + exists(Class t | this.extendsOrImplements*(t) and t instanceof PlayMVCControllerClass) + } +} + +/** + * Play Framework Controller Action Methods + * + * @description Gets the controller action methods defined against it. + * (https://www.playframework.com/documentation/2.8.x/JavaActions) + * @tip Checking for Public methods usually retrieves direct controller mapped methods defined in routes. + */ +class PlayControllerActionMethod extends Method { + PlayControllerActionMethod() { + exists(PlayController controller | + this = controller.getAMethod() and + ( + this.getReturnType() instanceof PlayAsyncResultPromise or + this.getReturnType() instanceof PlayMVCResult or + this.getReturnType() instanceof PlayAsyncResultCompletionStage + ) + ) + } +} + +/** + * Play Action-Method parameters, these are essentially part of routes. + */ +class PlayActionQueryParameter extends Parameter { + PlayActionQueryParameter() { + exists(PlayControllerActionMethod a | + a.isPublic() and + this = a.getAParameter() + ) + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll new file mode 100644 index 00000000000..91e06af4802 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll @@ -0,0 +1,20 @@ +import java + +/** + * Play MVC Framework HTTP Request Header + * + * @description Member of play.mvc.HTTP. Gets the play.mvc.HTTP$RequestHeader class/interface + */ +class PlayMVCHTTPRequestHeader extends RefType { + PlayMVCHTTPRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } +} + +/** + * Play Framework HTTP$RequestHeader Methods + * + * @description Gets the methods of play.mvc.HTTP$RequestHeader like - headers, getQueryString, getHeader, uri + * (https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html) + */ +class PlayHTTPRequestHeaderMethods extends Method { + PlayHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll new file mode 100644 index 00000000000..60729bf7041 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll @@ -0,0 +1,11 @@ +import java + +/** + * Play MVC Framework Result + * + * @description Gets the play.mvc.Result class - Used to set a HTTP result with a status code, a set of HTTP headers and a body to be sent to the web client. + * (https://www.playframework.com/documentation/2.8.x/JavaActions) + */ +class PlayMVCResult extends Class { + PlayMVCResult() { this.hasQualifiedName("play.mvc", "Result") } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll new file mode 100644 index 00000000000..1c3d46c3ae4 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll @@ -0,0 +1,35 @@ +import java + +/** + * Play MVC Framework Results + * + * @description Gets the play.mvc.Results class - Helper utilities to generate results + * (https://www.playframework.com/documentation/2.8.x/JavaActions) + */ +class PlayMVCResults extends Class { + PlayMVCResults() { this.hasQualifiedName("play.mvc", "Results") } +} + +/** + * Play Framework mvc.Results Methods + * + * @description Gets the methods of play.mvc.Results like - ok, status, redirect etc. + * (https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html) + */ +class PlayHTTPResultsMethods extends Method { + PlayHTTPResultsMethods() { this.getDeclaringType() instanceof PlayMVCResults } + + /** + * Gets all references to play.mvc.Results ok method + */ + MethodAccess ok() { + exists(MethodAccess ma | ma = this.getAReference() and this.hasName("ok") | result = ma) + } + + /** + * Gets all references to play.mvc.Results redirect method + */ + MethodAccess redirect() { + exists(MethodAccess ma | ma = this.getAReference() and this.hasName("redirect") | result = ma) + } +} diff --git a/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java b/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java new file mode 100644 index 00000000000..02ef657a986 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java @@ -0,0 +1,14 @@ +import play.mvc.Controller; +import play.mvc.Result; +import play.filters.csrf.AddCSRFToken; +import java.util.concurrent.CompletionStage; + + +public class PlayResource extends Controller { + + @AddCSRFToken + public Result play_index(String username, String password) { + String append_token = "password" + password; + ok("Working"); + } +} \ No newline at end of file diff --git a/java/ql/test/library-tests/dataflow/taintsources/options b/java/ql/test/library-tests/dataflow/taintsources/options index 4c9e6d3e443..988fb98ce47 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/options +++ b/java/ql/test/library-tests/dataflow/taintsources/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/springframework-5.2.3 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/springframework-5.2.3:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jackson-databind-2.10:${testdir}/../../../stubs/akka-2.6.x diff --git a/java/ql/test/library-tests/dataflow/taintsources/remote.expected b/java/ql/test/library-tests/dataflow/taintsources/remote.expected index 21403e9f01b..96dd85a27f4 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/remote.expected +++ b/java/ql/test/library-tests/dataflow/taintsources/remote.expected @@ -5,6 +5,10 @@ | A.java:41:5:41:53 | getInputStream(...) | A.java:41:5:41:53 | getInputStream(...) | | A.java:42:5:42:45 | getInputStream(...) | A.java:42:5:42:45 | getInputStream(...) | | A.java:43:5:43:47 | getHostName(...) | A.java:43:5:43:47 | getHostName(...) | +| PlayResource.java:10:30:10:44 | username | PlayResource.java:10:30:10:44 | username | +| PlayResource.java:10:47:10:61 | password | PlayResource.java:10:47:10:61 | password | +| PlayResource.java:10:47:10:61 | password | PlayResource.java:11:31:11:51 | ... + ... | +| PlayResource.java:10:47:10:61 | password | PlayResource.java:11:44:11:51 | password | | RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:4:30:4:40 | path | | RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:5:20:5:31 | ... + ... | | RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:5:28:5:31 | path | diff --git a/java/ql/test/stubs/akka-2.6.x/akka/util/ByteString.java b/java/ql/test/stubs/akka-2.6.x/akka/util/ByteString.java new file mode 100644 index 00000000000..136da250941 --- /dev/null +++ b/java/ql/test/stubs/akka-2.6.x/akka/util/ByteString.java @@ -0,0 +1,6 @@ +package akka.util; + +/** XML utilities. */ +public class ByteString { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonEncoding.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonEncoding.java new file mode 100644 index 00000000000..bc94ae34932 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/core/JsonEncoding.java @@ -0,0 +1,3 @@ +package com.fasterxml.jackson.core; + +public class JsonEncoding {} \ No newline at end of file diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java new file mode 100644 index 00000000000..a26eb2592c6 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java @@ -0,0 +1,6 @@ +package com.fasterxml.jackson.databind; + +public class JsonNode { + public JsonNode() { + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/Application.java b/java/ql/test/stubs/playframework-2.6.x/play/Application.java new file mode 100644 index 00000000000..e86112509a4 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/Application.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package play; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; + +//import play.inject.Injector; -> Scala stuff +//import play.libs.Scala; + +/** + * A Play application. + * + * Application creation is handled by the framework engine. + */ +public interface Application { + + /** + * Get the underlying Scala application. + * + * @ + */ + //play.api.Application getWrappedApplication(); + + /** + * Get the application configuration. + * + * @ + */ + //Configuration configuration(); + + /** + * Get the injector for this application. + * + * @ + */ + //Injector injector(); + + /** + * Get the application path. + * + * @ + */ + default File path() { + } + + /** + * Get the application classloader. + * + * @ + */ + default ClassLoader classloader() { + + } + + /** + * Get a file relative to the application root path. + * + * @param relativePath relative path of the file to fetch + * @ + */ + default File getFile(String relativePath) { + + } + + /** + * Get a resource from the classpath. + * + * @param relativePath relative path of the resource to fetch + * @ + */ + default URL resource(String relativePath) { + + } + + /** + * Get a resource stream from the classpath. + * + * @param relativePath relative path of the resource to fetch + * @ + */ + default InputStream resourceAsStream(String relativePath) { + + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/Play.java b/java/ql/test/stubs/playframework-2.6.x/play/Play.java new file mode 100644 index 00000000000..83a863c9120 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/Play.java @@ -0,0 +1,20 @@ +package play; + +/** + * High-level API to access Play global features. + * + * @deprecated Please use dependency injection. Deprecated since 2.5.0. + */ +@Deprecated +public class Play { + + /** + * @deprecated inject the {@link play.Application} instead. Deprecated since 2.5.0. + * @return Deprecated + */ + @Deprecated + public static Application application() { + } + + //private static play.api.Application privateCurrent() { } +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/Request.java b/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/Request.java new file mode 100644 index 00000000000..f550dcc3706 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/Request.java @@ -0,0 +1,6 @@ +package play.api.mvc; + +/** Scala dummy */ +public class Request { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/RequestHeader.java b/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/RequestHeader.java new file mode 100644 index 00000000000..667a2ee34eb --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/RequestHeader.java @@ -0,0 +1,6 @@ +package play.api.mvc; + +/** Scala dummy */ +public class RequestHeader { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/StatusHeader.java b/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/StatusHeader.java new file mode 100644 index 00000000000..392edb747fe --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/api/mvc/StatusHeader.java @@ -0,0 +1,6 @@ +package play.api.mvc; + +/** XML utilities. */ +public class StatusHeader { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/core/j/JavaContextComponents.java b/java/ql/test/stubs/playframework-2.6.x/play/core/j/JavaContextComponents.java new file mode 100644 index 00000000000..fcb6308cefc --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/core/j/JavaContextComponents.java @@ -0,0 +1,6 @@ +package play.core.j; + +/** XML utilities. */ +public class JavaContextComponents { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/core/j/RequestImpl.java b/java/ql/test/stubs/playframework-2.6.x/play/core/j/RequestImpl.java new file mode 100644 index 00000000000..409f64975b7 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/core/j/RequestImpl.java @@ -0,0 +1,6 @@ +package play.core.j; + +/** Scala dummy */ +public class RequestImpl { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/filters/csrf/AddCSRFToken.java b/java/ql/test/stubs/playframework-2.6.x/play/filters/csrf/AddCSRFToken.java new file mode 100644 index 00000000000..15c16c230ee --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/filters/csrf/AddCSRFToken.java @@ -0,0 +1,11 @@ +package play.filters.csrf; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** This action adds a CSRF token to the request and response if not already there. */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface AddCSRFToken {} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/http/HttpEntity.java b/java/ql/test/stubs/playframework-2.6.x/play/http/HttpEntity.java new file mode 100644 index 00000000000..32deaf88b06 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/http/HttpEntity.java @@ -0,0 +1,6 @@ +package play.http; + +/** XML utilities. */ +public abstract class HttpEntity { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/i18n/Lang.java b/java/ql/test/stubs/playframework-2.6.x/play/i18n/Lang.java new file mode 100644 index 00000000000..db91817b661 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/i18n/Lang.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.i18n; + +import java.util.*; +import java.util.stream.Stream; + +//import play.Application; +//import play.libs.*; +//import scala.collection.immutable.Seq; + +import static java.util.stream.Collectors.toList; + +/** A Lang supported by the application. */ +public class Lang /* extends play.api.i18n.Lang */ { +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/i18n/Langs.java b/java/ql/test/stubs/playframework-2.6.x/play/i18n/Langs.java new file mode 100644 index 00000000000..6aba808dc2b --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/i18n/Langs.java @@ -0,0 +1,5 @@ +package play.i18n; + +/** A Lang supported by the application. */ +public class Langs { +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/i18n/Messages.java b/java/ql/test/stubs/playframework-2.6.x/play/i18n/Messages.java new file mode 100644 index 00000000000..772718de3ed --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/i18n/Messages.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.i18n; + +/** + * A Messages will produce messages using a specific language. + * + *

This interface that is typically backed by MessagesImpl, but does not return MessagesApi. + */ +public interface Messages { +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/i18n/MessagesApi.java b/java/ql/test/stubs/playframework-2.6.x/play/i18n/MessagesApi.java new file mode 100644 index 00000000000..23db65e4c91 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/i18n/MessagesApi.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.i18n; + +//import javax.inject.Singleton; + +/** + * A Messages will produce messages using a specific language. + * + *

This interface that is typically backed by MessagesImpl, but does not return MessagesApi. + */ +//@Singleton +public class MessagesApi { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java b/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java new file mode 100644 index 00000000000..f54ff2b5f5b --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.libs; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.Supplier; + +//import scala.concurrent.ExecutionContext; + +/** Defines a set of functional programming style helpers. */ +public class F { + public static class Promise { } // this is needed for play.libs.F for Play 2.3.x +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/libs/Files.java b/java/ql/test/stubs/playframework-2.6.x/play/libs/Files.java new file mode 100644 index 00000000000..e3cec2fb1c4 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/libs/Files.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.libs; + +//import javax.inject.Inject; +import java.io.File; +import java.nio.file.Path; + +/** Contains TemporaryFile and TemporaryFileCreator operations. */ +public final class Files { + + /** This creates temporary files when Play needs to keep overflow data on the filesystem. */ + public interface TemporaryFileCreator { + TemporaryFile create(String prefix, String suffix); + + TemporaryFile create(Path path); + + boolean delete(TemporaryFile temporaryFile); + + // Needed for RawBuffer compatibility + } + + /** A temporary file created by a TemporaryFileCreator. */ + public interface TemporaryFile { + + /** @return the path to the temporary file. */ + Path path(); + + /** + * @return the temporaryFile as a java.io.File. + * @deprecated Use path() over file(). + */ + @Deprecated + File file(); + + TemporaryFileCreator temporaryFileCreator(); + + default TemporaryFile moveTo(File to) { + } + + TemporaryFile moveTo(File to, boolean replace); + + TemporaryFile atomicMoveWithFallback(File to); + } + + /** A temporary file creator that delegates to a Scala TemporaryFileCreator. */ + public static class DelegateTemporaryFileCreator implements TemporaryFileCreator { + //private final play.api.libs.Files.TemporaryFileCreator temporaryFileCreator; + + //@Inject +/* public DelegateTemporaryFileCreator( + play.api.libs.Files.TemporaryFileCreator temporaryFileCreator) { + *///} + + public TemporaryFile create(String prefix, String suffix) { + } + + public TemporaryFile create(Path path) { + } + + public boolean delete(TemporaryFile temporaryFile) { + } + + //public play.api.libs.Files.TemporaryFileCreator asScala() {} + } + + /** Delegates to the Scala implementation. */ + public static class DelegateTemporaryFile implements TemporaryFile { + + public Path path() { + } + + public File file() { + } + + public TemporaryFileCreator temporaryFileCreator() { + } + + public TemporaryFile moveTo(File to, boolean replace) { + } + + public TemporaryFile atomicMoveWithFallback(File to) { + } + } + + /** + * A temporary file creator that uses the Scala play.api.libs.Files.SingletonTemporaryFileCreator + * class behind the scenes. + */ + public static class SingletonTemporaryFileCreator implements TemporaryFileCreator { + public TemporaryFile create(String prefix, String suffix) { + } + + public TemporaryFile create(Path path) { + } + + public boolean delete(TemporaryFile temporaryFile) { + } + + } + + private static final TemporaryFileCreator instance = new Files.SingletonTemporaryFileCreator(); + + /** @return the singleton instance of SingletonTemporaryFileCreator. */ + public static TemporaryFileCreator singletonTemporaryFileCreator() { + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/libs/XML.java b/java/ql/test/stubs/playframework-2.6.x/play/libs/XML.java new file mode 100644 index 00000000000..d04f4a89cef --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/libs/XML.java @@ -0,0 +1,6 @@ +package play.libs; + +/** XML utilities. */ +public class XML { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedKey.java b/java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedKey.java new file mode 100644 index 00000000000..8e488b22bd0 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedKey.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.libs.typedmap; + +//import play.api.libs.typedmap.TypedKey$; + +/** + * A TypedKey is a key that can be used to get and set values in a {@link TypedMap} or any object + * with typed keys. This class uses reference equality for comparisons, so each new instance is + * different key. + */ +public final class TypedKey { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedMap.java b/java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedMap.java new file mode 100644 index 00000000000..21132c1643a --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/libs/typedmap/TypedMap.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.libs.typedmap; + +import play.libs.typedmap.TypedKey; + +import java.util.Optional; + +/** + * A TypedMap is an immutable map containing typed values. Each entry is associated with a {@link + * TypedKey} that can be used to look up the value. A TypedKey also defines the type of + * the value, e.g. a TypedKey<String> would be associated with a String + * value. + * + *

Instances of this class are created with the {@link #empty()} method. + * + *

The elements inside TypedMaps cannot be enumerated. This is a decision designed to enforce + * modularity. It's not possible to accidentally or intentionally access a value in a TypedMap + * without holding the corresponding {@link TypedKey}. + */ +public final class TypedMap { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/mvc/BodyParser.java b/java/ql/test/stubs/playframework-2.6.x/play/mvc/BodyParser.java new file mode 100644 index 00000000000..81e6a11d888 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/mvc/BodyParser.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.mvc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** A body parser parses the HTTP request body content. */ +public interface BodyParser { + + /** + * Return an accumulator to parse the body of the given HTTP request. + * + *

The accumulator should either produce a result if an error was encountered, or the parsed + * body. + * + * @param request The request to create the body parser for. + * @return The accumulator to parse the body. + */ + + /** Specify the body parser to use for an Action method. */ + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @interface Of { + + /** + * The class of the body parser to use. + * + * @return the class + */ + //Class value(); + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/mvc/Call.java b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Call.java new file mode 100644 index 00000000000..abb26a30ab7 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Call.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2013 Typesafe Inc. + */ +package play.mvc; + +/** + * Any action result. + */ +public class Call { + + /** + * Retrieves the real (Scala-based) result. + */ + // play.api.mvc.Result toScala(); +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/mvc/Controller.java b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Controller.java new file mode 100644 index 00000000000..898c7c48033 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Controller.java @@ -0,0 +1,110 @@ +package play.mvc; + +import play.i18n.Lang; + +import play.mvc.Http.HeaderNames; +import play.mvc.Http.Response; +import play.mvc.Http.Context; +import play.mvc.Http.Request; +import play.mvc.Http.Session; +import play.mvc.Http.Status; +import play.mvc.Http.Flash; + +/** + * Superclass for a Java-based controller. + */ +public abstract class Controller extends Results implements Status, HeaderNames { + + /** + * Returns the current HTTP context. + */ + public static Context ctx() { + + } + + /** + * Returns the current HTTP request. + */ + public static Request request() { + + } + + /** + * Returns the current lang. + */ + public static Lang lang() { + + } + + /** + * Change durably the lang for the current user + * @param code New lang code to use (e.g. "fr", "en-US", etc.) + * @ + */ + public static boolean changeLang(String code) { + + } + + /** + * Change durably the lang for the current user + * @param lang New Lang object to use + * @ + */ + public static boolean changeLang(Lang lang) { + + } + + /** + * Clear the lang for the current user. + */ + public static void clearLang() { + } + + /** + * Returns the current HTTP response. + */ + public static Response response() { + + } + + /** + * Returns the current HTTP session. + */ + public static Session session() { + + } + + /** + * Puts a new value into the current session. + */ + public static void session(String key, String value) { + } + + /** + * Returns a value from the session. + */ + public static String session(String key) { + + } + + /** + * Returns the current HTTP flash scope. + */ + public static Flash flash() { + + } + + /** + * Puts a new value into the flash scope. + */ + public static void flash(String key, String value) { + } + + /** + * Returns a value from the flash scope. + */ + public static String flash(String key) { + + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/mvc/Http.java b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Http.java new file mode 100644 index 00000000000..e2e28e2626d --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Http.java @@ -0,0 +1,2074 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.mvc; + +//import akka.stream.Materializer; +//import akka.stream.javadsl.Sink; +//import akka.stream.javadsl.Source; +//import akka.util.ByteString; +import com.fasterxml.jackson.databind.JsonNode; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +//import play.api.i18n.Messages$; +//import play.api.libs.json.JsValue; +//import play.api.mvc.Headers$; +//import play.api.mvc.request.*; +import play.core.j.JavaContextComponents; +//import play.core.j.JavaHelpers$; +//import play.core.j.JavaParsers; +import play.i18n.Lang; +import play.i18n.Langs; +import play.i18n.Messages; +import play.i18n.MessagesApi; +import play.libs.Files; +//import play.libs.Json; +import play.libs.XML; +import play.libs.typedmap.TypedKey; +import play.libs.typedmap.TypedMap; +import play.mvc.Http.Cookie.SameSite; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +/** Defines HTTP standard objects. */ +public class Http { + + /** The global HTTP context. */ + public static class Context { + + public static ThreadLocal current = new ThreadLocal<>(); + + /** + * Retrieves the current HTTP context, for the current thread. + * + * @return the context + */ + public static Context current() { + } + + // + + /** + * Creates a new HTTP context. + * + * @param requestBuilder the HTTP request builder. + * @param components the context components. + */ + public Context(RequestBuilder requestBuilder, JavaContextComponents components) {} + + /** + * Creates a new HTTP context. + * + * @param request the HTTP request + * @param components the context components. + */ + public Context(Request request, JavaContextComponents components) {} + + /** + * Creates a new HTTP context. + * + * @param id the unique context ID + * @param header the request header + * @param request the request with body + * @param sessionData the session data extracted from the session cookie + * @param flashData the flash data extracted from the flash cookie + * @param args any arbitrary data to associate with this request context. + * @param components the context components. + */ + public Context( + Long id, + play.api.mvc.RequestHeader header, + Request request, + Map sessionData, + Map flashData, + Map args, + JavaContextComponents components) { + } + + /** + * Creates a new HTTP context, using the references provided. + * + *

Use this constructor (or withRequest) to copy a context within a Java Action to be passed + * to a delegate. + * + * @param id the unique context ID + * @param header the request header + * @param request the request with body + * @param response the response instance to use + * @param session the session instance to use + * @param flash the flash instance to use + * @param args any arbitrary data to associate with this request context. + * @param components the context components. + */ + public Context( + Long id, + play.api.mvc.RequestHeader header, + Request request, + Response response, + Session session, + Flash flash, + Map args, + JavaContextComponents components) { + + } + + /** + * The context id (unique) + * + * @return the id + */ + public Long id() { + } + + /** + * Returns the current request. + * + * @return the request + */ + public Request request() { + } + + /** + * Returns the current response. + * + * @return the response + */ + public Response response() { + } + + /** + * Returns the current session. + * + * @return the session + */ + public Session session() { + } + + /** + * Returns the current flash scope. + * + * @return the flash scope + */ + public Flash flash() { + } + + /** + * The original Play request Header used to create this context. For internal usage only. + * + * @return the original request header. + */ + public play.api.mvc.RequestHeader _requestHeader() { + } + + /** + * The current lang + * + * @return the current lang + */ + public Lang lang() { + } + + /** @return the messages for the current lang */ + public Messages messages() { + } + + /** + * Change durably the lang for the current user. + * + * @param code New lang code to use (e.g. "fr", "en-US", etc.) + * @return true if the requested lang was supported by the application, otherwise false + */ + public boolean changeLang(String code) { + } + + /** + * Change durably the lang for the current user. + * + * @param lang New Lang object to use + * @return true if the requested lang was supported by the application, otherwise false + */ + public boolean changeLang(Lang lang) { + } + + /** Clear the lang for the current user. */ + public void clearLang() { + } + + private Langs langs() { + } + + private MessagesApi messagesApi() { + } + + //private scala.Option sessionDomain() { + //} + + private String sessionPath() { + } + + /** + * Set the language for the current request, but don't change the language cookie. This means + * the language will be set for this request, but will not change for future requests. + * + * @param code the language code to set (e.g. "en-US") + * @throws IllegalArgumentException If the given language is not supported by the application. + */ + public void setTransientLang(String code) { + } + + /** + * Set the language for the current request, but don't change the language cookie. This means + * the language will be set for this request, but will not change for future requests. + * + * @param lang the language to set + * @throws IllegalArgumentException If the given language is not supported by the application. + */ + public void setTransientLang(Lang lang) { + } + + /** + * Clear the language for the current request, but don't change the language cookie. This means + * the language will be cleared for this request (so a default will be used), but will not + * change for future requests. + */ + public void clearTransientLang() { + } + + /** Free space to store your request specific data. */ + public Map args; + + //public FileMimeTypes fileMimeTypes() {} + + /** Import in templates to get implicit HTTP context. */ + public static class Implicit { + + /** + * Returns the current response. + * + * @return the current response. + */ + public static Response response() { + } + + /** + * Returns the current request. + * + * @return the current request. + */ + public static Request request() { + } + + /** + * Returns the current flash scope. + * + * @return the current flash scope. + */ + public static Flash flash() { + } + + /** + * Returns the current session. + * + * @return the current session. + */ + public static Session session() { + } + + /** + * Returns the current lang. + * + * @return the current lang. + */ + public static Lang lang() { + } + + /** @return the messages for the current lang */ + public static Messages messages() { + } + + /** + * Returns the current context. + * + * @return the current context. + */ + public static Context ctx() { + } + } + + /** @return a String representation */ + public String toString() { + } + + /** + * Create a new context with the given request. + * + *

The id, Scala RequestHeader, session, flash and args remain unchanged. + * + *

This method is intended for use within a Java action, to create a new Context to pass to a + * delegate action. + * + * @param request The request to create the new header from. + * @return The new context. + */ + public Context withRequest(Request request) { + } + } + + /** A wrapped context. Use this to modify the context in some way. */ + //public abstract static class WrappedContext extends Context { + + /** @param wrapped the context the created instance will wrap */ +/* public WrappedContext(Context wrapped) { + super( + wrapped.id(), + wrapped._requestHeader(), + wrapped.request(), + wrapped.session(), + wrapped.flash(), + wrapped.args, + wrapped.components); + } + + @Override + public Long id() { + } + + @Override + public Request request() { + } + + @Override + public Response response() { + } + + @Override + public Session session() { + } + + @Override + public Flash flash() { + } + + //@Override + //public play.api.mvc.RequestHeader _requestHeader() { } + + @Override + public Lang lang() { + } + + @Override + public boolean changeLang(String code) { + } + + @Override + public boolean changeLang(Lang lang) { + } + + @Override + public void clearLang() { + } + } */ + + public static class Headers { + + private final Map> headers; + + public Headers(Map> headers) { + } + + /** @return all the headers as a map. */ + public Map> toMap() { + } + + /** + * Checks if the given header is present. + * + * @param headerName The name of the header (case-insensitive) + * @return true if the request did contain the header. + */ + public boolean contains(String headerName) { + } + + /** + * Gets the header value. If more than one value is associated with this header, then returns + * the first one. + * + * @param name the header name + * @return the first header value or empty if no value available. + */ + public Optional get(String name) { + } + + /** + * Get all the values associated with the header name. + * + * @param name the header name. + * @return the list of values associates with the header of empty. + */ + public List getAll(String name) { + } + + /** @return the scala version of this headers. */ + //public play.api.mvc.Headers asScala() {} + + /** + * Add a new header with the given value. + * + * @param name the header name + * @param value the header value + * @return this with the new header added + */ + public Headers addHeader(String name, String value) { + } + + /** + * Add a new header with the given values. + * + * @param name the header name + * @param values the header values + * @return this with the new header added + */ + public Headers addHeader(String name, List values) { + } + + /** + * Remove a header. + * + * @param name the header name. + * @return this without the removed header. + */ + public Headers remove(String name) { + } + } + + public interface RequestHeader { + + /** + * The complete request URI, containing both path and query string. + * + * @return the uri + */ + String uri(); + + /** + * The HTTP Method. + * + * @return the http method + */ + String method(); + + /** + * The HTTP version. + * + * @return the version + */ + String version(); + + /** + * The client IP address. + * + *

retrieves the last untrusted proxy from the Forwarded-Headers or the + * X-Forwarded-*-Headers. + * + * @return the remote address + */ + String remoteAddress(); + + /** + * Is the client using SSL? + * + * @return true that the client is using SSL + */ + boolean secure(); + + /** @return a map of typed attributes associated with the request. */ + TypedMap attrs(); + + /** + * Create a new version of this object with the given attributes attached to it. + * + * @param newAttrs The new attributes to add. + * @return The new version of this object with the attributes attached. + */ + RequestHeader withAttrs(TypedMap newAttrs); + + /** + * Create a new versions of this object with the given attribute attached to it. + * + * @param key The new attribute key. + * @param value The attribute value. + * @param the attribute type + * @return The new version of this object with the new attribute. + */ + RequestHeader addAttr(TypedKey key, A value); + + /** + * Attach a body to this header. + * + * @param body The body to attach. + * @return A new request with the body attached to the header. + */ + Request withBody(RequestBody body); + + /** + * The request host. + * + * @return the host + */ + String host(); + + /** + * The URI path. + * + * @return the path + */ + String path(); + + /** + * The Request Langs extracted from the Accept-Language header and sorted by preference + * (preferred first). + * + * @return the preference-ordered list of languages accepted by the client + */ + List acceptLanguages(); + + /** + * @return The media types set in the request Accept header, sorted by preference (preferred + * first) + */ + //List acceptedTypes(); + + /** + * Check if this request accepts a given media type. + * + * @param mimeType the mimeType to check for support. + * @return true if mimeType is in the Accept header, otherwise false + */ + boolean accepts(String mimeType); + + /** + * The query string content. + * + * @return the query string map + */ + Map queryString(); + + /** + * Helper method to access a queryString parameter. + * + * @param key the query string parameter to look up + * @return the value for the provided key. + */ + String getQueryString(String key); + + /** @return the request cookies */ + Cookies cookies(); + + /** + * @param name Name of the cookie to retrieve + * @return the cookie, if found, otherwise null + */ + Cookie cookie(String name); + + /** + * Retrieve all headers. + * + * @return the request headers for this request. + */ + Headers getHeaders(); + + /** + * Retrieves all headers. + * + * @return a map of of header name to headers with case-insensitive keys + * @deprecated Deprecated as of 2.6.0. Use {@link #getHeaders()} instead. + */ + @Deprecated + default Map headers() { + } + + /** + * Retrieves a single header. + * + * @param headerName The name of the header (case-insensitive) + * @return the value corresponding to headerName, or null if it was not present + * @deprecated Deprecated as of 2.6.0. Use {@link #header(String)} ()} instead. + */ + @Deprecated + default String getHeader(String headerName) { + } + + /** + * Retrieves a single header. + * + * @param headerName The name of the header (case-insensitive) + * @return the value corresponding to headerName, or empty if it was not present + */ + default Optional header(String headerName) { + } + + /** + * Checks if the request has the header. + * + * @param headerName The name of the header (case-insensitive) + * @return true if the request did contain the header. + */ + default boolean hasHeader(String headerName) { + } + + /** + * Checks if the request has a body. + * + * @return true if request has a body, false otherwise. + */ + boolean hasBody(); + + /** + * Get the content type of the request. + * + * @return The request content type excluding the charset, if it exists. + */ + Optional contentType(); + + /** + * Get the charset of the request. + * + * @return The request charset, which comes from the content type header, if it exists. + */ + Optional charset(); + + /** + * The X509 certificate chain presented by a client during SSL requests. + * + * @return The chain of X509Certificates used for the request if the request is secure and the + * server supports it. + */ + Optional> clientCertificateChain(); + + /** + * @return the tags for the request + * @deprecated Use attr, withAttr, etc. + */ + @Deprecated + Map tags(); + + /** + * For internal Play-use only + * + * @return the underlying request + * @deprecated As of release 2.6.0. Use {@link #asScala()} + */ + //@Deprecated + //play.api.mvc.RequestHeader _underlyingHeader(); + + /** + * Return the Scala version of the request header. + * + * @return the Scala version for this request header. + * @see play.api.mvc.RequestHeader + */ + //play.api.mvc.RequestHeader asScala(); + } + + /** An HTTP request. */ + public interface Request extends RequestHeader { + + /** + * The request body. + * + * @return the body + */ + RequestBody body(); + + Request withBody(RequestBody body); + + // Override return type + Request withAttrs(TypedMap newAttrs); + + // Override return type + Request addAttr(TypedKey key, A value); + + /** + * The user name for this request, if defined. This is usually set by annotating your Action + * with @Authenticated. + * + * @return the username + * @deprecated As of release 2.6, use attrs.get(Security.USERNAME) or + * attrs.getOptional(Security.USERNAME). + */ + @Deprecated + String username(); + + /** + * Returns a request updated with specified user name + * + * @param username the new user name + * @return a copy of the request containing the specified user name + * @deprecated As of release 2.6, use + * req.withAttrs(req.attrs().put(Security.USERNAME, username)). + */ + @Deprecated + Request withUsername(String username); + + /** + * For internal Play-use only + * + * @return the underlying request + * @deprecated As of release 2.6.0. Use {@link #asScala()} + */ + //@Deprecated + //play.api.mvc.Request _underlyingRequest(); + + /** + * Return the Scala version of the request + * + * @return the underlying request. + * @see play.api.mvc.Request + */ + //play.api.mvc.Request asScala(); + } + + /** An HTTP request. */ + public static class RequestImpl extends play.core.j.RequestImpl { + + /** + * Constructor only based on a header. + * + * @param header the header from a request + */ + public RequestImpl(play.api.mvc.RequestHeader header) {} + + /** + * Constructor with a requestbody. + * + * @param request the body of the request + */ + public RequestImpl(play.api.mvc.Request request) {} + } + + /** The builder for building a request. */ + public static class RequestBuilder { + + //protected play.api.mvc.Request req; + + /** + * Returns a simple request builder. The initial request is "GET / HTTP/1.1" from 127.0.0.1 over + * an insecure connection. The request is created using the default factory. + */ + public RequestBuilder() { + } + + /** + * Returns a simple request builder. The initial request is "GET / HTTP/1.1" from 127.0.0.1 over + * an insecure connection. The request is created using the given factory. + * + * @param requestFactory the incoming request factory + */ + //public RequestBuilder(RequestFactory requestFactory) {} + + /** @return the request body, if a previously the body has been set */ + public RequestBody body() { + } + + /** + * Get the username. This method calls attrs().getOptional(Security.USERNAME). + * + * @return the username or null + * @deprecated Use attrs().get(Security.USERNAME) or + * attrs().getOptional(Security.USERNAME) instead. + */ + @Deprecated + public String username() { + } + + /** + * Set the username. This method calls attr(Security.USERNAME, username). + * + * @param username the username for the request + * @return the modified builder + * @deprecated Use attr(Security.USERNAME, username) instead. + */ + @Deprecated + public RequestBuilder username(String username) { + } + + /** + * Set the body of the request. + * + * @param body the body + * @param contentType Content-Type header value + * @return the modified builder + */ + protected RequestBuilder body(RequestBody body, String contentType) { + } + + /** + * Set the body of the request. + * + * @param body The body. + * @return the modified builder + */ + protected RequestBuilder body(RequestBody body) { + } + + /** + * Set a Binary Data to this request using a singleton temp file creator The + * Content-Type header of the request is set to application/octet-stream. + * + * @param data the Binary Data + * @return the modified builder + */ + public RequestBuilder bodyRaw(/*ByteString*/String data) { + } + + /** + * Set a Binary Data to this request. The Content-Type header of the request is set to + * application/octet-stream. + * + * @param data the Binary Data + * @param tempFileCreator the temporary file creator for binary data. + * @return the modified builder + */ + public RequestBuilder bodyRaw(/* ByteString */String data, Files.TemporaryFileCreator tempFileCreator) { + } + + /** + * Set a Binary Data to this request using a singleton temporary file creator. The + * Content-Type header of the request is set to application/octet-stream. + * + * @param data the Binary Data + * @return the modified builder + */ + public RequestBuilder bodyRaw(byte[] data) { + } + + /** + * Set a Binary Data to this request. The Content-Type header of the request is set to + * application/octet-stream. + * + * @param data the Binary Data + * @param tempFileCreator the temporary file creator for binary data. + * @return the modified builder + */ + public RequestBuilder bodyRaw(byte[] data, Files.TemporaryFileCreator tempFileCreator) { + } + + /** + * Set a Form url encoded body to this request. + * + * @param data the x-www-form-urlencoded parameters + * @return the modified builder + */ + public RequestBuilder bodyFormArrayValues(Map data) { + } + + /** + * Set a Form url encoded body to this request. + * + * @param data the x-www-form-urlencoded parameters + * @return the modified builder + */ + public RequestBuilder bodyForm(Map data) { + } + + /** + * Set a Multipart Form url encoded body to this request. + * + * @param data the multipart-form parameters + * @param temporaryFileCreator the temporary file creator. + * @param mat a Akka Streams Materializer + * @return the modified builder + */ + public RequestBuilder bodyMultipart( + /*List>>*/List data, + Files.TemporaryFileCreator temporaryFileCreator, + /*Materializer*/String mat) { + } + + /** + * Set a Json Body to this request. The Content-Type header of the request is set to + * application/json. + * + * @param node the Json Node + * @return this builder, updated + */ + public RequestBuilder bodyJson(JsonNode node) { + } + + /** + * Set a Json Body to this request. The Content-Type header of the request is set to + * application/json. + * + * @param json the JsValue + * @return the modified builder + */ + public RequestBuilder bodyJson(/* JsValue */String json) { + } + + /** + * Set a XML to this request. The Content-Type header of the request is set to + * application/xml. + * + * @param xml the XML + * @return the modified builder + */ + public RequestBuilder bodyXml(InputSource xml) { + } + + /** + * Set a XML to this request. + * + *

The Content-Type header of the request is set to application/xml. + * + * @param xml the XML + * @return the modified builder + */ + public RequestBuilder bodyXml(Document xml) { + } + + /** + * Set a Text to this request. The Content-Type header of the request is set to + * text/plain. + * + * @param text the text + * @return this builder, updated + */ + public RequestBuilder bodyText(String text) { + } + + /** + * Builds the request. + * + * @return a build of the given parameters + */ + public RequestImpl build() { + } + + // ------------------- + // REQUEST HEADER CODE + + /** @return the id of the request */ + public Long id() { + } + + /** + * @param id the id to be used + * @return the builder instance + */ + public RequestBuilder id(Long id) { + } + + /** + * Add an attribute to the request. + * + * @param key The key of the attribute to add. + * @param value The value of the attribute to add. + * @param The type of the attribute to add. + * @return the request builder with extra attribute + */ + public RequestBuilder attr(TypedKey key, T value) { + } + + /** + * Update the request attributes. This replaces all existing attributes. + * + * @param newAttrs The attribute entries to add. + * @return the request builder with extra attributes set. + */ + public RequestBuilder attrs(TypedMap newAttrs) { + } + + /** + * Get the request attributes. + * + * @return the request builder's request attributes. + */ + public TypedMap attrs() { + } + + /** + * @return the tags for the request + * @deprecated Use typed attributes, i.e. attrs(), instead. + */ + @Deprecated + public Map tags() { + } + + /** + * @param tags overwrites the tags for this request + * @return the builder instance + * @deprecated Use attrs(...) instead. + */ + @Deprecated + public RequestBuilder tags(Map tags) { + } + + /** + * Puts an extra tag. + * + * @param key the key for the tag + * @param value the value for the tag + * @return the builder + * @deprecated Use attr(key, value) instead. + */ + @Deprecated + public RequestBuilder tag(String key, String value) { + } + + /** @return the builder instance. */ + public String method() { + } + + /** + * @param method sets the method + * @return the builder instance + */ + public RequestBuilder method(String method) { + } + + /** @return gives the uri of the request */ + public String uri() { + } + + public RequestBuilder uri(URI uri) { + } + + /** + * Sets the uri. + * + * @param str the uri + * @return the builder instance + */ + public RequestBuilder uri(String str) { + } + + /** + * @param secure true if the request is secure + * @return the builder instance + */ + public RequestBuilder secure(boolean secure) { + } + + /** @return the status if the request is secure */ + public boolean secure() { + } + + /** @return the host name from the header */ + public String host() { + } + + /** + * @param host sets the host in the header + * @return the builder instance + */ + public RequestBuilder host(String host) { + } + + /** @return the raw path of the uri */ + public String path() { + } + + /** + * This method sets the path of the uri. + * + * @param path the path after the port and for the query in a uri + * @return the builder instance + */ + public RequestBuilder path(String path) { + // Update URI with new path element + } + + /** @return the version */ + public String version() { + } + + /** + * @param version the version + * @return the builder instance + */ + public RequestBuilder version(String version) { + } + + /** + * @param key the key to be used in the header + * @return the value associated with the key, if multiple, the first, if none returns null + * @deprecated As of release 2.6, use {@link #getHeaders()} instead. + */ + @Deprecated + public String header(String key) { + } + + /** + * @param key the key to be used in the header + * @return all values (could be 0) associated with the key + * @deprecated As of release 2.6, use {@link #getHeaders()} instead. + */ + @Deprecated + public String[] headers(String key) { + } + + /** + * @return the headers + * @deprecated As of release 2.6, use {@link #getHeaders()} instead. + */ + @Deprecated + public Map headers() { + } + + /** @return the headers for this request builder */ + public Headers getHeaders() { + } + + /** + * @param headers the headers to be replaced + * @return the builder instance + * @deprecated As of release 2.6, use {@link #headers(Headers)} instead. + */ + @Deprecated + public RequestBuilder headers(Map headers) { + } + + /** + * Set the headers to be used by the request builder. + * + * @param headers the headers to be replaced + * @return the builder instance + */ + public RequestBuilder headers(Headers headers) { + } + + /** + * @param key the key for in the header + * @param values the values associated with the key + * @return the builder instance + * @deprecated As of release 2.6, use {@link #header(String, List)} instead. + */ + @Deprecated + public RequestBuilder header(String key, String[] values) { + } + + /** + * @param key the key for in the header + * @param values the values associated with the key + * @return the builder instance + */ + public RequestBuilder header(String key, List values) { + } + + /** + * @param key the key for in the header + * @param value the value (one) associated with the key + * @return the builder instance + */ + public RequestBuilder header(String key, String value) { + } + + /** @return the cookies in Java instances */ + public Cookies cookies() { + } + + /** + * Sets one cookie. + * + * @param cookie the cookie to be set + * @return the builder instance + */ + public RequestBuilder cookie(Cookie cookie) { + } + + /** @return the cookies in a Java map */ + public Map flash() { + } + + /** + * Sets a cookie in the request. + * + * @param key the key for the cookie + * @param value the value for the cookie + * @return the builder instance + */ + public RequestBuilder flash(String key, String value) { + + } + + /** + * Sets cookies in a request. + * + * @param data a key value mapping of cookies + * @return the builder instance + */ + public RequestBuilder flash(Map data) { + + } + + /** @return the sessions in the request */ + public Map session() { + } + + /** + * Sets a session. + * + * @param key the key for the session + * @param value the value associated with the key for the session + * @return the builder instance + */ + public RequestBuilder session(String key, String value) { + } + + /** + * Sets all parameters for the session. + * + * @param data a key value mapping of the session data + * @return the builder instance + */ + public RequestBuilder session(Map data) { + } + + /** @return the remote address */ + public String remoteAddress() { + } + + /** + * @param remoteAddress sets the remote address + * @return the builder instance + */ + public RequestBuilder remoteAddress(String remoteAddress) { + } + + /** @return the client X509Certificates if they have been set */ + public Optional> clientCertificateChain() { + } + + /** + * @param clientCertificateChain sets the X509Certificates to use + * @return the builder instance + */ + public RequestBuilder clientCertificateChain(List clientCertificateChain) { + } + } + + /** Handle the request body a raw bytes data. */ + public abstract static class RawBuffer { + + /** + * Buffer size. + * + * @return the buffer size + */ + public abstract Long size(); + + /** + * Returns the buffer content as a bytes array. + * + * @param maxLength The max length allowed to be stored in memory + * @return null if the content is too big to fit in memory + */ + //public abstract ByteString asBytes(int maxLength); + + /** + * Returns the buffer content as a bytes array + * + * @return the bytes + */ + //public abstract ByteString asBytes(); + + /** + * Returns the buffer content as File + * + * @return the file + */ + public abstract File asFile(); + } + + /** Multipart form data body. */ + public abstract static class MultipartFormData { + + /** Info about a file part */ + public static class FileInfo { + public FileInfo(String key, String filename, String contentType) { + } + + public String getKey() { + } + + public String getFilename() { + } + + public String getContentType() { + } + } + + public interface Part {} + + /** A file part. */ + public static class FilePart implements Part { + + public FilePart(String key, String filename, String contentType, A file) { + } + + /** + * The part name. + * + * @return the part name + */ + public String getKey() { + } + + /** + * The file name. + * + * @return the file name + */ + public String getFilename() { + } + + /** + * The file Content-Type + * + * @return the content type + */ + public String getContentType() { + } + + /** + * The File. + * + * @return the file + */ + public A getFile() { + } + } + + public static class DataPart /*implements Part>*/ { + + public DataPart(String key, String value) { + } + + /** + * The part name. + * + * @return the part name + */ + public String getKey() { + } + + /** + * The part value. + * + * @return the part value + */ + public String getValue() { + } + } + + /** + * Extract the data parts as Form url encoded. + * + * @return the data that was URL encoded + */ + public abstract Map asFormUrlEncoded(); + + /** + * Retrieves all file parts. + * + * @return the file parts + */ + public abstract List> getFiles(); + + /** + * Access a file part. + * + * @param key name of the file part to access + * @return the file part specified by key + */ + public FilePart getFile(String key) { + } + } + + /** The request body. */ + public static final class RequestBody { + + public RequestBody(Object body) { + } + + /** + * The request content parsed as multipart form data. + * + * @param the file type (e.g. play.api.libs.Files.TemporaryFile) + * @return the content parsed as multipart form data + */ + public MultipartFormData asMultipartFormData() { + } + + /** + * The request content parsed as URL form-encoded. + * + * @return the request content parsed as URL form-encoded. + */ + public Map asFormUrlEncoded() { + } + + /** + * The request content as Array bytes. + * + * @return The request content as Array bytes. + */ + public RawBuffer asRaw() { + } + + /** + * The request content as text. + * + * @return The request content as text. + */ + public String asText() { + } + + /** + * The request content as XML. + * + * @return The request content as XML. + */ + public Document asXml() { + } + + /** + * The request content as Json. + * + * @return The request content as Json. + */ + public JsonNode asJson() { + } + + /** + * The request content as a ByteString. + * + *

This makes a best effort attempt to convert the parsed body to a ByteString, if it knows + * how. This includes String, json, XML and form bodies. It doesn't include multipart/form-data + * or raw bodies that don't fit in the configured max memory buffer, nor does it include custom + * output types from custom body parsers. + * + * @return the request content as a ByteString + */ + //public ByteString asBytes() {} + + private String encode(String value) { + } + + /** + * Cast this RequestBody as T if possible. + * + * @param tType class that we are trying to cast the body as + * @param type of the provided tType + * @return either a successful cast into T or null + */ + public T as(Class tType) { + } + + public String toString() { + } + } + + /** The HTTP response. */ + public static class Response implements HeaderNames { + + /** + * Adds a new header to the response. + * + * @param name The name of the header, must not be null + * @param value The value of the header, must not be null + */ + public void setHeader(String name, String value) { + } + + /** + * Gets the current response headers. + * + * @return the current response headers. + */ + public Map getHeaders() { + } + + /** + * @deprecated noop. Use {@link Result#as(String)} instead. + * @param contentType Deprecated + */ + @Deprecated + public void setContentType(String contentType) {} + + /** + * Set a new cookie. + * + * @param name Cookie name, must not be null + * @param value Cookie value + * @param maxAge Cookie duration in seconds (null for a transient cookie, 0 or less for one that + * expires now) + * @param path Cookie path + * @param domain Cookie domain + * @param secure Whether the cookie is secured (for HTTPS requests) + * @param httpOnly Whether the cookie is HTTP only (i.e. not accessible from client-side + * JavaScript code) + * @param sameSite The SameSite value (Strict or Lax) + * @deprecated Use {@link #setCookie(Http.Cookie)} instead. + */ + @Deprecated + public void setCookie( + String name, + String value, + Integer maxAge, + String path, + String domain, + boolean secure, + boolean httpOnly, + SameSite sameSite) { + } + + /** + * Set a new cookie. + * + * @param cookie to set + */ + public void setCookie(Cookie cookie) { + } + + /** + * Discard a cookie on the default path ("/") with no domain and that's not secure. + * + * @param name The name of the cookie to discard, must not be null + */ + public void discardCookie(String name) { + } + + /** + * Discard a cookie on the given path with no domain and not that's secure. + * + * @param name The name of the cookie to discard, must not be null + * @param path The path of the cookie te discard, may be null + */ + public void discardCookie(String name, String path) { + } + + /** + * Discard a cookie on the given path and domain that's not secure. + * + * @param name The name of the cookie to discard, must not be null + * @param path The path of the cookie te discard, may be null + * @param domain The domain of the cookie to discard, may be null + */ + public void discardCookie(String name, String path, String domain) { + } + + /** + * Discard a cookie in this result + * + * @param name The name of the cookie to discard, must not be null + * @param path The path of the cookie te discard, may be null + * @param domain The domain of the cookie to discard, may be null + * @param secure Whether the cookie to discard is secure + */ + public void discardCookie(String name, String path, String domain, boolean secure) { + } + + public Collection cookies() { + } + + public Optional cookie(String name) { + } + } + + /** + * HTTP Session. + * + *

Session data are encoded into an HTTP cookie, and can only contain simple String + * values. + */ + public static class Session extends HashMap { + + public boolean isDirty = false; + + public Session(Map data) { + } + + /** Removes the specified value from the session. */ + @Override + public String remove(Object key) { + } + + /** Adds the given value to the session. */ + @Override + public String put(String key, String value) { + } + + /** Adds the given values to the session. */ + @Override + public void putAll(Map values) { + } + + /** Clears the session. */ + @Override + public void clear() { + } + } + + /** + * HTTP Flash. + * + *

Flash data are encoded into an HTTP cookie, and can only contain simple String values. + */ + public static class Flash extends HashMap { + + public boolean isDirty = false; + + public Flash(Map data) { + } + + /** Removes the specified value from the flash scope. */ + @Override + public String remove(Object key) { + } + + /** Adds the given value to the flash scope. */ + @Override + public String put(String key, String value) { + } + + /** Adds the given values to the flash scope. */ + @Override + public void putAll(Map values) { + } + + /** Clears the flash scope. */ + @Override + public void clear() { + } + } + + /** HTTP Cookie */ + public static class Cookie { + /** + * Construct a new cookie. Prefer {@link Cookie#builder} for creating new cookies in your + * application. + * + * @param name Cookie name, must not be null + * @param value Cookie value + * @param maxAge Cookie duration in seconds (null for a transient cookie, 0 or less for one that + * expires now) + * @param path Cookie path + * @param domain Cookie domain + * @param secure Whether the cookie is secured (for HTTPS requests) + * @param httpOnly Whether the cookie is HTTP only (i.e. not accessible from client-side + * JavaScript code) + * @param sameSite the SameSite attribute for this cookie (for CSRF protection). + */ + public Cookie( + String name, + String value, + Integer maxAge, + String path, + String domain, + boolean secure, + boolean httpOnly, + SameSite sameSite) { + } + + /** + * @param name Cookie name, must not be null + * @param value Cookie value + * @param maxAge Cookie duration in seconds (null for a transient cookie, 0 or less for one that + * expires now) + * @param path Cookie path + * @param domain Cookie domain + * @param secure Whether the cookie is secured (for HTTPS requests) + * @param httpOnly Whether the cookie is HTTP only (i.e. not accessible from client-side + * JavaScript code) + * @deprecated as of 2.6.0. Use {@link Cookie#builder}. + */ + @Deprecated + public Cookie( + String name, + String value, + Integer maxAge, + String path, + String domain, + boolean secure, + boolean httpOnly) { + } + + /** + * @param name the cookie builder name + * @param value the cookie builder value + * @return the cookie builder with the specified name and value + */ + public static CookieBuilder builder(String name, String value) { + } + + /** @return the cookie name */ + public String name() { + } + + /** @return the cookie value */ + public String value() { + } + + /** + * @return the cookie expiration date in seconds, null for a transient cookie, a value less than + * zero for a cookie that expires now + */ + public Integer maxAge() { + } + + /** @return the cookie path */ + public String path() { + } + + /** @return the cookie domain, or null if not defined */ + public String domain() { + } + + /** @return wether the cookie is secured, sent only for HTTPS requests */ + public boolean secure() { + } + + /** + * @return wether the cookie is HTTP only, i.e. not accessible from client-side JavaScript code + */ + public boolean httpOnly() { + } + + /** @return the SameSite attribute for this cookie */ + public Optional sameSite() { + } + + /** The cookie SameSite attribute */ + public enum SameSite { + STRICT("Strict"), + LAX("Lax"), + NONE("None"); + + private final String value; + + SameSite(String value) { + } + + public String value() { + } + + //public play.api.mvc.Cookie.SameSite asScala() {} + + public static Optional parse(String sameSite) { + } + } + + //public play.api.mvc.Cookie asScala() {} + } + + /* + * HTTP Cookie builder + */ + + public static class CookieBuilder { + + + /** + * @param name the cookie builder name + * @param value the cookie builder value + * @return the cookie builder with the specified name and value + */ + private CookieBuilder(String name, String value) { + } + + /** + * @param name The name of the cookie + * @return the cookie builder with the new name + */ + public CookieBuilder withName(String name) { + } + + /** + * @param value The value of the cookie + * @return the cookie builder with the new value + */ + public CookieBuilder withValue(String value) { + } + + /** + * @param maxAge The maxAge of the cookie in seconds + * @return the cookie builder with the new maxAge + * @deprecated As of 2.6.0, use withMaxAge(Duration) instead. + */ + @Deprecated + public CookieBuilder withMaxAge(Integer maxAge) { + } + + /** + * Set the maximum age of the cookie. + * + *

For example, to set a maxAge of 40 days: + * builder.withMaxAge(Duration.of(40, ChronoUnit.DAYS)) + * + * @param maxAge a duration representing the maximum age of the cookie. Will be truncated to the + * nearest second. + * @return the cookie builder with the new maxAge + */ + public CookieBuilder withMaxAge(Duration maxAge) { + } + + /** + * @param path The path of the cookie + * @return the cookie builder with the new path + */ + public CookieBuilder withPath(String path) { + } + + /** + * @param domain The domain of the cookie + * @return the cookie builder with the new domain + */ + public CookieBuilder withDomain(String domain) { + } + + /** + * @param secure specify if the cookie is secure + * @return the cookie builder with the new is secure flag + */ + public CookieBuilder withSecure(boolean secure) { + } + + /** + * @param httpOnly specify if the cookie is httpOnly + * @return the cookie builder with the new is httpOnly flag + */ + public CookieBuilder withHttpOnly(boolean httpOnly) { + } + + /** + * @param sameSite specify if the cookie is SameSite + * @return the cookie builder with the new SameSite flag + */ + public CookieBuilder withSameSite(SameSite sameSite) { + } + + /** @return a new cookie with the current builder parameters */ + public Cookie build() { + } + } + + /** HTTP Cookies set */ + public interface Cookies extends Iterable { + + /** + * @param name Name of the cookie to retrieve + * @return the cookie that is associated with the given name + */ + Cookie get(String name); + } + + /** Defines all standard HTTP headers. */ + + public interface HeaderNames { + + String ACCEPT = "Accept"; + String ACCEPT_CHARSET = "Accept-Charset"; + String ACCEPT_ENCODING = "Accept-Encoding"; + String ACCEPT_LANGUAGE = "Accept-Language"; + String ACCEPT_RANGES = "Accept-Ranges"; + String AGE = "Age"; + String ALLOW = "Allow"; + String AUTHORIZATION = "Authorization"; + String CACHE_CONTROL = "Cache-Control"; + String CONNECTION = "Connection"; + String CONTENT_DISPOSITION = "Content-Disposition"; + String CONTENT_ENCODING = "Content-Encoding"; + String CONTENT_LANGUAGE = "Content-Language"; + String CONTENT_LENGTH = "Content-Length"; + String CONTENT_LOCATION = "Content-Location"; + String CONTENT_MD5 = "Content-MD5"; + String CONTENT_RANGE = "Content-Range"; + String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + String CONTENT_TYPE = "Content-Type"; + String COOKIE = "Cookie"; + String DATE = "Date"; + String ETAG = "ETag"; + String EXPECT = "Expect"; + String EXPIRES = "Expires"; + String FORWARDED = "Forwarded"; + String FROM = "From"; + String HOST = "Host"; + String IF_MATCH = "If-Match"; + String IF_MODIFIED_SINCE = "If-Modified-Since"; + String IF_NONE_MATCH = "If-None-Match"; + String IF_RANGE = "If-Range"; + String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + String LAST_MODIFIED = "Last-Modified"; + String LINK = "Link"; + String LOCATION = "Location"; + String MAX_FORWARDS = "Max-Forwards"; + String PRAGMA = "Pragma"; + String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + String PROXY_AUTHORIZATION = "Proxy-Authorization"; + String RANGE = "Range"; + String REFERER = "Referer"; + String RETRY_AFTER = "Retry-After"; + String SERVER = "Server"; + String SET_COOKIE = "Set-Cookie"; + String SET_COOKIE2 = "Set-Cookie2"; + String TE = "Te"; + String TRAILER = "Trailer"; + String TRANSFER_ENCODING = "Transfer-Encoding"; + String UPGRADE = "Upgrade"; + String USER_AGENT = "User-Agent"; + String VARY = "Vary"; + String VIA = "Via"; + String WARNING = "Warning"; + String WWW_AUTHENTICATE = "WWW-Authenticate"; + String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + String ORIGIN = "Origin"; + String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + String X_FORWARDED_FOR = "X-Forwarded-For"; + String X_FORWARDED_HOST = "X-Forwarded-Host"; + String X_FORWARDED_PORT = "X-Forwarded-Port"; + String X_FORWARDED_PROTO = "X-Forwarded-Proto"; + String X_REQUESTED_WITH = "X-Requested-With"; + String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + String X_FRAME_OPTIONS = "X-Frame-Options"; + String X_XSS_PROTECTION = "X-XSS-Protection"; + String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; + String X_PERMITTED_CROSS_DOMAIN_POLICIES = "X-Permitted-Cross-Domain-Policies"; + String CONTENT_SECURITY_POLICY = "Content-Security-Policy"; + String CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"; + String X_CONTENT_SECURITY_POLICY_NONCE_HEADER = "X-Content-Security-Policy-Nonce"; + String REFERRER_POLICY = "Referrer-Policy"; + } + + /** + * Defines all standard HTTP status codes. + * + * @see RFC 7231 and RFC 6585 + */ + public interface Status { + int CONTINUE = 100; + int SWITCHING_PROTOCOLS = 101; + + int OK = 200; + int CREATED = 201; + int ACCEPTED = 202; + int NON_AUTHORITATIVE_INFORMATION = 203; + int NO_CONTENT = 204; + int RESET_CONTENT = 205; + int PARTIAL_CONTENT = 206; + int MULTI_STATUS = 207; + + int MULTIPLE_CHOICES = 300; + int MOVED_PERMANENTLY = 301; + int FOUND = 302; + int SEE_OTHER = 303; + int NOT_MODIFIED = 304; + int USE_PROXY = 305; + int TEMPORARY_REDIRECT = 307; + int PERMANENT_REDIRECT = 308; + + int BAD_REQUEST = 400; + int UNAUTHORIZED = 401; + int PAYMENT_REQUIRED = 402; + int FORBIDDEN = 403; + int NOT_FOUND = 404; + int METHOD_NOT_ALLOWED = 405; + int NOT_ACCEPTABLE = 406; + int PROXY_AUTHENTICATION_REQUIRED = 407; + int REQUEST_TIMEOUT = 408; + int CONFLICT = 409; + int GONE = 410; + int LENGTH_REQUIRED = 411; + int PRECONDITION_FAILED = 412; + int REQUEST_ENTITY_TOO_LARGE = 413; + int REQUEST_URI_TOO_LONG = 414; + int UNSUPPORTED_MEDIA_TYPE = 415; + int REQUESTED_RANGE_NOT_SATISFIABLE = 416; + int EXPECTATION_FAILED = 417; + int IM_A_TEAPOT = 418; + int UNPROCESSABLE_ENTITY = 422; + int LOCKED = 423; + int FAILED_DEPENDENCY = 424; + int UPGRADE_REQUIRED = 426; + + // See https://tools.ietf.org/html/rfc6585 for the following statuses + int PRECONDITION_REQUIRED = 428; + int TOO_MANY_REQUESTS = 429; + int REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + + int INTERNAL_SERVER_ERROR = 500; + int NOT_IMPLEMENTED = 501; + int BAD_GATEWAY = 502; + int SERVICE_UNAVAILABLE = 503; + int GATEWAY_TIMEOUT = 504; + int HTTP_VERSION_NOT_SUPPORTED = 505; + int INSUFFICIENT_STORAGE = 507; + + // See https://tools.ietf.org/html/rfc6585#section-6 + int NETWORK_AUTHENTICATION_REQUIRED = 511; + } + + /** Common HTTP MIME types */ + public interface MimeTypes { + + /** Content-Type of text. */ + String TEXT = "text/plain"; + + /** Content-Type of html. */ + String HTML = "text/html"; + + /** Content-Type of json. */ + String JSON = "application/json"; + + /** Content-Type of xml. */ + String XML = "application/xml"; + + /** Content-Type of xhtml. */ + String XHTML = "application/xhtml+xml"; + + /** Content-Type of css. */ + String CSS = "text/css"; + + /** Content-Type of javascript. */ + String JAVASCRIPT = "application/javascript"; + + /** Content-Type of form-urlencoded. */ + String FORM = "application/x-www-form-urlencoded"; + + /** Content-Type of server sent events. */ + String EVENT_STREAM = "text/event-stream"; + + /** Content-Type of binary data. */ + String BINARY = "application/octet-stream"; + } + + /** Standard HTTP Verbs */ + public interface HttpVerbs { + String GET = "GET"; + String POST = "POST"; + String PUT = "PUT"; + String PATCH = "PATCH"; + String DELETE = "DELETE"; + String HEAD = "HEAD"; + String OPTIONS = "OPTIONS"; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/mvc/Result.java b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Result.java new file mode 100644 index 00000000000..6ad30cb02f3 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Result.java @@ -0,0 +1,10 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.mvc; + +/** Any action result. */ +public class Result { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/mvc/Results.java b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Results.java new file mode 100644 index 00000000000..586f4a0d979 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/mvc/Results.java @@ -0,0 +1,1805 @@ +/* + * Copyright (C) Lightbend Inc. + */ + +package play.mvc; + +import java.io.File; +import java.io.InputStream; +import java.util.Collections; +import java.util.Optional; +import akka.util.ByteString; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.databind.JsonNode; +import play.api.mvc.StatusHeader; +import play.mvc.Result; +import play.twirl.api.Content; + +/* Most of these are stubbed not yes useful imports from scala */ +//import play.api.mvc.Results$; +//import play.core.j.JavaHelpers; +//import play.http.HttpEntity; +//import scala.collection.JavaConverters; +//import scala.compat.java8.OptionConverters; + +import static play.mvc.Http.HeaderNames.LOCATION; +import static play.mvc.Http.Status.*; + +/** Common results. */ +public class Results { + + private static final String UTF8 = "utf-8"; + + // -- Constructor methods + + /** Generates a 501 NOT_IMPLEMENTED simple result. */ + //public static final Result TODO = status(NOT_IMPLEMENTED, views.html.defaultpages.todo.render()); + + // -- Status + + /** + * Generates a simple result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @ + */ + public static StatusHeader status(int status) { + + } + + /** + * Generates a simple result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content + * @ + */ + public static Result status(int status, Content content) { + + } + + /** + * Generates a simple result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content + * @param charset the charset to encode the content with (e.g. "UTF-8") + * @ + */ + public static Result status(int status, Content content, String charset) { + + } + + /** + * Generates a simple result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content. It will be encoded as a UTF-8 string. + * @ + */ + public static Result status(int status, String content) { + + } + + /** + * Generates a simple result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content. + * @param charset the charset in which to encode the content (e.g. "UTF-8") + * @ + */ + public static Result status(int status, String content, String charset) { + + } + + /** + * Generates a simple result with json content and UTF8 encoding. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content as a play-json object + * @ + */ + public static Result status(int status, JsonNode content) { + + } + + /** + * Generates a simple result with json content. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content, as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use status(int, JsonNode, JsonEncoding) + */ + @Deprecated + public static Result status(int status, JsonNode content, String charset) { + + } + + /** + * Generates a simple result with json content. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content, as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result status(int status, JsonNode content, JsonEncoding encoding) { + } + + /** + * Generates a simple result with byte-array content. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content, as a byte array + * @ + */ + public static Result status(int status, byte[] content) { + } + + /** + * Generates a simple result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the result's body content + * @ + */ + public static Result status(int status, ByteString content) { + } + + /** + * Generates a chunked result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the input stream containing data to chunk over + * @ + */ + public static Result status(int status, InputStream content) { + + } + + /** + * Generates a chunked result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result status(int status, InputStream content, long contentLength) { + + } + + /** + * Generates a result with file contents. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the file to send + * @ + */ + public static Result status(int status, File content) { + + } + + /** + * Generates a result with file content. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the file to send + * @param inline true to have it sent with inline Content-Disposition. + * @ + */ + public static Result status(int status, File content, boolean inline) { + + } + + /** + * Generates a result. + * + * @param status the HTTP status for this result e.g. 200 (OK), 404 (NOT_FOUND) + * @param content the file to send + * @param fileName the name that the client should receive this file as + * @ + */ + public static Result status(int status, File content, String fileName) { + + } + + /** + * Generates a 204 No Content result. + * + * @ + */ + public static StatusHeader noContent() { + + } + + ////////////////////////////////////////////////////// + // EVERYTHING BELOW HERE IS GENERATED + // + // See https://github.com/jroper/play-source-generator + ////////////////////////////////////////////////////// + + /** + * Generates a 200 OK result. + * + * @ + */ + public static StatusHeader ok() { + + } + + /** + * Generates a 200 OK result. + * + * @param content the HTTP response body + * @ + */ + public static Result ok(Content content) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result ok(Content content, String charset) { + + } + + /** + * Generates a 200 OK result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result ok(String content) {} + + /** + * Generates a 200 OK result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result ok(String content, String charset) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result ok(JsonNode content) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use ok(JsonNode, JsonEncoding) + */ + @Deprecated + public static Result ok(JsonNode content, String charset) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result ok(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the result's body content + * @ + */ + public static Result ok(byte[] content) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result ok(InputStream content) { + + } + + /** + * Generates a 200 OK result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result ok(InputStream content, long contentLength) { + + } + + /** + * Generates a 200 OK result. + * + * @param content The file to send. + * @ + */ + public static Result ok(File content) { + + } + + /** + * Generates a 200 OK result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result ok(File content, boolean inline) { + + } + + /** + * Generates a 200 OK result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result ok(File content, String filename) { + + } + + /** + * Generates a 201 Created result. + * + * @ + */ + public static StatusHeader created() { + + } + + /** + * Generates a 201 Created result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result created(Content content) { + + } */ + + /** + * Generates a 201 Created result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result created(Content content, String charset) { + + } + + /** + * Generates a 201 Created result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result created(String content) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result created(String content, String charset) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result created(JsonNode content) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use created(JsonNode, JsonEncoding) + */ + @Deprecated + public static Result created(JsonNode content, String charset) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result created(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the result's body content + * @ + */ + public static Result created(byte[] content) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result created(InputStream content) { + + } + + /** + * Generates a 201 Created result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result created(InputStream content, long contentLength) { + + } + + /** + * Generates a 201 Created result. + * + * @param content The file to send. + * @ + */ + public static Result created(File content) { + + } + + /** + * Generates a 201 Created result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result created(File content, boolean inline) { + + } + + /** + * Generates a 201 Created result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result created(File content, String filename) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @ + */ + public static StatusHeader badRequest() { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result badRequest(Content content) { + + } */ + + /** + * Generates a 400 Bad Request result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result badRequest(Content content, String charset) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result badRequest(String content) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result badRequest(String content, String charset) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result badRequest(JsonNode content) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use badRequest(JsonNode, JsonEncoding) + */ + @Deprecated + public static Result badRequest(JsonNode content, String charset) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result badRequest(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the result's body content + * @ + */ + public static Result badRequest(byte[] content) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result badRequest(InputStream content) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result badRequest(InputStream content, long contentLength) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content The file to send. + * @ + */ + public static Result badRequest(File content) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result badRequest(File content, boolean inline) { + + } + + /** + * Generates a 400 Bad Request result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result badRequest(File content, String filename) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @ + */ + public static StatusHeader unauthorized() { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result unauthorized(Content content) { + + } */ + + /** + * Generates a 401 Unauthorized result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result unauthorized(Content content, String charset) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result unauthorized(String content) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result unauthorized(String content, String charset) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result unauthorized(JsonNode content) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use {@link #unauthorized(JsonNode, JsonEncoding)} instead. + */ + @Deprecated + public static Result unauthorized(JsonNode content, String charset) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result unauthorized(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the result's body content + * @ + */ + public static Result unauthorized(byte[] content) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result unauthorized(InputStream content) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result unauthorized(InputStream content, long contentLength) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content The file to send. + * @ + */ + public static Result unauthorized(File content) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result unauthorized(File content, boolean inline) { + + } + + /** + * Generates a 401 Unauthorized result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result unauthorized(File content, String filename) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @ + */ + public static StatusHeader paymentRequired() { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the HTTP response body + * @ + */ + public static Result paymentRequired(Content content) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result paymentRequired(Content content, String charset) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result paymentRequired(String content) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result paymentRequired(String content, String charset) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result paymentRequired(JsonNode content) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use paymentRequired(JsonNode, JsonEncoding) + */ + @Deprecated + public static Result paymentRequired(JsonNode content, String charset) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result paymentRequired(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the result's body content + * @ + */ + public static Result paymentRequired(byte[] content) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result paymentRequired(InputStream content) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result paymentRequired(InputStream content, long contentLength) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content The file to send. + * @ + */ + public static Result paymentRequired(File content) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result paymentRequired(File content, boolean inline) { + + } + + /** + * Generates a 402 Payment Required result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result paymentRequired(File content, String filename) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @ + */ + public static StatusHeader forbidden() { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result forbidden(Content content) { + + } */ + + /** + * Generates a 403 Forbidden result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result forbidden(Content content, String charset) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result forbidden(String content) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result forbidden(String content, String charset) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result forbidden(JsonNode content) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use forbidden(JsonNode, JsonEncoding) + */ + @Deprecated + public static Result forbidden(JsonNode content, String charset) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result forbidden(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the result's body content + * @ + */ + public static Result forbidden(byte[] content) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result forbidden(InputStream content) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result forbidden(InputStream content, long contentLength) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content The file to send. + * @ + */ + public static Result forbidden(File content) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result forbidden(File content, boolean inline) { + + } + + /** + * Generates a 403 Forbidden result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result forbidden(File content, String filename) { + + } + + /** + * Generates a 404 Not Found result. + * + * @ + */ + public static StatusHeader notFound() { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result notFound(Content content) { + + } */ + + /** + * Generates a 404 Not Found result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result notFound(Content content, String charset) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result notFound(String content) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result notFound(String content, String charset) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result notFound(JsonNode content) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use notFound(JsonNode, JsonEncoding + */ + @Deprecated + public static Result notFound(JsonNode content, String charset) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result notFound(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the result's body content + * @ + */ + public static Result notFound(byte[] content) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result notFound(InputStream content) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result notFound(InputStream content, long contentLength) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content The file to send. + * @ + */ + public static Result notFound(File content) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result notFound(File content, boolean inline) { + + } + + /** + * Generates a 404 Not Found result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result notFound(File content, String filename) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @ + */ + public static StatusHeader notAcceptable() { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result notAcceptable(Content content) { + + } */ + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result notAcceptable(Content content, String charset) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result notAcceptable(String content) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result notAcceptable(String content, String charset) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result notAcceptable(JsonNode content) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result notAcceptable(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the result's body content + * @ + */ + public static Result notAcceptable(byte[] content) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result notAcceptable(InputStream content) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result notAcceptable(InputStream content, long contentLength) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content The file to send. + * @ + */ + public static Result notAcceptable(File content) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result notAcceptable(File content, boolean inline) { + + } + + /** + * Generates a 406 Not Acceptable result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result notAcceptable(File content, String filename) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @ + */ + public static StatusHeader unsupportedMediaType() { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result unsupportedMediaType(Content content) { + + } */ + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result unsupportedMediaType(Content content, String charset) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result unsupportedMediaType(String content) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result unsupportedMediaType(String content, String charset) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result unsupportedMediaType(JsonNode content) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result unsupportedMediaType(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the result's body content + * @ + */ + public static Result unsupportedMediaType(byte[] content) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result unsupportedMediaType(InputStream content) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result unsupportedMediaType(InputStream content, long contentLength) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content The file to send. + * @ + */ + public static Result unsupportedMediaType(File content) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result unsupportedMediaType(File content, boolean inline) { + + } + + /** + * Generates a 415 Unsupported Media Type result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result unsupportedMediaType(File content, String filename) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @ + */ + public static StatusHeader internalServerError() { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the HTTP response body + * @ + */ +/* public static Result internalServerError(Content content) { + + } */ + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result internalServerError(Content content, String charset) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content HTTP response body, encoded as a UTF-8 string + * @ + */ + public static Result internalServerError(String content) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the HTTP response body + * @param charset the charset into which the content should be encoded (e.g. "UTF-8") + * @ + */ + public static Result internalServerError(String content, String charset) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the result's body content as a play-json object. It will be encoded as a UTF-8 + * string. + * @ + */ + public static Result internalServerError(JsonNode content) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the result's body content as a play-json object + * @param charset the charset into which the json should be encoded + * @ + * @deprecated As of 2.6.0, use internalServerError(JsonNode, JsonEncoding) + */ + @Deprecated + public static Result internalServerError(JsonNode content, String charset) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the result's body content as a play-json object + * @param encoding the encoding into which the json should be encoded + * @ + */ + public static Result internalServerError(JsonNode content, JsonEncoding encoding) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the result's body content + * @ + */ + public static Result internalServerError(byte[] content) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the input stream containing data to chunk over + * @ + */ + public static Result internalServerError(InputStream content) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content the input stream containing data to chunk over + * @param contentLength the length of the provided content in bytes. + * @ + */ + public static Result internalServerError(InputStream content, long contentLength) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content The file to send. + * @ + */ + public static Result internalServerError(File content) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content The file to send. + * @param inline Whether the file should be sent inline, or as an attachment. + * @ + */ + public static Result internalServerError(File content, boolean inline) { + + } + + /** + * Generates a 500 Internal Server Error result. + * + * @param content The file to send. + * @param filename The name to send the file as. + * @ + */ + public static Result internalServerError(File content, String filename) { + + } + + /** + * Generates a 301 Moved Permanently result. + * + * @param url The url to redirect. + * @ + */ + public static Result movedPermanently(String url) { + + } + + /** + * Generates a 301 Moved Permanently result. + * + * @param call Call defining the url to redirect (typically comes from reverse router). + * @ + */ + public static Result movedPermanently(Call call) { + + } + + /** + * Generates a 302 Found result. + * + * @param url The url to redirect. + * @ + */ + public static Result found(String url) { + + } + + /** + * Generates a 302 Found result. + * + * @param call Call defining the url to redirect (typically comes from reverse router). + * @ + */ + public static Result found(Call call) { + + } + + /** + * Generates a 303 See Other result. + * + * @param url The url to redirect. + * @ + */ + public static Result seeOther(String url) { + + } + + /** + * Generates a 303 See Other result. + * + * @param call Call defining the url to redirect (typically comes from reverse router). + * @ + */ + public static Result seeOther(Call call) { + + } + + /** + * Generates a 303 See Other result. + * + * @param url The url to redirect. + * @ + */ + public static Result redirect(String url) { + } + + /** + * Generates a 303 See Other result. + * + * @param call Call defining the url to redirect (typically comes from reverse router). + * @ + */ + public static Result redirect(Call call) { + + } + + /** + * Generates a 307 Temporary Redirect result. + * + * @param url The url to redirect. + * @ + */ + public static Result temporaryRedirect(String url) { + + } + + /** + * Generates a 307 Temporary Redirect result. + * + * @param call Call defining the url to redirect (typically comes from reverse router). + * @ + */ + public static Result temporaryRedirect(Call call) { + + } + + /** + * Generates a 308 Permanent Redirect result. + * + * @param url The url to redirect. + * @ + */ + public static Result permanentRedirect(String url) { + + } + + /** + * Generates a 308 Permanent Redirect result. + * + * @param call Call defining the url to redirect (typically comes from reverse router). + * @ + */ + public static Result permanentRedirect(Call call) { + + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/playframework-2.6.x/play/twirl/api/Content.java b/java/ql/test/stubs/playframework-2.6.x/play/twirl/api/Content.java new file mode 100644 index 00000000000..0b861b97604 --- /dev/null +++ b/java/ql/test/stubs/playframework-2.6.x/play/twirl/api/Content.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2009-2013 Typesafe Inc. + */ +package play.twirl.api; + +/** + * Play twirl Content result. (Part of scala) + */ +public class Content { + +} \ No newline at end of file From 33f7d52a46806b07189e4e9da675f72fe4483311 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Fri, 18 Sep 2020 17:33:39 +0530 Subject: [PATCH 002/757] Naming Fixes --- .../src/semmle/code/java/frameworks/play/PlayController.qll | 2 +- .../src/semmle/code/java/frameworks/play/PlayMVCResult.qll | 4 ++-- .../src/semmle/code/java/frameworks/play/PlayMVCResults.qll | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll index 5592f05ca69..ff68d006e18 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll @@ -35,7 +35,7 @@ class PlayControllerActionMethod extends Method { this = controller.getAMethod() and ( this.getReturnType() instanceof PlayAsyncResultPromise or - this.getReturnType() instanceof PlayMVCResult or + this.getReturnType() instanceof PlayMVCResultClass or this.getReturnType() instanceof PlayAsyncResultCompletionStage ) ) diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll index 60729bf7041..2a0ce62e36e 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll @@ -6,6 +6,6 @@ import java * @description Gets the play.mvc.Result class - Used to set a HTTP result with a status code, a set of HTTP headers and a body to be sent to the web client. * (https://www.playframework.com/documentation/2.8.x/JavaActions) */ -class PlayMVCResult extends Class { - PlayMVCResult() { this.hasQualifiedName("play.mvc", "Result") } +class PlayMVCResultClass extends Class { + PlayMVCResultClass() { this.hasQualifiedName("play.mvc", "Result") } } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll index 1c3d46c3ae4..28274de0312 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll @@ -6,8 +6,8 @@ import java * @description Gets the play.mvc.Results class - Helper utilities to generate results * (https://www.playframework.com/documentation/2.8.x/JavaActions) */ -class PlayMVCResults extends Class { - PlayMVCResults() { this.hasQualifiedName("play.mvc", "Results") } +class PlayMVCResultsClass extends Class { + PlayMVCResultsClass() { this.hasQualifiedName("play.mvc", "Results") } } /** @@ -17,7 +17,7 @@ class PlayMVCResults extends Class { * (https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html) */ class PlayHTTPResultsMethods extends Method { - PlayHTTPResultsMethods() { this.getDeclaringType() instanceof PlayMVCResults } + PlayHTTPResultsMethods() { this.getDeclaringType() instanceof PlayMVCResultsClass } /** * Gets all references to play.mvc.Results ok method From f7d63f8666779cfb7ccb7cc3cf1efa26d54d16df Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Thu, 22 Oct 2020 20:21:47 +0530 Subject: [PATCH 003/757] Feedback incorporation and documentation updates --- .../semmle/code/java/dataflow/FlowSources.qll | 14 ++++++ .../java/frameworks/play/PlayAddCSRFToken.qll | 6 +++ .../java/frameworks/play/PlayAsyncResult.qll | 12 +++++ .../java/frameworks/play/PlayBodyParser.qll | 6 +++ .../java/frameworks/play/PlayController.qll | 44 +++++++++++++++++++ .../frameworks/play/PlayHTTPRequestHeader.qll | 21 +++++++++ .../java/frameworks/play/PlayMVCResult.qll | 4 ++ .../java/frameworks/play/PlayMVCResults.qll | 28 ++++++++++++ 8 files changed, 135 insertions(+) diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index 33fac24cdbd..52bd93fae24 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -108,6 +108,7 @@ private class MessageBodyReaderParameterSource extends RemoteFlowSource { override string getSourceType() { result = "MessageBodyReader parameter" } } +<<<<<<< HEAD private class SpringMultipartRequestSource extends RemoteFlowSource { SpringMultipartRequestSource() { exists(MethodAccess ma, Method m | @@ -124,6 +125,11 @@ private class SpringMultipartRequestSource extends RemoteFlowSource { override string getSourceType() { result = "Spring MultipartRequest getter" } } +class PlayParameterSource extends RemoteFlowSource { + PlayParameterSource() { + exists(PlayActionMethodQueryParameter p | p = this.asParameter()) or + exists(PlayMVCHTTPRequestHeaderMethods m | m.getQueryString().getAnArgument() = this.asExpr()) +======= class PlayParameterSource extends RemoteFlowSource { PlayParameterSource() { exists(PlayActionQueryParameter p | p = this.asParameter()) @@ -131,6 +137,7 @@ class PlayParameterSource extends RemoteFlowSource { exists(PlayHTTPRequestHeaderMethods m | m.hasName("getQueryString") and m.getAParameter() = this.asParameter() ) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } override string getSourceType() { result = "Play Query Parameters" } @@ -279,10 +286,17 @@ private class RemoteTaintedMethod extends Method { } } +<<<<<<< HEAD +private class PlayRequestGetMethod extends Method { + PlayRequestGetMethod() { + this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader and + this.hasName(["header", "getHeader"]) +======= private class PlayRequestGetMethod extends PlayHTTPRequestHeaderMethods { PlayRequestGetMethod() { this.hasName("Header") or this.hasName("getQueryString") +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll index 391106aadc0..a3e15328ed6 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll @@ -1,10 +1,16 @@ import java /** +<<<<<<< HEAD + * Play Framework AddCSRFToken Annotation + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaCsrf +======= * Play Framework AddCSRFToken * * @description Gets the methods using AddCSRFToken annotation. * (https://www.playframework.com/documentation/2.6.x/JavaBodyParsers#Choosing-an-explicit-body-parser) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayAddCSRFTokenAnnotation extends Annotation { PlayAddCSRFTokenAnnotation() { diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll index 1eb0108f0d3..e8ed9f09436 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll @@ -1,10 +1,16 @@ import java /** +<<<<<<< HEAD + * Play Framework Async Promise - Gets the Promise Generic Member/Type of (play.libs.F) + * + * Documentation: https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html +======= * Play Framework Async Promise of Generic Result * * @description Gets the Promise Generic Type of (play.libs.F), This is async in 2.6x and below. * (https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayAsyncResultPromise extends Member { PlayAsyncResultPromise() { @@ -17,10 +23,16 @@ class PlayAsyncResultPromise extends Member { } /** +<<<<<<< HEAD + * Play Framework Async Generic Result - Gets the CompletionStage Generic Type of (java.util.concurrent) + * + * Documentation: https://www.playframework.com/documentation/2.6.x/JavaAsync +======= * Play Framework Async Generic Result extending generic promise API called CompletionStage. * * @description Gets the CompletionStage Generic Type of (java.util.concurrent) * (https://www.playframework.com/documentation/2.6.x/JavaAsync) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayAsyncResultCompletionStage extends Type { PlayAsyncResultCompletionStage() { diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll index df16c4d137e..1a8a9aa597b 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll @@ -1,10 +1,16 @@ import java /** +<<<<<<< HEAD + * Play Framework Explicit Body Parser Annotation + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser +======= * Play Framework Explicit Body Parser * * @description Gets the methods using the explicit body parser annotation. The methods are usually controller action methods * (https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayBodyParserAnnotation extends Annotation { PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll index ff68d006e18..45489c4e216 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll @@ -4,17 +4,24 @@ import semmle.code.java.frameworks.play.PlayMVCResult /** * Play MVC Framework Controller +<<<<<<< HEAD +======= * * @description Gets the play.mvc.Controller class +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCControllerClass extends Class { PlayMVCControllerClass() { this.hasQualifiedName("play.mvc", "Controller") } } /** +<<<<<<< HEAD + * Play Framework Controllers which extends/implements PlayMVCController recursively - Used to find all Controllers +======= * Play Framework Controller which extends/implements * * @description Gets the classes which extends play.mvc.controller rescursively. +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayController extends Class { PlayController() { @@ -23,11 +30,32 @@ class PlayController extends Class { } /** +<<<<<<< HEAD + * Play Framework Controller Action Methods - Mappings to route files + * + * Sample Route - `POST /login @com.linkedin.Application.login()` + * + * Example - class get's `index` & `login` as valid action methods. + * ``` + * public class Application extends Controller { + * public Result index(String username, String password) { + * return ok("It works!"); + * } + * + * public Result login() { + * return ok("Log me In!"); + * } + * } + * ``` + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions +======= * Play Framework Controller Action Methods * * @description Gets the controller action methods defined against it. * (https://www.playframework.com/documentation/2.8.x/JavaActions) * @tip Checking for Public methods usually retrieves direct controller mapped methods defined in routes. +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayControllerActionMethod extends Method { PlayControllerActionMethod() { @@ -43,10 +71,26 @@ class PlayControllerActionMethod extends Method { } /** +<<<<<<< HEAD + * Play Action-Method parameters. These are a source of user input + * + * Example - Class get's `username` & `password` as valid parameters + * ``` + * public class Application extends Controller { + * public Result index(String username, String password) { + * return ok("It works!"); + * } + * } + * ``` + */ +class PlayActionMethodQueryParameter extends Parameter { + PlayActionMethodQueryParameter() { +======= * Play Action-Method parameters, these are essentially part of routes. */ class PlayActionQueryParameter extends Parameter { PlayActionQueryParameter() { +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 exists(PlayControllerActionMethod a | a.isPublic() and this = a.getAParameter() diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll index 91e06af4802..68a8e852afa 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll @@ -1,15 +1,35 @@ import java /** +<<<<<<< HEAD + * Play MVC Framework HTTP Request Header Class +======= * Play MVC Framework HTTP Request Header * * @description Member of play.mvc.HTTP. Gets the play.mvc.HTTP$RequestHeader class/interface +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCHTTPRequestHeader extends RefType { PlayMVCHTTPRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } } /** +<<<<<<< HEAD + * Play Framework HTTPRequestHeader Methods - `headers`, `getQueryString`, `getHeader` + * + * Documentation: https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html + */ +class PlayMVCHTTPRequestHeaderMethods extends Method { + PlayMVCHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } + + /** + * Gets all references to play.mvc.HTTP.RequestHeader `getQueryString` method + */ + MethodAccess getQueryString() { + this.hasName("getQueryString") and result = this.getAReference() + } + +======= * Play Framework HTTP$RequestHeader Methods * * @description Gets the methods of play.mvc.HTTP$RequestHeader like - headers, getQueryString, getHeader, uri @@ -17,4 +37,5 @@ class PlayMVCHTTPRequestHeader extends RefType { */ class PlayHTTPRequestHeaderMethods extends Method { PlayHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll index 2a0ce62e36e..abd697aa3ab 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll @@ -1,10 +1,14 @@ import java /** +<<<<<<< HEAD + * Play MVC Framework Result Class +======= * Play MVC Framework Result * * @description Gets the play.mvc.Result class - Used to set a HTTP result with a status code, a set of HTTP headers and a body to be sent to the web client. * (https://www.playframework.com/documentation/2.8.x/JavaActions) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCResultClass extends Class { PlayMVCResultClass() { this.hasQualifiedName("play.mvc", "Result") } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll index 28274de0312..ac6e4766475 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll @@ -1,16 +1,43 @@ import java /** +<<<<<<< HEAD + * Play MVC Framework Results Class + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions +======= * Play MVC Framework Results * * @description Gets the play.mvc.Results class - Helper utilities to generate results * (https://www.playframework.com/documentation/2.8.x/JavaActions) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCResultsClass extends Class { PlayMVCResultsClass() { this.hasQualifiedName("play.mvc", "Results") } } /** +<<<<<<< HEAD + * Play Framework mvc.Results Methods - `ok`, `status`, `redirect` + * + * Documentation: https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html + */ +class PlayMVCResultsMethods extends Method { + PlayMVCResultsMethods() { this.getDeclaringType() instanceof PlayMVCResultsClass } + + /** + * Gets all references to play.mvc.Results `ok` method + */ + MethodAccess getAnOkAccess() { + this.hasName("ok") and result = this.getAReference() + } + + /** + * Gets all references to play.mvc.Results `redirect` method + */ + MethodAccess getARedirectAccess() { + this.hasName("redirect") and result = this.getAReference() +======= * Play Framework mvc.Results Methods * * @description Gets the methods of play.mvc.Results like - ok, status, redirect etc. @@ -31,5 +58,6 @@ class PlayHTTPResultsMethods extends Method { */ MethodAccess redirect() { exists(MethodAccess ma | ma = this.getAReference() and this.hasName("redirect") | result = ma) +>>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } } From 5c256dadc8abe3dde31a668fc8a6f8bde0a84a88 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Thu, 22 Oct 2020 20:27:38 +0530 Subject: [PATCH 004/757] Feedback incorporation and documentation updates --- .../semmle/code/java/dataflow/FlowSources.qll | 10 ------- .../java/frameworks/play/PlayAddCSRFToken.qll | 7 ----- .../java/frameworks/play/PlayAsyncResult.qll | 14 --------- .../java/frameworks/play/PlayBodyParser.qll | 7 ----- .../java/frameworks/play/PlayController.qll | 26 ---------------- .../java/frameworks/play/PlayMVCResult.qll | 7 ----- .../java/frameworks/play/PlayMVCResults.qll | 30 ------------------- 7 files changed, 101 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index 52bd93fae24..a3a806ac3de 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -108,7 +108,6 @@ private class MessageBodyReaderParameterSource extends RemoteFlowSource { override string getSourceType() { result = "MessageBodyReader parameter" } } -<<<<<<< HEAD private class SpringMultipartRequestSource extends RemoteFlowSource { SpringMultipartRequestSource() { exists(MethodAccess ma, Method m | @@ -129,15 +128,6 @@ class PlayParameterSource extends RemoteFlowSource { PlayParameterSource() { exists(PlayActionMethodQueryParameter p | p = this.asParameter()) or exists(PlayMVCHTTPRequestHeaderMethods m | m.getQueryString().getAnArgument() = this.asExpr()) -======= -class PlayParameterSource extends RemoteFlowSource { - PlayParameterSource() { - exists(PlayActionQueryParameter p | p = this.asParameter()) - or - exists(PlayHTTPRequestHeaderMethods m | - m.hasName("getQueryString") and m.getAParameter() = this.asParameter() - ) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } override string getSourceType() { result = "Play Query Parameters" } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll index a3e15328ed6..3b289e23c37 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll @@ -1,16 +1,9 @@ import java /** -<<<<<<< HEAD * Play Framework AddCSRFToken Annotation * * Documentation: https://www.playframework.com/documentation/2.8.x/JavaCsrf -======= - * Play Framework AddCSRFToken - * - * @description Gets the methods using AddCSRFToken annotation. - * (https://www.playframework.com/documentation/2.6.x/JavaBodyParsers#Choosing-an-explicit-body-parser) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayAddCSRFTokenAnnotation extends Annotation { PlayAddCSRFTokenAnnotation() { diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll index e8ed9f09436..27edc7a2521 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll @@ -1,16 +1,9 @@ import java /** -<<<<<<< HEAD * Play Framework Async Promise - Gets the Promise Generic Member/Type of (play.libs.F) * * Documentation: https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html -======= - * Play Framework Async Promise of Generic Result - * - * @description Gets the Promise Generic Type of (play.libs.F), This is async in 2.6x and below. - * (https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayAsyncResultPromise extends Member { PlayAsyncResultPromise() { @@ -23,16 +16,9 @@ class PlayAsyncResultPromise extends Member { } /** -<<<<<<< HEAD * Play Framework Async Generic Result - Gets the CompletionStage Generic Type of (java.util.concurrent) * * Documentation: https://www.playframework.com/documentation/2.6.x/JavaAsync -======= - * Play Framework Async Generic Result extending generic promise API called CompletionStage. - * - * @description Gets the CompletionStage Generic Type of (java.util.concurrent) - * (https://www.playframework.com/documentation/2.6.x/JavaAsync) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayAsyncResultCompletionStage extends Type { PlayAsyncResultCompletionStage() { diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll index 1a8a9aa597b..167bea85d4e 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll @@ -1,16 +1,9 @@ import java /** -<<<<<<< HEAD * Play Framework Explicit Body Parser Annotation * * Documentation: https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser -======= - * Play Framework Explicit Body Parser - * - * @description Gets the methods using the explicit body parser annotation. The methods are usually controller action methods - * (https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayBodyParserAnnotation extends Annotation { PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll index 45489c4e216..af05d96de23 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll @@ -4,24 +4,13 @@ import semmle.code.java.frameworks.play.PlayMVCResult /** * Play MVC Framework Controller -<<<<<<< HEAD -======= - * - * @description Gets the play.mvc.Controller class ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCControllerClass extends Class { PlayMVCControllerClass() { this.hasQualifiedName("play.mvc", "Controller") } } /** -<<<<<<< HEAD * Play Framework Controllers which extends/implements PlayMVCController recursively - Used to find all Controllers -======= - * Play Framework Controller which extends/implements - * - * @description Gets the classes which extends play.mvc.controller rescursively. ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayController extends Class { PlayController() { @@ -30,7 +19,6 @@ class PlayController extends Class { } /** -<<<<<<< HEAD * Play Framework Controller Action Methods - Mappings to route files * * Sample Route - `POST /login @com.linkedin.Application.login()` @@ -49,13 +37,6 @@ class PlayController extends Class { * ``` * * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions -======= - * Play Framework Controller Action Methods - * - * @description Gets the controller action methods defined against it. - * (https://www.playframework.com/documentation/2.8.x/JavaActions) - * @tip Checking for Public methods usually retrieves direct controller mapped methods defined in routes. ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayControllerActionMethod extends Method { PlayControllerActionMethod() { @@ -71,7 +52,6 @@ class PlayControllerActionMethod extends Method { } /** -<<<<<<< HEAD * Play Action-Method parameters. These are a source of user input * * Example - Class get's `username` & `password` as valid parameters @@ -85,12 +65,6 @@ class PlayControllerActionMethod extends Method { */ class PlayActionMethodQueryParameter extends Parameter { PlayActionMethodQueryParameter() { -======= - * Play Action-Method parameters, these are essentially part of routes. - */ -class PlayActionQueryParameter extends Parameter { - PlayActionQueryParameter() { ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 exists(PlayControllerActionMethod a | a.isPublic() and this = a.getAParameter() diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll index abd697aa3ab..027f8fe0907 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll @@ -1,14 +1,7 @@ import java /** -<<<<<<< HEAD * Play MVC Framework Result Class -======= - * Play MVC Framework Result - * - * @description Gets the play.mvc.Result class - Used to set a HTTP result with a status code, a set of HTTP headers and a body to be sent to the web client. - * (https://www.playframework.com/documentation/2.8.x/JavaActions) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCResultClass extends Class { PlayMVCResultClass() { this.hasQualifiedName("play.mvc", "Result") } diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll index ac6e4766475..c03a9358ce5 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll @@ -1,23 +1,15 @@ import java /** -<<<<<<< HEAD * Play MVC Framework Results Class * * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions -======= - * Play MVC Framework Results - * - * @description Gets the play.mvc.Results class - Helper utilities to generate results - * (https://www.playframework.com/documentation/2.8.x/JavaActions) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCResultsClass extends Class { PlayMVCResultsClass() { this.hasQualifiedName("play.mvc", "Results") } } /** -<<<<<<< HEAD * Play Framework mvc.Results Methods - `ok`, `status`, `redirect` * * Documentation: https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html @@ -37,27 +29,5 @@ class PlayMVCResultsMethods extends Method { */ MethodAccess getARedirectAccess() { this.hasName("redirect") and result = this.getAReference() -======= - * Play Framework mvc.Results Methods - * - * @description Gets the methods of play.mvc.Results like - ok, status, redirect etc. - * (https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html) - */ -class PlayHTTPResultsMethods extends Method { - PlayHTTPResultsMethods() { this.getDeclaringType() instanceof PlayMVCResultsClass } - - /** - * Gets all references to play.mvc.Results ok method - */ - MethodAccess ok() { - exists(MethodAccess ma | ma = this.getAReference() and this.hasName("ok") | result = ma) - } - - /** - * Gets all references to play.mvc.Results redirect method - */ - MethodAccess redirect() { - exists(MethodAccess ma | ma = this.getAReference() and this.hasName("redirect") | result = ma) ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } } From 518de822e1be433eb2e4685520dd7b8c4bb1f01d Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Thu, 22 Oct 2020 20:47:11 +0530 Subject: [PATCH 005/757] updates --- java/ql/src/semmle/code/java/dataflow/FlowSources.qll | 7 ------- 1 file changed, 7 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index a3a806ac3de..7db5acd4073 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -276,17 +276,10 @@ private class RemoteTaintedMethod extends Method { } } -<<<<<<< HEAD private class PlayRequestGetMethod extends Method { PlayRequestGetMethod() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader and this.hasName(["header", "getHeader"]) -======= -private class PlayRequestGetMethod extends PlayHTTPRequestHeaderMethods { - PlayRequestGetMethod() { - this.hasName("Header") or - this.hasName("getQueryString") ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } } From d216dcdee05068e2e0293485821c10f7ea3a20bf Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Thu, 22 Oct 2020 22:25:36 +0530 Subject: [PATCH 006/757] updates & conflict marker removal --- .../frameworks/play/PlayHTTPRequestHeader.qll | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll index 68a8e852afa..539253dab4a 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll @@ -1,20 +1,13 @@ import java /** -<<<<<<< HEAD * Play MVC Framework HTTP Request Header Class -======= - * Play MVC Framework HTTP Request Header - * - * @description Member of play.mvc.HTTP. Gets the play.mvc.HTTP$RequestHeader class/interface ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 */ class PlayMVCHTTPRequestHeader extends RefType { PlayMVCHTTPRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } } /** -<<<<<<< HEAD * Play Framework HTTPRequestHeader Methods - `headers`, `getQueryString`, `getHeader` * * Documentation: https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html @@ -29,13 +22,4 @@ class PlayMVCHTTPRequestHeaderMethods extends Method { this.hasName("getQueryString") and result = this.getAReference() } -======= - * Play Framework HTTP$RequestHeader Methods - * - * @description Gets the methods of play.mvc.HTTP$RequestHeader like - headers, getQueryString, getHeader, uri - * (https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html) - */ -class PlayHTTPRequestHeaderMethods extends Method { - PlayHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } ->>>>>>> fa523e456f96493dcc08b819ad4bd620cca789b8 } From 5d5b84974bd8bf13923035851f2c2379845ad930 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Thu, 22 Oct 2020 22:29:43 +0530 Subject: [PATCH 007/757] Play remote source update to return functionaccessexpr --- java/ql/src/semmle/code/java/dataflow/FlowSources.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index 7db5acd4073..e17e60f4ca6 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -127,7 +127,7 @@ private class SpringMultipartRequestSource extends RemoteFlowSource { class PlayParameterSource extends RemoteFlowSource { PlayParameterSource() { exists(PlayActionMethodQueryParameter p | p = this.asParameter()) or - exists(PlayMVCHTTPRequestHeaderMethods m | m.getQueryString().getAnArgument() = this.asExpr()) + exists(PlayMVCHTTPRequestHeaderMethods m | m.getQueryString() = this.asExpr()) } override string getSourceType() { result = "Play Query Parameters" } From 27c554c164ccde69133b93d96d60232e829f1a2d Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Sat, 24 Oct 2020 11:56:06 +0530 Subject: [PATCH 008/757] feedback integration - Move all files to Play.qll, improvements to add methods to remotetainted method for play --- .../semmle/code/java/dataflow/FlowSources.qll | 5 +- .../semmle/code/java/frameworks/play/Play.qll | 172 +++++++++++++++++- .../java/frameworks/play/PlayAddCSRFToken.qll | 12 -- .../java/frameworks/play/PlayAsyncResult.qll | 28 --- .../java/frameworks/play/PlayBodyParser.qll | 10 - .../java/frameworks/play/PlayController.qll | 73 -------- .../frameworks/play/PlayHTTPRequestHeader.qll | 25 --- .../java/frameworks/play/PlayMVCResult.qll | 8 - .../java/frameworks/play/PlayMVCResults.qll | 33 ---- 9 files changed, 166 insertions(+), 200 deletions(-) delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayController.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index e17e60f4ca6..5bb471259b3 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -17,8 +17,7 @@ import semmle.code.java.frameworks.android.WebView import semmle.code.java.frameworks.JaxWS import semmle.code.java.frameworks.javase.WebSocket import semmle.code.java.frameworks.android.Intent -import semmle.code.java.frameworks.play.PlayController -import semmle.code.java.frameworks.play.PlayHTTPRequestHeader +import semmle.code.java.frameworks.play.Play import semmle.code.java.frameworks.spring.SpringWeb import semmle.code.java.frameworks.spring.SpringController import semmle.code.java.frameworks.spring.SpringWebClient @@ -279,7 +278,7 @@ private class RemoteTaintedMethod extends Method { private class PlayRequestGetMethod extends Method { PlayRequestGetMethod() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader and - this.hasName(["header", "getHeader"]) + this.hasName(["queryString","getQueryString","header", "getHeader"]) } } diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll index 18f6dabf468..252379674b2 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/Play.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -1,9 +1,165 @@ import java -import semmle.code.java.dataflow.FlowSources -import semmle.code.java.frameworks.play.PlayController -import semmle.code.java.frameworks.play.PlayAddCSRFToken -import semmle.code.java.frameworks.play.PlayAsyncResult -import semmle.code.java.frameworks.play.PlayBodyParser -import semmle.code.java.frameworks.play.PlayHTTPRequestHeader -import semmle.code.java.frameworks.play.PlayMVCResult -import semmle.code.java.frameworks.play.PlayMVCResults + +/** + * Play MVC Framework Result Class + */ +class PlayMVCResultClass extends Class { + PlayMVCResultClass() { this.hasQualifiedName("play.mvc", "Result") } +} + +/** + * Play MVC Framework Results Class + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions + */ +class PlayMVCResultsClass extends Class { + PlayMVCResultsClass() { this.hasQualifiedName("play.mvc", "Results") } +} + +/** + * Play MVC Framework HTTP Request Header Class + */ +class PlayMVCHTTPRequestHeader extends RefType { + PlayMVCHTTPRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } +} + +/** + * Play Framework Explicit Body Parser Annotation + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser + */ +class PlayBodyParserAnnotation extends Annotation { + PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } +} + +/** + * Play Framework AddCSRFToken Annotation + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaCsrf + */ +class PlayAddCSRFTokenAnnotation extends Annotation { + PlayAddCSRFTokenAnnotation() { + this.getType().hasQualifiedName("play.filters.csrf", "AddCSRFToken") + } +} + +/** + * Play Framework Async Promise - Gets the Promise Generic Member/Type of (play.libs.F) + * + * Documentation: https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html + */ +class PlayAsyncResultPromise extends Member { + PlayAsyncResultPromise() { + exists(Class c | + c.hasQualifiedName("play.libs", "F") and + this = c.getAMember() and + this.getQualifiedName() = "F.Promise" + ) + } +} + +/** + * Play Framework Async Generic Result - Gets the CompletionStage Generic Type of (java.util.concurrent) + * + * Documentation: https://www.playframework.com/documentation/2.6.x/JavaAsync + */ +class PlayAsyncResultCompletionStage extends Type { + PlayAsyncResultCompletionStage() { + this.hasName("CompletionStage") and + this.getCompilationUnit().getPackage().hasName("java.util.concurrent") + } +} + +/** + * Play Framework Controllers which extends PlayMVCController recursively - Used to find all Controllers + */ +class PlayController extends Class { + PlayController() { + this.extendsOrImplements*(any(Class t | t.hasQualifiedName("play.mvc", "Controller"))) + } +} + +/** + * Play Framework Controller Action Methods - Mappings to route files + * + * Sample Route - `POST /login @com.company.Application.login()` + * + * Example - class get's `index` & `login` as valid action methods. + * ``` + * public class Application extends Controller { + * public Result index(String username, String password) { + * return ok("It works!"); + * } + * + * public Result login() { + * return ok("Log me In!"); + * } + * } + * ``` + * + * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions + */ +class PlayControllerActionMethod extends Method { + PlayControllerActionMethod() { + this = any(PlayController c).getAMethod() and + ( + this.getReturnType() instanceof PlayAsyncResultPromise or + this.getReturnType() instanceof PlayMVCResultClass or + this.getReturnType() instanceof PlayAsyncResultCompletionStage + ) + } +} + +/** + * Play Action-Method parameters. These are a source of user input + * + * Example - Class get's `username` & `password` as valid parameters + * ``` + * public class Application extends Controller { + * public Result index(String username, String password) { + * return ok("It works!"); + * } + * } + * ``` + */ +class PlayActionMethodQueryParameter extends Parameter { + PlayActionMethodQueryParameter() { + exists(PlayControllerActionMethod a | + a.isPublic() and + this = a.getAParameter() + ) + } +} + +/** + * Play Framework HTTPRequestHeader Methods - `headers`, `getQueryString`, `getHeader` + * + * Documentation: https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html + */ +class PlayMVCHTTPRequestHeaderMethods extends Method { + PlayMVCHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } + + /** + * Gets all references to play.mvc.HTTP.RequestHeader `getQueryString` method + */ + MethodAccess getQueryString() { this.hasName("getQueryString") and result = this.getAReference() } +} + +/** + * Play Framework mvc.Results Methods - `ok`, `status`, `redirect` + * + * Documentation: https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html + */ +class PlayMVCResultsMethods extends Method { + PlayMVCResultsMethods() { this.getDeclaringType() instanceof PlayMVCResultsClass } + + /** + * Gets all references to play.mvc.Results `ok` method + */ + MethodAccess getAnOkAccess() { this.hasName("ok") and result = this.getAReference() } + + /** + * Gets all references to play.mvc.Results `redirect` method + */ + MethodAccess getARedirectAccess() { this.hasName("redirect") and result = this.getAReference() } +} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll deleted file mode 100644 index 3b289e23c37..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayAddCSRFToken.qll +++ /dev/null @@ -1,12 +0,0 @@ -import java - -/** - * Play Framework AddCSRFToken Annotation - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaCsrf - */ -class PlayAddCSRFTokenAnnotation extends Annotation { - PlayAddCSRFTokenAnnotation() { - this.getType().hasQualifiedName("play.filters.csrf", "AddCSRFToken") - } -} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll deleted file mode 100644 index 27edc7a2521..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayAsyncResult.qll +++ /dev/null @@ -1,28 +0,0 @@ -import java - -/** - * Play Framework Async Promise - Gets the Promise Generic Member/Type of (play.libs.F) - * - * Documentation: https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html - */ -class PlayAsyncResultPromise extends Member { - PlayAsyncResultPromise() { - exists(Class c | - c.hasQualifiedName("play.libs", "F") and - this = c.getAMember() and - this.getQualifiedName() = "F.Promise" - ) - } -} - -/** - * Play Framework Async Generic Result - Gets the CompletionStage Generic Type of (java.util.concurrent) - * - * Documentation: https://www.playframework.com/documentation/2.6.x/JavaAsync - */ -class PlayAsyncResultCompletionStage extends Type { - PlayAsyncResultCompletionStage() { - this.hasName("CompletionStage") and - this.getCompilationUnit().getPackage().hasName("java.util.concurrent") - } -} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll deleted file mode 100644 index 167bea85d4e..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayBodyParser.qll +++ /dev/null @@ -1,10 +0,0 @@ -import java - -/** - * Play Framework Explicit Body Parser Annotation - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser - */ -class PlayBodyParserAnnotation extends Annotation { - PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } -} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll deleted file mode 100644 index af05d96de23..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayController.qll +++ /dev/null @@ -1,73 +0,0 @@ -import java -import semmle.code.java.frameworks.play.PlayAsyncResult -import semmle.code.java.frameworks.play.PlayMVCResult - -/** - * Play MVC Framework Controller - */ -class PlayMVCControllerClass extends Class { - PlayMVCControllerClass() { this.hasQualifiedName("play.mvc", "Controller") } -} - -/** - * Play Framework Controllers which extends/implements PlayMVCController recursively - Used to find all Controllers - */ -class PlayController extends Class { - PlayController() { - exists(Class t | this.extendsOrImplements*(t) and t instanceof PlayMVCControllerClass) - } -} - -/** - * Play Framework Controller Action Methods - Mappings to route files - * - * Sample Route - `POST /login @com.linkedin.Application.login()` - * - * Example - class get's `index` & `login` as valid action methods. - * ``` - * public class Application extends Controller { - * public Result index(String username, String password) { - * return ok("It works!"); - * } - * - * public Result login() { - * return ok("Log me In!"); - * } - * } - * ``` - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions - */ -class PlayControllerActionMethod extends Method { - PlayControllerActionMethod() { - exists(PlayController controller | - this = controller.getAMethod() and - ( - this.getReturnType() instanceof PlayAsyncResultPromise or - this.getReturnType() instanceof PlayMVCResultClass or - this.getReturnType() instanceof PlayAsyncResultCompletionStage - ) - ) - } -} - -/** - * Play Action-Method parameters. These are a source of user input - * - * Example - Class get's `username` & `password` as valid parameters - * ``` - * public class Application extends Controller { - * public Result index(String username, String password) { - * return ok("It works!"); - * } - * } - * ``` - */ -class PlayActionMethodQueryParameter extends Parameter { - PlayActionMethodQueryParameter() { - exists(PlayControllerActionMethod a | - a.isPublic() and - this = a.getAParameter() - ) - } -} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll deleted file mode 100644 index 539253dab4a..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayHTTPRequestHeader.qll +++ /dev/null @@ -1,25 +0,0 @@ -import java - -/** - * Play MVC Framework HTTP Request Header Class - */ -class PlayMVCHTTPRequestHeader extends RefType { - PlayMVCHTTPRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } -} - -/** - * Play Framework HTTPRequestHeader Methods - `headers`, `getQueryString`, `getHeader` - * - * Documentation: https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html - */ -class PlayMVCHTTPRequestHeaderMethods extends Method { - PlayMVCHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } - - /** - * Gets all references to play.mvc.HTTP.RequestHeader `getQueryString` method - */ - MethodAccess getQueryString() { - this.hasName("getQueryString") and result = this.getAReference() - } - -} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll deleted file mode 100644 index 027f8fe0907..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResult.qll +++ /dev/null @@ -1,8 +0,0 @@ -import java - -/** - * Play MVC Framework Result Class - */ -class PlayMVCResultClass extends Class { - PlayMVCResultClass() { this.hasQualifiedName("play.mvc", "Result") } -} diff --git a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll b/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll deleted file mode 100644 index c03a9358ce5..00000000000 --- a/java/ql/src/semmle/code/java/frameworks/play/PlayMVCResults.qll +++ /dev/null @@ -1,33 +0,0 @@ -import java - -/** - * Play MVC Framework Results Class - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions - */ -class PlayMVCResultsClass extends Class { - PlayMVCResultsClass() { this.hasQualifiedName("play.mvc", "Results") } -} - -/** - * Play Framework mvc.Results Methods - `ok`, `status`, `redirect` - * - * Documentation: https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html - */ -class PlayMVCResultsMethods extends Method { - PlayMVCResultsMethods() { this.getDeclaringType() instanceof PlayMVCResultsClass } - - /** - * Gets all references to play.mvc.Results `ok` method - */ - MethodAccess getAnOkAccess() { - this.hasName("ok") and result = this.getAReference() - } - - /** - * Gets all references to play.mvc.Results `redirect` method - */ - MethodAccess getARedirectAccess() { - this.hasName("redirect") and result = this.getAReference() - } -} From 1e08c11d4049219032f1f9326e907e2830a44bca Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 7 Dec 2020 17:22:10 -0800 Subject: [PATCH 009/757] C++: Share Operand IPA type across IR stages --- .../ir/implementation/aliased_ssa/Operand.qll | 89 ++--------- .../aliased_ssa/internal/OperandImports.qll | 1 + .../aliased_ssa/internal/OperandInternal.qll | 2 + .../aliased_ssa/internal/SSAConstruction.qll | 4 + .../internal/SSAConstructionImports.qll | 1 + .../internal/SSAConstructionInternal.qll | 1 + .../ir/implementation/internal/TOperand.qll | 148 ++++++++++++++++++ .../cpp/ir/implementation/raw/Operand.qll | 89 ++--------- .../raw/internal/OperandImports.qll | 1 + .../raw/internal/OperandInternal.qll | 2 + .../implementation/unaliased_ssa/Operand.qll | 89 ++--------- .../unaliased_ssa/internal/OperandImports.qll | 1 + .../internal/OperandInternal.qll | 2 + .../internal/SSAConstruction.qll | 4 + .../internal/SSAConstructionImports.qll | 1 + .../internal/SSAConstructionInternal.qll | 1 + 16 files changed, 217 insertions(+), 219 deletions(-) create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandInternal.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandInternal.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index a12e35d471b..3a699fa3c4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -10,73 +10,10 @@ private import Imports::MemoryAccessKind private import Imports::IRType private import Imports::Overlap private import Imports::OperandTag +private import Imports::TOperand +private import internal.OperandInternal -cached -private newtype TOperand = - TRegisterOperand(Instruction useInstr, RegisterOperandTag tag, Instruction defInstr) { - defInstr = Construction::getRegisterOperandDefinition(useInstr, tag) and - not Construction::isInCycle(useInstr) and - strictcount(Construction::getRegisterOperandDefinition(useInstr, tag)) = 1 - } or - TNonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - useInstr.getOpcode().hasOperand(tag) - } or - TPhiOperand( - PhiInstruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap - ) { - defInstr = Construction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) - } - -/** - * Base class for all register operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class RegisterOperandBase extends TRegisterOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the register operand with the specified parameters. - */ -private RegisterOperandBase registerOperand( - Instruction useInstr, RegisterOperandTag tag, Instruction defInstr -) { - result = TRegisterOperand(useInstr, tag, defInstr) -} - -/** - * Base class for all non-Phi memory operands. This is a placeholder for the IPA union type that we - * will eventually use for this purpose. - */ -private class NonPhiMemoryOperandBase extends TNonPhiMemoryOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the non-Phi memory operand with the specified parameters. - */ -private NonPhiMemoryOperandBase nonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - result = TNonPhiMemoryOperand(useInstr, tag) -} - -/** - * Base class for all Phi operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class PhiOperandBase extends TPhiOperand { - abstract string toString(); -} - -/** - * Returns the Phi operand with the specified parameters. - */ -private PhiOperandBase phiOperand( - Instruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap -) { - result = TPhiOperand(useInstr, defInstr, predecessorBlock, overlap) -} +class TOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction @@ -239,8 +176,9 @@ class Operand extends TOperand { */ class MemoryOperand extends Operand { MemoryOperand() { - this instanceof NonPhiMemoryOperandBase or - this instanceof PhiOperandBase + this instanceof TNonSSAMemoryOperand or + this instanceof TPhiOperand or + this instanceof TChiOperand } /** @@ -278,7 +216,8 @@ class NonPhiOperand extends Operand { NonPhiOperand() { this = registerOperand(useInstr, tag, _) or - this = nonPhiMemoryOperand(useInstr, tag) + this = nonSSAMemoryOperand(useInstr, tag) or + this = chiOperand(useInstr, tag) } final override Instruction getUse() { result = useInstr } @@ -298,7 +237,7 @@ class NonPhiOperand extends Operand { /** * An operand that consumes a register (non-memory) result. */ -class RegisterOperand extends NonPhiOperand, RegisterOperandBase { +class RegisterOperand extends NonPhiOperand, TRegisterOperand { override RegisterOperandTag tag; Instruction defInstr; @@ -317,10 +256,14 @@ class RegisterOperand extends NonPhiOperand, RegisterOperandBase { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, NonPhiMemoryOperandBase { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { override MemoryOperandTag tag; - NonPhiMemoryOperand() { this = nonPhiMemoryOperand(useInstr, tag) } + NonPhiMemoryOperand() { + this = nonSSAMemoryOperand(useInstr, tag) + or + this = chiOperand(useInstr, tag) + } final override string toString() { result = tag.toString() } @@ -462,7 +405,7 @@ class SideEffectOperand extends TypedOperand { /** * An operand of a `PhiInstruction`. */ -class PhiInputOperand extends MemoryOperand, PhiOperandBase { +class PhiInputOperand extends MemoryOperand, TPhiOperand { PhiInstruction useInstr; Instruction defInstr; IRBlock predecessorBlock; diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandImports.qll index 3c781579cee..d0e013d1fba 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandImports.qll @@ -2,3 +2,4 @@ import semmle.code.cpp.ir.implementation.MemoryAccessKind as MemoryAccessKind import semmle.code.cpp.ir.implementation.IRType as IRType import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandInternal.qll new file mode 100644 index 00000000000..b47c20e97ef --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/OperandInternal.qll @@ -0,0 +1,2 @@ +private import semmle.code.cpp.ir.implementation.internal.TOperand +import AliasedSSAOperands diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index 8e904ee6bc4..ad6f3fc8eb2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -6,6 +6,7 @@ private import Imports::Overlap private import Imports::TInstruction private import Imports::RawIR as RawIR private import SSAInstructions +private import SSAOperands private import NewIR private class OldBlock = Reachability::ReachableBlock; @@ -42,6 +43,9 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; + class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; + cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll index f347df86ba1..2e5b8cc6ed3 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll @@ -3,3 +3,4 @@ import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.TInstruction as TInstruction import semmle.code.cpp.ir.implementation.raw.IR as RawIR +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand \ No newline at end of file diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll index bb068bdd489..e42895e56b6 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll @@ -5,3 +5,4 @@ import semmle.code.cpp.ir.implementation.aliased_ssa.IR as NewIR import semmle.code.cpp.ir.implementation.internal.TInstruction::AliasedSSAInstructions as SSAInstructions import semmle.code.cpp.ir.internal.IRCppLanguage as Language import AliasedSSA as Alias +import semmle.code.cpp.ir.implementation.internal.TOperand::UnliasedSSAOperands as SSAOperands diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll new file mode 100644 index 00000000000..696d372bc15 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -0,0 +1,148 @@ +private import TInstruction +private import OperandTag +private import semmle.code.cpp.ir.implementation.raw.internal.IRConstruction as RawConstruction +private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction as UnaliasedConstruction +private import semmle.code.cpp.ir.implementation.aliased_ssa.internal.SSAConstruction as AliasedConstruction +private import semmle.code.cpp.ir.implementation.raw.IR as Raw +private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as Unaliased +private import semmle.code.cpp.ir.implementation.aliased_ssa.IR as Aliased +private import semmle.code.cpp.ir.internal.Overlap + +private module Internal { + cached + newtype TOperand = + // RAW + TRegisterOperand(TRawInstruction useInstr, RegisterOperandTag tag, TRawInstruction defInstr) { + defInstr = RawConstruction::getRegisterOperandDefinition(useInstr, tag) and + not RawConstruction::isInCycle(useInstr) and + strictcount(RawConstruction::getRegisterOperandDefinition(useInstr, tag)) = 1 + } or + // Placeholder for Phi and Chi operands in stages that don't have the corresponding instructions + TNoOperand() { none() } or + // Can be "removed" later when there's unreachable code + // These operands can be reused across all three stages. They just get different defs. + TNonSSAMemoryOperand(Raw::Instruction useInstr, MemoryOperandTag tag) { + // Has no definition in raw but will get definitions later + useInstr.getOpcode().hasOperand(tag) + } or + TUnaliasedPhiOperand( + Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, + Unaliased::IRBlock predecessorBlock, Overlap overlap + ) { + defInstr = UnaliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) + } or + //// ALIASED + //// + // If we share SSA, these will be all the phis there are. Otherwise these + // will add to the ones that are already there. + // If we share SSA, be careful with the case where we remove all possible + // indirect writes to a variable because they're dead code. In that case it's + // important that we use the same definition of "is variable aliased" across + // the phases. + TAliasedPhiOperand( + TAliasedSSAPhiInstruction useInstr, Aliased::Instruction defInstr, + Aliased::IRBlock predecessorBlock, Overlap overlap + ) { + defInstr = AliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) + } or + TAliasedChiOperand(TAliasedSSAChiInstruction useInstr, ChiOperandTag tag) { + // TODO: any further restrictions here? + any() + } +} + +private module Shared { + class TRegisterOperand = Internal::TRegisterOperand; + + /** + * Returns the register operand with the specified parameters. + */ + TRegisterOperand registerOperand( + TRawInstruction useInstr, RegisterOperandTag tag, TRawInstruction defInstr + ) { + result = Internal::TRegisterOperand(useInstr, tag, defInstr) + } + + class TNonSSAMemoryOperand = Internal::TNonSSAMemoryOperand; + + /** + * Returns the non-Phi memory operand with the specified parameters. + */ + TNonSSAMemoryOperand nonSSAMemoryOperand(TRawInstruction useInstr, MemoryOperandTag tag) { + result = Internal::TNonSSAMemoryOperand(useInstr, tag) + } +} + +module RawOperands { + import Shared + + class TPhiOperand = Internal::TNoOperand; + + class TChiOperand = Internal::TNoOperand; + + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand phiOperand( + Raw::PhiInstruction useInstr, Raw::Instruction defInstr, Raw::IRBlock predecessorBlock, + Overlap overlap + ) { + none() + } + + /** + * Returns the Chi operand with the specified parameters. + */ + TChiOperand chiOperand(Raw::Instruction useInstr, ChiOperandTag tag) { none() } +} + +// TODO: can we get everything into either here or Operand.qll? +// TODO: can we put `TStageOperand` in Construction? Might break something about the module caching setup, `Operand` is currently after everything in SSAConstruction +// TODO: share empty ChiOperand? +module UnliasedSSAOperands { + import Shared + + class TPhiOperand = Internal::TUnaliasedPhiOperand; + + class TChiOperand = Internal::TNoOperand; + + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand phiOperand( + Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, + Unaliased::IRBlock predecessorBlock, Overlap overlap + ) { + result = Internal::TUnaliasedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } + + /** + * Returns the Chi operand with the specified parameters. + */ + TChiOperand chiOperand(Unaliased::Instruction useInstr, ChiOperandTag tag) { none() } +} + +module AliasedSSAOperands { + import Shared + + class TPhiOperand = Internal::TAliasedPhiOperand; + + class TChiOperand = Internal::TAliasedChiOperand; + + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand phiOperand( + TAliasedSSAPhiInstruction useInstr, Aliased::Instruction defInstr, + Aliased::IRBlock predecessorBlock, Overlap overlap + ) { + result = Internal::TAliasedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } + + /** + * Returns the Chi operand with the specified parameters. + */ + TChiOperand chiOperand(TAliasedSSAChiInstruction useInstr, ChiOperandTag tag) { + result = Internal::TAliasedChiOperand(useInstr, tag) + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index a12e35d471b..3a699fa3c4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -10,73 +10,10 @@ private import Imports::MemoryAccessKind private import Imports::IRType private import Imports::Overlap private import Imports::OperandTag +private import Imports::TOperand +private import internal.OperandInternal -cached -private newtype TOperand = - TRegisterOperand(Instruction useInstr, RegisterOperandTag tag, Instruction defInstr) { - defInstr = Construction::getRegisterOperandDefinition(useInstr, tag) and - not Construction::isInCycle(useInstr) and - strictcount(Construction::getRegisterOperandDefinition(useInstr, tag)) = 1 - } or - TNonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - useInstr.getOpcode().hasOperand(tag) - } or - TPhiOperand( - PhiInstruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap - ) { - defInstr = Construction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) - } - -/** - * Base class for all register operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class RegisterOperandBase extends TRegisterOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the register operand with the specified parameters. - */ -private RegisterOperandBase registerOperand( - Instruction useInstr, RegisterOperandTag tag, Instruction defInstr -) { - result = TRegisterOperand(useInstr, tag, defInstr) -} - -/** - * Base class for all non-Phi memory operands. This is a placeholder for the IPA union type that we - * will eventually use for this purpose. - */ -private class NonPhiMemoryOperandBase extends TNonPhiMemoryOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the non-Phi memory operand with the specified parameters. - */ -private NonPhiMemoryOperandBase nonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - result = TNonPhiMemoryOperand(useInstr, tag) -} - -/** - * Base class for all Phi operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class PhiOperandBase extends TPhiOperand { - abstract string toString(); -} - -/** - * Returns the Phi operand with the specified parameters. - */ -private PhiOperandBase phiOperand( - Instruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap -) { - result = TPhiOperand(useInstr, defInstr, predecessorBlock, overlap) -} +class TOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction @@ -239,8 +176,9 @@ class Operand extends TOperand { */ class MemoryOperand extends Operand { MemoryOperand() { - this instanceof NonPhiMemoryOperandBase or - this instanceof PhiOperandBase + this instanceof TNonSSAMemoryOperand or + this instanceof TPhiOperand or + this instanceof TChiOperand } /** @@ -278,7 +216,8 @@ class NonPhiOperand extends Operand { NonPhiOperand() { this = registerOperand(useInstr, tag, _) or - this = nonPhiMemoryOperand(useInstr, tag) + this = nonSSAMemoryOperand(useInstr, tag) or + this = chiOperand(useInstr, tag) } final override Instruction getUse() { result = useInstr } @@ -298,7 +237,7 @@ class NonPhiOperand extends Operand { /** * An operand that consumes a register (non-memory) result. */ -class RegisterOperand extends NonPhiOperand, RegisterOperandBase { +class RegisterOperand extends NonPhiOperand, TRegisterOperand { override RegisterOperandTag tag; Instruction defInstr; @@ -317,10 +256,14 @@ class RegisterOperand extends NonPhiOperand, RegisterOperandBase { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, NonPhiMemoryOperandBase { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { override MemoryOperandTag tag; - NonPhiMemoryOperand() { this = nonPhiMemoryOperand(useInstr, tag) } + NonPhiMemoryOperand() { + this = nonSSAMemoryOperand(useInstr, tag) + or + this = chiOperand(useInstr, tag) + } final override string toString() { result = tag.toString() } @@ -462,7 +405,7 @@ class SideEffectOperand extends TypedOperand { /** * An operand of a `PhiInstruction`. */ -class PhiInputOperand extends MemoryOperand, PhiOperandBase { +class PhiInputOperand extends MemoryOperand, TPhiOperand { PhiInstruction useInstr; Instruction defInstr; IRBlock predecessorBlock; diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandImports.qll index 3c781579cee..d0e013d1fba 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandImports.qll @@ -2,3 +2,4 @@ import semmle.code.cpp.ir.implementation.MemoryAccessKind as MemoryAccessKind import semmle.code.cpp.ir.implementation.IRType as IRType import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandInternal.qll new file mode 100644 index 00000000000..194e21e0d93 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/OperandInternal.qll @@ -0,0 +1,2 @@ +private import semmle.code.cpp.ir.implementation.internal.TOperand +import RawOperands diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index a12e35d471b..3a699fa3c4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -10,73 +10,10 @@ private import Imports::MemoryAccessKind private import Imports::IRType private import Imports::Overlap private import Imports::OperandTag +private import Imports::TOperand +private import internal.OperandInternal -cached -private newtype TOperand = - TRegisterOperand(Instruction useInstr, RegisterOperandTag tag, Instruction defInstr) { - defInstr = Construction::getRegisterOperandDefinition(useInstr, tag) and - not Construction::isInCycle(useInstr) and - strictcount(Construction::getRegisterOperandDefinition(useInstr, tag)) = 1 - } or - TNonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - useInstr.getOpcode().hasOperand(tag) - } or - TPhiOperand( - PhiInstruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap - ) { - defInstr = Construction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) - } - -/** - * Base class for all register operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class RegisterOperandBase extends TRegisterOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the register operand with the specified parameters. - */ -private RegisterOperandBase registerOperand( - Instruction useInstr, RegisterOperandTag tag, Instruction defInstr -) { - result = TRegisterOperand(useInstr, tag, defInstr) -} - -/** - * Base class for all non-Phi memory operands. This is a placeholder for the IPA union type that we - * will eventually use for this purpose. - */ -private class NonPhiMemoryOperandBase extends TNonPhiMemoryOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the non-Phi memory operand with the specified parameters. - */ -private NonPhiMemoryOperandBase nonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - result = TNonPhiMemoryOperand(useInstr, tag) -} - -/** - * Base class for all Phi operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class PhiOperandBase extends TPhiOperand { - abstract string toString(); -} - -/** - * Returns the Phi operand with the specified parameters. - */ -private PhiOperandBase phiOperand( - Instruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap -) { - result = TPhiOperand(useInstr, defInstr, predecessorBlock, overlap) -} +class TOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction @@ -239,8 +176,9 @@ class Operand extends TOperand { */ class MemoryOperand extends Operand { MemoryOperand() { - this instanceof NonPhiMemoryOperandBase or - this instanceof PhiOperandBase + this instanceof TNonSSAMemoryOperand or + this instanceof TPhiOperand or + this instanceof TChiOperand } /** @@ -278,7 +216,8 @@ class NonPhiOperand extends Operand { NonPhiOperand() { this = registerOperand(useInstr, tag, _) or - this = nonPhiMemoryOperand(useInstr, tag) + this = nonSSAMemoryOperand(useInstr, tag) or + this = chiOperand(useInstr, tag) } final override Instruction getUse() { result = useInstr } @@ -298,7 +237,7 @@ class NonPhiOperand extends Operand { /** * An operand that consumes a register (non-memory) result. */ -class RegisterOperand extends NonPhiOperand, RegisterOperandBase { +class RegisterOperand extends NonPhiOperand, TRegisterOperand { override RegisterOperandTag tag; Instruction defInstr; @@ -317,10 +256,14 @@ class RegisterOperand extends NonPhiOperand, RegisterOperandBase { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, NonPhiMemoryOperandBase { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { override MemoryOperandTag tag; - NonPhiMemoryOperand() { this = nonPhiMemoryOperand(useInstr, tag) } + NonPhiMemoryOperand() { + this = nonSSAMemoryOperand(useInstr, tag) + or + this = chiOperand(useInstr, tag) + } final override string toString() { result = tag.toString() } @@ -462,7 +405,7 @@ class SideEffectOperand extends TypedOperand { /** * An operand of a `PhiInstruction`. */ -class PhiInputOperand extends MemoryOperand, PhiOperandBase { +class PhiInputOperand extends MemoryOperand, TPhiOperand { PhiInstruction useInstr; Instruction defInstr; IRBlock predecessorBlock; diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandImports.qll index 3c781579cee..d0e013d1fba 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandImports.qll @@ -2,3 +2,4 @@ import semmle.code.cpp.ir.implementation.MemoryAccessKind as MemoryAccessKind import semmle.code.cpp.ir.implementation.IRType as IRType import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll new file mode 100644 index 00000000000..b668f1f04a4 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll @@ -0,0 +1,2 @@ +private import semmle.code.cpp.ir.implementation.internal.TOperand +import UnliasedSSAOperands diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 8e904ee6bc4..ad6f3fc8eb2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -6,6 +6,7 @@ private import Imports::Overlap private import Imports::TInstruction private import Imports::RawIR as RawIR private import SSAInstructions +private import SSAOperands private import NewIR private class OldBlock = Reachability::ReachableBlock; @@ -42,6 +43,9 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; + class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; + cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll index f347df86ba1..2e5b8cc6ed3 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll @@ -3,3 +3,4 @@ import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.TInstruction as TInstruction import semmle.code.cpp.ir.implementation.raw.IR as RawIR +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand \ No newline at end of file diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll index 73b08d1286b..480f473bb3f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll @@ -6,3 +6,4 @@ import semmle.code.cpp.ir.implementation.raw.internal.IRConstruction as RawStage import semmle.code.cpp.ir.implementation.internal.TInstruction::UnaliasedSSAInstructions as SSAInstructions import semmle.code.cpp.ir.internal.IRCppLanguage as Language import SimpleSSA as Alias +import semmle.code.cpp.ir.implementation.internal.TOperand::UnliasedSSAOperands as SSAOperands From 89a59d5f1a200bf9ade74c5b2f5e7736197c4176 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 11 Dec 2020 15:52:07 -0800 Subject: [PATCH 010/757] C++: comments about shared Operand IPA type --- .../ir/implementation/aliased_ssa/Operand.qll | 8 +++-- .../aliased_ssa/internal/SSAConstruction.qll | 3 -- .../ir/implementation/internal/TOperand.qll | 36 +++++++++++++++++-- .../cpp/ir/implementation/raw/Operand.qll | 8 +++-- .../implementation/unaliased_ssa/Operand.qll | 8 +++-- .../internal/SSAConstruction.qll | 3 -- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index 3a699fa3c4e..d87e513ed13 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -13,13 +13,17 @@ private import Imports::OperandTag private import Imports::TOperand private import internal.OperandInternal -class TOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +/** + * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches + * of `TOperand` that are used in this stage. + */ +private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ -class Operand extends TOperand { +class Operand extends TStageOperand { /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index ad6f3fc8eb2..a1c970121d2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -43,9 +43,6 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; - class TStageOperand = - TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; - cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index 696d372bc15..b09ce41ffd1 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -8,7 +8,18 @@ private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as Unaliased private import semmle.code.cpp.ir.implementation.aliased_ssa.IR as Aliased private import semmle.code.cpp.ir.internal.Overlap +/** + * Provides the newtype used to represent operands across all phases of the IR. + */ private module Internal { + + /** + * An IR operand. `TOperand` is shared across all phases of the IR. There are branches of this + * type for operands created directly from the AST (`TRegisterOperand` and `TNonSSAMemoryOperand`), + * for operands computed by each stage of SSA construction (`T*PhiOperand` and + * `TAliasedChiOperand`), and a placehold branch for operands that do not exist in a given + * stage of IR construction (`TNoOperand`). + */ cached newtype TOperand = // RAW @@ -51,6 +62,10 @@ private module Internal { } } +/** + * Reexports some branches from `TOperand` so they can be used in stage modules without importing + * `TOperand` itself. + */ private module Shared { class TRegisterOperand = Internal::TRegisterOperand; @@ -73,6 +88,12 @@ private module Shared { } } +/** + * Provides wrappers for the constructors of each branch of `TOperand` that is used by the + * raw IR stage. + * These wrappers are not parameterized because it is not possible to invoke an IPA constructor via + * a class alias. + */ module RawOperands { import Shared @@ -96,9 +117,12 @@ module RawOperands { TChiOperand chiOperand(Raw::Instruction useInstr, ChiOperandTag tag) { none() } } -// TODO: can we get everything into either here or Operand.qll? -// TODO: can we put `TStageOperand` in Construction? Might break something about the module caching setup, `Operand` is currently after everything in SSAConstruction -// TODO: share empty ChiOperand? +/** + * Provides wrappers for the constructors of each branch of `TOperand` that is used by the + * unaliased SSA stage. + * These wrappers are not parameterized because it is not possible to invoke an IPA constructor via + * a class alias. + */ module UnliasedSSAOperands { import Shared @@ -122,6 +146,12 @@ module UnliasedSSAOperands { TChiOperand chiOperand(Unaliased::Instruction useInstr, ChiOperandTag tag) { none() } } +/** + * Provides wrappers for the constructors of each branch of `TOperand` that is used by the + * asliased SSA stage. + * These wrappers are not parameterized because it is not possible to invoke an IPA constructor via + * a class alias. + */ module AliasedSSAOperands { import Shared diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index 3a699fa3c4e..d87e513ed13 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -13,13 +13,17 @@ private import Imports::OperandTag private import Imports::TOperand private import internal.OperandInternal -class TOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +/** + * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches + * of `TOperand` that are used in this stage. + */ +private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ -class Operand extends TOperand { +class Operand extends TStageOperand { /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index 3a699fa3c4e..d87e513ed13 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -13,13 +13,17 @@ private import Imports::OperandTag private import Imports::TOperand private import internal.OperandInternal -class TOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +/** + * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches + * of `TOperand` that are used in this stage. + */ +private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ -class Operand extends TOperand { +class Operand extends TStageOperand { /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index ad6f3fc8eb2..a1c970121d2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -43,9 +43,6 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; - class TStageOperand = - TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; - cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction From 96e913031d37c63f364c2ab4922bdd7fe7dd4d02 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 11 Dec 2020 16:11:00 -0800 Subject: [PATCH 011/757] C#: share IR Operand IPA type between stages --- .../ir/implementation/internal/TOperand.qll | 127 ++++++++++++++++++ .../ir/implementation/raw/Operand.qll | 91 +++---------- .../raw/internal/OperandImports.qll | 1 + .../raw/internal/OperandInternal.qll | 2 + .../implementation/unaliased_ssa/Operand.qll | 91 +++---------- .../unaliased_ssa/internal/OperandImports.qll | 1 + .../internal/OperandInternal.qll | 2 + .../internal/SSAConstruction.qll | 1 + .../internal/SSAConstructionInternal.qll | 1 + 9 files changed, 173 insertions(+), 144 deletions(-) create mode 100644 csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll create mode 100644 csharp/ql/src/experimental/ir/implementation/raw/internal/OperandInternal.qll create mode 100644 csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandInternal.qll diff --git a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll new file mode 100644 index 00000000000..361e4105ae7 --- /dev/null +++ b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll @@ -0,0 +1,127 @@ +private import TInstruction +private import OperandTag +private import experimental.ir.implementation.raw.internal.IRConstruction as RawConstruction +private import experimental.ir.implementation.unaliased_ssa.internal.SSAConstruction as UnaliasedConstruction +private import experimental.ir.implementation.raw.IR as Raw +private import experimental.ir.implementation.unaliased_ssa.IR as Unaliased +private import experimental.ir.internal.Overlap + +/** + * Provides the newtype used to represent operands across all phases of the IR. + */ +private module Internal { + + /** + * An IR operand. `TOperand` is shared across all phases of the IR. There are branches of this + * type for operands created directly from the AST (`TRegisterOperand` and `TNonSSAMemoryOperand`), + * for operands computed by each stage of SSA construction (`T*PhiOperand` and + * `TAliasedChiOperand`), and a placehold branch for operands that do not exist in a given + * stage of IR construction (`TNoOperand`). + */ + cached + newtype TOperand = + // RAW + TRegisterOperand(TRawInstruction useInstr, RegisterOperandTag tag, TRawInstruction defInstr) { + defInstr = RawConstruction::getRegisterOperandDefinition(useInstr, tag) and + not RawConstruction::isInCycle(useInstr) and + strictcount(RawConstruction::getRegisterOperandDefinition(useInstr, tag)) = 1 + } or + // Placeholder for Phi and Chi operands in stages that don't have the corresponding instructions + TNoOperand() { none() } or + // Can be "removed" later when there's unreachable code + // These operands can be reused across all three stages. They just get different defs. + TNonSSAMemoryOperand(Raw::Instruction useInstr, MemoryOperandTag tag) { + // Has no definition in raw but will get definitions later + useInstr.getOpcode().hasOperand(tag) + } or + TUnaliasedPhiOperand( + Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, + Unaliased::IRBlock predecessorBlock, Overlap overlap + ) { + defInstr = UnaliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) + } +} + +/** + * Reexports some branches from `TOperand` so they can be used in stage modules without importing + * `TOperand` itself. + */ +private module Shared { + class TRegisterOperand = Internal::TRegisterOperand; + + /** + * Returns the register operand with the specified parameters. + */ + TRegisterOperand registerOperand( + TRawInstruction useInstr, RegisterOperandTag tag, TRawInstruction defInstr + ) { + result = Internal::TRegisterOperand(useInstr, tag, defInstr) + } + + class TNonSSAMemoryOperand = Internal::TNonSSAMemoryOperand; + + /** + * Returns the non-Phi memory operand with the specified parameters. + */ + TNonSSAMemoryOperand nonSSAMemoryOperand(TRawInstruction useInstr, MemoryOperandTag tag) { + result = Internal::TNonSSAMemoryOperand(useInstr, tag) + } +} + +/** + * Provides wrappers for the constructors of each branch of `TOperand` that is used by the + * raw IR stage. + * These wrappers are not parameterized because it is not possible to invoke an IPA constructor via + * a class alias. + */ +module RawOperands { + import Shared + + class TPhiOperand = Internal::TNoOperand; + + class TChiOperand = Internal::TNoOperand; + + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand phiOperand( + Raw::PhiInstruction useInstr, Raw::Instruction defInstr, Raw::IRBlock predecessorBlock, + Overlap overlap + ) { + none() + } + + /** + * Returns the Chi operand with the specified parameters. + */ + TChiOperand chiOperand(Raw::Instruction useInstr, ChiOperandTag tag) { none() } +} + +/** + * Provides wrappers for the constructors of each branch of `TOperand` that is used by the + * unaliased SSA stage. + * These wrappers are not parameterized because it is not possible to invoke an IPA constructor via + * a class alias. + */ +module UnaliasedSSAOperands { + import Shared + + class TPhiOperand = Internal::TUnaliasedPhiOperand; + + class TChiOperand = Internal::TNoOperand; + + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand phiOperand( + Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, + Unaliased::IRBlock predecessorBlock, Overlap overlap + ) { + result = Internal::TUnaliasedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } + + /** + * Returns the Chi operand with the specified parameters. + */ + TChiOperand chiOperand(Unaliased::Instruction useInstr, ChiOperandTag tag) { none() } +} diff --git a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll index a12e35d471b..d87e513ed13 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll @@ -10,79 +10,20 @@ private import Imports::MemoryAccessKind private import Imports::IRType private import Imports::Overlap private import Imports::OperandTag - -cached -private newtype TOperand = - TRegisterOperand(Instruction useInstr, RegisterOperandTag tag, Instruction defInstr) { - defInstr = Construction::getRegisterOperandDefinition(useInstr, tag) and - not Construction::isInCycle(useInstr) and - strictcount(Construction::getRegisterOperandDefinition(useInstr, tag)) = 1 - } or - TNonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - useInstr.getOpcode().hasOperand(tag) - } or - TPhiOperand( - PhiInstruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap - ) { - defInstr = Construction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) - } +private import Imports::TOperand +private import internal.OperandInternal /** - * Base class for all register operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. + * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches + * of `TOperand` that are used in this stage. */ -private class RegisterOperandBase extends TRegisterOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the register operand with the specified parameters. - */ -private RegisterOperandBase registerOperand( - Instruction useInstr, RegisterOperandTag tag, Instruction defInstr -) { - result = TRegisterOperand(useInstr, tag, defInstr) -} - -/** - * Base class for all non-Phi memory operands. This is a placeholder for the IPA union type that we - * will eventually use for this purpose. - */ -private class NonPhiMemoryOperandBase extends TNonPhiMemoryOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the non-Phi memory operand with the specified parameters. - */ -private NonPhiMemoryOperandBase nonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - result = TNonPhiMemoryOperand(useInstr, tag) -} - -/** - * Base class for all Phi operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class PhiOperandBase extends TPhiOperand { - abstract string toString(); -} - -/** - * Returns the Phi operand with the specified parameters. - */ -private PhiOperandBase phiOperand( - Instruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap -) { - result = TPhiOperand(useInstr, defInstr, predecessorBlock, overlap) -} +private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ -class Operand extends TOperand { +class Operand extends TStageOperand { /** Gets a textual representation of this element. */ string toString() { result = "Operand" } @@ -239,8 +180,9 @@ class Operand extends TOperand { */ class MemoryOperand extends Operand { MemoryOperand() { - this instanceof NonPhiMemoryOperandBase or - this instanceof PhiOperandBase + this instanceof TNonSSAMemoryOperand or + this instanceof TPhiOperand or + this instanceof TChiOperand } /** @@ -278,7 +220,8 @@ class NonPhiOperand extends Operand { NonPhiOperand() { this = registerOperand(useInstr, tag, _) or - this = nonPhiMemoryOperand(useInstr, tag) + this = nonSSAMemoryOperand(useInstr, tag) or + this = chiOperand(useInstr, tag) } final override Instruction getUse() { result = useInstr } @@ -298,7 +241,7 @@ class NonPhiOperand extends Operand { /** * An operand that consumes a register (non-memory) result. */ -class RegisterOperand extends NonPhiOperand, RegisterOperandBase { +class RegisterOperand extends NonPhiOperand, TRegisterOperand { override RegisterOperandTag tag; Instruction defInstr; @@ -317,10 +260,14 @@ class RegisterOperand extends NonPhiOperand, RegisterOperandBase { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, NonPhiMemoryOperandBase { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { override MemoryOperandTag tag; - NonPhiMemoryOperand() { this = nonPhiMemoryOperand(useInstr, tag) } + NonPhiMemoryOperand() { + this = nonSSAMemoryOperand(useInstr, tag) + or + this = chiOperand(useInstr, tag) + } final override string toString() { result = tag.toString() } @@ -462,7 +409,7 @@ class SideEffectOperand extends TypedOperand { /** * An operand of a `PhiInstruction`. */ -class PhiInputOperand extends MemoryOperand, PhiOperandBase { +class PhiInputOperand extends MemoryOperand, TPhiOperand { PhiInstruction useInstr; Instruction defInstr; IRBlock predecessorBlock; diff --git a/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandImports.qll b/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandImports.qll index 40af4631927..65676caf724 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandImports.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandImports.qll @@ -2,3 +2,4 @@ import experimental.ir.implementation.MemoryAccessKind as MemoryAccessKind import experimental.ir.implementation.IRType as IRType import experimental.ir.internal.Overlap as Overlap import experimental.ir.implementation.internal.OperandTag as OperandTag +import experimental.ir.implementation.internal.TOperand as TOperand diff --git a/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandInternal.qll b/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandInternal.qll new file mode 100644 index 00000000000..771aeb9033c --- /dev/null +++ b/csharp/ql/src/experimental/ir/implementation/raw/internal/OperandInternal.qll @@ -0,0 +1,2 @@ +private import experimental.ir.implementation.internal.TOperand +import RawOperands diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll index a12e35d471b..d87e513ed13 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll @@ -10,79 +10,20 @@ private import Imports::MemoryAccessKind private import Imports::IRType private import Imports::Overlap private import Imports::OperandTag - -cached -private newtype TOperand = - TRegisterOperand(Instruction useInstr, RegisterOperandTag tag, Instruction defInstr) { - defInstr = Construction::getRegisterOperandDefinition(useInstr, tag) and - not Construction::isInCycle(useInstr) and - strictcount(Construction::getRegisterOperandDefinition(useInstr, tag)) = 1 - } or - TNonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - useInstr.getOpcode().hasOperand(tag) - } or - TPhiOperand( - PhiInstruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap - ) { - defInstr = Construction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) - } +private import Imports::TOperand +private import internal.OperandInternal /** - * Base class for all register operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. + * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches + * of `TOperand` that are used in this stage. */ -private class RegisterOperandBase extends TRegisterOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the register operand with the specified parameters. - */ -private RegisterOperandBase registerOperand( - Instruction useInstr, RegisterOperandTag tag, Instruction defInstr -) { - result = TRegisterOperand(useInstr, tag, defInstr) -} - -/** - * Base class for all non-Phi memory operands. This is a placeholder for the IPA union type that we - * will eventually use for this purpose. - */ -private class NonPhiMemoryOperandBase extends TNonPhiMemoryOperand { - /** Gets a textual representation of this element. */ - abstract string toString(); -} - -/** - * Returns the non-Phi memory operand with the specified parameters. - */ -private NonPhiMemoryOperandBase nonPhiMemoryOperand(Instruction useInstr, MemoryOperandTag tag) { - result = TNonPhiMemoryOperand(useInstr, tag) -} - -/** - * Base class for all Phi operands. This is a placeholder for the IPA union type that we will - * eventually use for this purpose. - */ -private class PhiOperandBase extends TPhiOperand { - abstract string toString(); -} - -/** - * Returns the Phi operand with the specified parameters. - */ -private PhiOperandBase phiOperand( - Instruction useInstr, Instruction defInstr, IRBlock predecessorBlock, Overlap overlap -) { - result = TPhiOperand(useInstr, defInstr, predecessorBlock, overlap) -} +private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ -class Operand extends TOperand { +class Operand extends TStageOperand { /** Gets a textual representation of this element. */ string toString() { result = "Operand" } @@ -239,8 +180,9 @@ class Operand extends TOperand { */ class MemoryOperand extends Operand { MemoryOperand() { - this instanceof NonPhiMemoryOperandBase or - this instanceof PhiOperandBase + this instanceof TNonSSAMemoryOperand or + this instanceof TPhiOperand or + this instanceof TChiOperand } /** @@ -278,7 +220,8 @@ class NonPhiOperand extends Operand { NonPhiOperand() { this = registerOperand(useInstr, tag, _) or - this = nonPhiMemoryOperand(useInstr, tag) + this = nonSSAMemoryOperand(useInstr, tag) or + this = chiOperand(useInstr, tag) } final override Instruction getUse() { result = useInstr } @@ -298,7 +241,7 @@ class NonPhiOperand extends Operand { /** * An operand that consumes a register (non-memory) result. */ -class RegisterOperand extends NonPhiOperand, RegisterOperandBase { +class RegisterOperand extends NonPhiOperand, TRegisterOperand { override RegisterOperandTag tag; Instruction defInstr; @@ -317,10 +260,14 @@ class RegisterOperand extends NonPhiOperand, RegisterOperandBase { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, NonPhiMemoryOperandBase { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { override MemoryOperandTag tag; - NonPhiMemoryOperand() { this = nonPhiMemoryOperand(useInstr, tag) } + NonPhiMemoryOperand() { + this = nonSSAMemoryOperand(useInstr, tag) + or + this = chiOperand(useInstr, tag) + } final override string toString() { result = tag.toString() } @@ -462,7 +409,7 @@ class SideEffectOperand extends TypedOperand { /** * An operand of a `PhiInstruction`. */ -class PhiInputOperand extends MemoryOperand, PhiOperandBase { +class PhiInputOperand extends MemoryOperand, TPhiOperand { PhiInstruction useInstr; Instruction defInstr; IRBlock predecessorBlock; diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandImports.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandImports.qll index 40af4631927..65676caf724 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandImports.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandImports.qll @@ -2,3 +2,4 @@ import experimental.ir.implementation.MemoryAccessKind as MemoryAccessKind import experimental.ir.implementation.IRType as IRType import experimental.ir.internal.Overlap as Overlap import experimental.ir.implementation.internal.OperandTag as OperandTag +import experimental.ir.implementation.internal.TOperand as TOperand diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandInternal.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandInternal.qll new file mode 100644 index 00000000000..88a4e6f8551 --- /dev/null +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/OperandInternal.qll @@ -0,0 +1,2 @@ +private import experimental.ir.implementation.internal.TOperand +import UnaliasedSSAOperands diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 8e904ee6bc4..a1c970121d2 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -6,6 +6,7 @@ private import Imports::Overlap private import Imports::TInstruction private import Imports::RawIR as RawIR private import SSAInstructions +private import SSAOperands private import NewIR private class OldBlock = Reachability::ReachableBlock; diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll index 15eaf8045a7..8f726b81345 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll @@ -6,3 +6,4 @@ import experimental.ir.implementation.raw.internal.IRConstruction as RawStage import experimental.ir.implementation.internal.TInstruction::UnaliasedSSAInstructions as SSAInstructions import experimental.ir.internal.IRCSharpLanguage as Language import SimpleSSA as Alias +import experimental.ir.implementation.internal.TOperand::UnaliasedSSAOperands as SSAOperands From a404ca66d17ccb1545e0696004a5176385fba680 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 11 Dec 2020 16:16:19 -0800 Subject: [PATCH 012/757] C++: fix typo --- .../aliased_ssa/internal/SSAConstructionInternal.qll | 2 +- .../src/semmle/code/cpp/ir/implementation/internal/TOperand.qll | 2 +- .../implementation/unaliased_ssa/internal/OperandInternal.qll | 2 +- .../unaliased_ssa/internal/SSAConstructionInternal.qll | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll index e42895e56b6..a1ce2629cc2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionInternal.qll @@ -5,4 +5,4 @@ import semmle.code.cpp.ir.implementation.aliased_ssa.IR as NewIR import semmle.code.cpp.ir.implementation.internal.TInstruction::AliasedSSAInstructions as SSAInstructions import semmle.code.cpp.ir.internal.IRCppLanguage as Language import AliasedSSA as Alias -import semmle.code.cpp.ir.implementation.internal.TOperand::UnliasedSSAOperands as SSAOperands +import semmle.code.cpp.ir.implementation.internal.TOperand::AliasedSSAOperands as SSAOperands diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index b09ce41ffd1..295ffdfa1e0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -123,7 +123,7 @@ module RawOperands { * These wrappers are not parameterized because it is not possible to invoke an IPA constructor via * a class alias. */ -module UnliasedSSAOperands { +module UnaliasedSSAOperands { import Shared class TPhiOperand = Internal::TUnaliasedPhiOperand; diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll index b668f1f04a4..80e06a381a1 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/OperandInternal.qll @@ -1,2 +1,2 @@ private import semmle.code.cpp.ir.implementation.internal.TOperand -import UnliasedSSAOperands +import UnaliasedSSAOperands diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll index 480f473bb3f..70d44e03267 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionInternal.qll @@ -6,4 +6,4 @@ import semmle.code.cpp.ir.implementation.raw.internal.IRConstruction as RawStage import semmle.code.cpp.ir.implementation.internal.TInstruction::UnaliasedSSAInstructions as SSAInstructions import semmle.code.cpp.ir.internal.IRCppLanguage as Language import SimpleSSA as Alias -import semmle.code.cpp.ir.implementation.internal.TOperand::UnliasedSSAOperands as SSAOperands +import semmle.code.cpp.ir.implementation.internal.TOperand::UnaliasedSSAOperands as SSAOperands From fd14eb4c8c652d6d38f6268636e1136bcdf37b1f Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 15 Dec 2020 11:45:40 -0800 Subject: [PATCH 013/757] C++: remove unreachable IR operands in late stages --- .../cpp/ir/implementation/aliased_ssa/Operand.qll | 14 +++++++++++++- .../code/cpp/ir/implementation/raw/Operand.qll | 14 +++++++++++++- .../ir/implementation/unaliased_ssa/Operand.qll | 14 +++++++++++++- .../experimental/ir/implementation/raw/Operand.qll | 14 +++++++++++++- .../ir/implementation/unaliased_ssa/Operand.qll | 14 +++++++++++++- 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index d87e513ed13..38fc6264133 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -17,13 +17,25 @@ private import internal.OperandInternal * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches * of `TOperand` that are used in this stage. */ -private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +private class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ class Operand extends TStageOperand { + Operand() { + // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or + exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + or + exists(Instruction use | this = chiOperand(use, _)) + } + /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index d87e513ed13..38fc6264133 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -17,13 +17,25 @@ private import internal.OperandInternal * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches * of `TOperand` that are used in this stage. */ -private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +private class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ class Operand extends TStageOperand { + Operand() { + // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or + exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + or + exists(Instruction use | this = chiOperand(use, _)) + } + /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index d87e513ed13..38fc6264133 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -17,13 +17,25 @@ private import internal.OperandInternal * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches * of `TOperand` that are used in this stage. */ -private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +private class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ class Operand extends TStageOperand { + Operand() { + // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or + exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + or + exists(Instruction use | this = chiOperand(use, _)) + } + /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll index d87e513ed13..38fc6264133 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll @@ -17,13 +17,25 @@ private import internal.OperandInternal * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches * of `TOperand` that are used in this stage. */ -private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +private class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ class Operand extends TStageOperand { + Operand() { + // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or + exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + or + exists(Instruction use | this = chiOperand(use, _)) + } + /** Gets a textual representation of this element. */ string toString() { result = "Operand" } diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll index d87e513ed13..38fc6264133 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll @@ -17,13 +17,25 @@ private import internal.OperandInternal * An operand of an `Instruction` in this stage of the IR. Implemented as a union of the branches * of `TOperand` that are used in this stage. */ -private class TStageOperand = TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; +private class TStageOperand = + TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand; /** * An operand of an `Instruction`. The operand represents a use of the result of one instruction * (the defining instruction) in another instruction (the use instruction) */ class Operand extends TStageOperand { + Operand() { + // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or + exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + or + exists(Instruction use | this = chiOperand(use, _)) + } + /** Gets a textual representation of this element. */ string toString() { result = "Operand" } From 5d2a553059cf8b065fe212fd2a0a3205e1779ff3 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 15 Dec 2020 17:16:31 -0800 Subject: [PATCH 014/757] C++/C#: autoformat --- .../aliased_ssa/internal/SSAConstructionImports.qll | 2 +- .../src/semmle/code/cpp/ir/implementation/internal/TOperand.qll | 1 - .../unaliased_ssa/internal/SSAConstructionImports.qll | 2 +- .../ql/src/experimental/ir/implementation/internal/TOperand.qll | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll index 2e5b8cc6ed3..219180d9f4d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll @@ -3,4 +3,4 @@ import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.TInstruction as TInstruction import semmle.code.cpp.ir.implementation.raw.IR as RawIR -import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand \ No newline at end of file +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index 295ffdfa1e0..243429603c7 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -12,7 +12,6 @@ private import semmle.code.cpp.ir.internal.Overlap * Provides the newtype used to represent operands across all phases of the IR. */ private module Internal { - /** * An IR operand. `TOperand` is shared across all phases of the IR. There are branches of this * type for operands created directly from the AST (`TRegisterOperand` and `TNonSSAMemoryOperand`), diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll index 2e5b8cc6ed3..219180d9f4d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll @@ -3,4 +3,4 @@ import semmle.code.cpp.ir.implementation.internal.OperandTag as OperandTag import semmle.code.cpp.ir.internal.Overlap as Overlap import semmle.code.cpp.ir.implementation.internal.TInstruction as TInstruction import semmle.code.cpp.ir.implementation.raw.IR as RawIR -import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand \ No newline at end of file +import semmle.code.cpp.ir.implementation.internal.TOperand as TOperand diff --git a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll index 361e4105ae7..143201eea14 100644 --- a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll +++ b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll @@ -10,7 +10,6 @@ private import experimental.ir.internal.Overlap * Provides the newtype used to represent operands across all phases of the IR. */ private module Internal { - /** * An IR operand. `TOperand` is shared across all phases of the IR. There are branches of this * type for operands created directly from the AST (`TRegisterOperand` and `TNonSSAMemoryOperand`), From 435502e070182c9a7b0f7e30b2593fbe02dd24ff Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Wed, 6 Jan 2021 23:05:09 +0530 Subject: [PATCH 015/757] missing new lines --- .../test/library-tests/dataflow/taintsources/PlayResource.java | 2 +- java/ql/test/library-tests/dataflow/taintsources/options | 2 +- .../test/library-tests/frameworks/play/resources/Resource.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java b/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java index cffa512b02f..547aad11edf 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java +++ b/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java @@ -31,4 +31,4 @@ public class PlayResource extends Controller { String return_code = no_action; return return_code; } -} \ No newline at end of file +} diff --git a/java/ql/test/library-tests/dataflow/taintsources/options b/java/ql/test/library-tests/dataflow/taintsources/options index d31f7858d33..1230949dd48 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/options +++ b/java/ql/test/library-tests/dataflow/taintsources/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/springframework-5.2.3:${testdir}/../../../stubs/google-android-9.0.0:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jackson-databind-2.10:${testdir}/../../../stubs/akka-2.6.x \ No newline at end of file +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/springframework-5.2.3:${testdir}/../../../stubs/google-android-9.0.0:${testdir}/../../../stubs/playframework-2.6.x:${testdir}/../../../stubs/jackson-databind-2.10:${testdir}/../../../stubs/akka-2.6.x diff --git a/java/ql/test/library-tests/frameworks/play/resources/Resource.java b/java/ql/test/library-tests/frameworks/play/resources/Resource.java index faf5da5b85a..3885920f85b 100644 --- a/java/ql/test/library-tests/frameworks/play/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/play/resources/Resource.java @@ -34,4 +34,4 @@ public class Resource extends Controller { String return_code = no_action; return return_code; } -} \ No newline at end of file +} From 99401f6e84cc74cdbef72030cb42671f737d06fb Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Fri, 8 Jan 2021 20:11:56 +0100 Subject: [PATCH 016/757] Java: Query for detecting JEXL injections --- .../Security/CWE/CWE-094/JexlInjection.qhelp | 46 ++ .../Security/CWE/CWE-094/JexlInjection.ql | 19 + .../Security/CWE/CWE-094/JexlInjectionLib.qll | 440 ++++++++++++++++++ .../UnsafeJexlExpressionEvaluation.java | 11 + .../security/CWE-094/Jexl2Injection.java | 120 +++++ .../security/CWE-094/Jexl3Injection.java | 135 ++++++ .../security/CWE-094/JexlInjection.expected | 180 +++++++ .../security/CWE-094/JexlInjection.qlref | 1 + .../query-tests/security/CWE-094/options | 2 +- .../org/apache/commons/jexl2/DebugInfo.java | 14 + .../org/apache/commons/jexl2/Expression.java | 7 + .../org/apache/commons/jexl2/JexlContext.java | 7 + .../org/apache/commons/jexl2/JexlEngine.java | 42 ++ .../org/apache/commons/jexl2/JexlInfo.java | 6 + .../org/apache/commons/jexl2/MapContext.java | 14 + .../org/apache/commons/jexl2/Script.java | 24 + .../org/apache/commons/jexl2/UnifiedJEXL.java | 47 ++ .../org/apache/commons/jexl3/JexlBuilder.java | 8 + .../org/apache/commons/jexl3/JexlContext.java | 3 + .../org/apache/commons/jexl3/JexlEngine.java | 34 ++ .../apache/commons/jexl3/JexlExpression.java | 8 + .../org/apache/commons/jexl3/JexlInfo.java | 5 + .../org/apache/commons/jexl3/JexlScript.java | 11 + .../org/apache/commons/jexl3/JxltEngine.java | 25 + .../org/apache/commons/jexl3/MapContext.java | 3 + 25 files changed, 1211 insertions(+), 1 deletion(-) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.ql create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/Jexl2Injection.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.qlref create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/DebugInfo.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Expression.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlContext.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlInfo.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/MapContext.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Script.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/UnifiedJEXL.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlContext.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlExpression.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlInfo.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlScript.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JxltEngine.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/MapContext.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp new file mode 100644 index 00000000000..94d74d1f6de --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp @@ -0,0 +1,46 @@ + + + + +

+Java EXpression Language (JEXL) is a simple expression language +provided by the Apache Commons JEXL library. +The syntax is close to a mix of ECMAScript and shell-script. +The language allows invocation of methods available in the JVM. +If a JEXL expression is built using attacker-controlled data, +and then evaluated, then it may allow the attacker to run arbitrary code. +

+ + + +

+Including user input in a JEXL expression should be avoided. +

+
+ + +

+The following example uses untrusted data to build and run a JEXL expression. +

+ +
+ + +
  • + Apache Commons JEXL: + Project page. +
  • +
  • + Apache Commons JEXL documentation: + JEXL 2.1.1 API. +
  • +
  • + Apache Commons JEXL documentation: + JEXL 3.1 API. +
  • +
  • + OWASP: + Expression Language Injection. +
  • +
    + \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.ql new file mode 100644 index 00000000000..98955af85f9 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.ql @@ -0,0 +1,19 @@ +/** + * @name Expression language injection (Jexl) + * @description Evaluation of a user-controlled Jexl expression + * may lead to arbitrary code execution. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/jexl-expression-injection + * @tags security + * external/cwe/cwe-094 + */ + +import java +import JexlInjectionLib +import DataFlow::PathGraph + +from DataFlow::PathNode source, DataFlow::PathNode sink, JexlInjectionConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Jexl injection from $@.", source.getNode(), "this user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll new file mode 100644 index 00000000000..f208267a4f8 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -0,0 +1,440 @@ +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.dataflow.TaintTracking + +/** + * A taint-tracking configuration for unsafe user input + * that is used to construct and evaluate a Jexl expression. + * It supports both Jexl2 and Jexl3. + */ +class JexlInjectionConfig extends TaintTracking::Configuration { + JexlInjectionConfig() { this = "JexlInjectionConfig" } + + override predicate isSource(DataFlow::Node source) { + source instanceof RemoteFlowSource or + source instanceof UserInput or + source instanceof EnvInput + } + + override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } + + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + creatingTaintedJexlExpression(node1, node2) or + creatingTaintedJexlTemplate(node1, node2) or + creatingTaintedJexlScript(node1, node2) or + creatingTaintedJexlCallable(node1, node2) + } +} + +/** + * A sink for Expresssion Language injection vulnerabilities via Jexl, + * i.e. methods that run evaluation of a Jexl expression. + */ +class JexlEvaluationSink extends DataFlow::ExprNode { + JexlEvaluationSink() { + isJexlExpressionEvaluationCall(asExpr()) or + isJexlTemplateEvaluationCall(asExpr()) or + isJexlScriptExecuteCall(asExpr()) or + isJexlGetSetPropertyCall(asExpr()) or + isCallableCall(asExpr()) + } +} + +/** + * Holds if `node1` to `node2` is a dataflow step that creates a Jexl expression. + */ +predicate creatingTaintedJexlExpression(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, Method m | ma.getMethod() = m | + ( + m instanceof JxltEngineCreateExpressionMethod or + m instanceof UnifiedJexlParseMethod or + m instanceof JexlEngineCreateExpressionMethod + ) and + ma.getAnArgument().getType() instanceof TypeString and + ma.getAnArgument() = node1.asExpr() and + node2.asExpr() = ma + ) +} + +/** + * Holds if `node1` to `node2` is a dataflow step that creates a Jexl expression. + */ +predicate creatingTaintedJxltEngineExpression(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, Method m | ma.getMethod() = m | + (m instanceof JxltEngineCreateExpressionMethod or m instanceof UnifiedJexlParseMethod) and + ma.getAnArgument().getType() instanceof TypeString and + ma.getAnArgument() = node1.asExpr() and + node2.asExpr() = ma + ) +} + +/** + * Holds if `node1` to `node2` is a dataflow step that creates a Jexl template. + */ +predicate creatingTaintedJexlTemplate(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, Method m | ma.getMethod() = m | + (m instanceof JxltEngineCreateTemplateMethod or m instanceof UnifiedJexlCreateTemplateMethod) and + ( + isCreateTemplateSourceArg(ma, 0, node1.asExpr()) or + isCreateTemplateSourceArg(ma, 1, node1.asExpr()) + ) and + node2.asExpr() = ma + ) +} + +/** + * Holds if: + * - `expr` is an argument with the `index` + * - `expr` is a string or an instance of `Reader` + */ +predicate isCreateTemplateSourceArg(MethodAccess ma, int index, Expr expr) { + ( + ma.getArgument(index).getType() instanceof TypeString or + ma.getArgument(index).getType() instanceof Reader + ) and + ma.getArgument(index) = expr +} + +/** + * Holds if `node1` to `node2` is a dataflow step that creates a Jexl script. + */ +predicate creatingTaintedJexlScript(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, Method m | ma.getMethod() = m | + m instanceof JexlEngineCreateScriptMethod and + ma.getArgument(0).getType() instanceof TypeString and + ma.getArgument(0) = node1.asExpr() and + node2.asExpr() = ma + ) +} + +/** + * Holds if `node1` to `node2` is a dataflow step + * that creates a callable from a Jexl expression or script. + */ +predicate creatingTaintedJexlCallable(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, Method m | ma.getMethod() = m | + (m instanceof JexlExpressionCallableMethod or m instanceof JexlScriptCallableMethod) and + ma.getQualifier() = node1.asExpr() and + node2.asExpr() = ma + ) +} + +/** + * Holds if `expr` is a call to one of the methods that execute a Jexl script. + */ +predicate isJexlScriptExecuteCall(Expr expr) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m instanceof JexlScriptExecuteMethod and + ma.getQualifier() = expr + ) +} + +/** + * Holds if `expr` is a call of the `Callable.call()` method. + */ +predicate isCallableCall(Expr expr) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m instanceof CallableCallMethod and + ma.getQualifier() = expr + ) +} + +/** + * Holds if `expr` is an argument in a call to one of the methods + * that get or set a property via a Jexl expression. + */ +predicate isJexlGetSetPropertyCall(Expr expr) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + (m instanceof JexlEngineGetPropertyMethod or m instanceof JexlEngineSetPropertyMethod) and + ma.getAnArgument().getType() instanceof TypeString and + ma.getAnArgument() = expr + ) +} + +/** + * Holds if `expr` is a call to one of the methods that trigger evaluation of a Jexl expression. + */ +predicate isJexlExpressionEvaluationCall(Expr expr) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + ( + m instanceof JexlExpressionEvaluateMethod or + m instanceof JxltEngineExpressionEvaluateMethod or + m instanceof JxltEngineExpressionPrepareMethod or + m instanceof UnifiedJexlExpressionEvaluateMethod or + m instanceof UnifiedJexlExpressionPrepareMethod + ) and + ma.getQualifier() = expr + ) +} + +/** + * Holds if `expr` is a call to one of the methods that evaluates a Jexl template. + */ +predicate isJexlTemplateEvaluationCall(Expr expr) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + ( + m instanceof JxltEngineTemplateEvaluateMethod or + m instanceof UnifiedJexlTemplateEvaluateMethod + ) and + ma.getQualifier() = expr + ) +} + +/** + * A method in the JexlExpression class that evaluates a Jexl expression. + */ +class JexlExpressionEvaluateMethod extends Method { + JexlExpressionEvaluateMethod() { + getDeclaringType() instanceof JexlExpression and + hasName("evaluate") + } +} + +/** + * A method in the JexlEngine class that creates a Jexl expression. + */ +class JexlEngineCreateExpressionMethod extends Method { + JexlEngineCreateExpressionMethod() { + getDeclaringType() instanceof JexlEngine and + hasName("createExpression") + } +} + +/** + * A method in the JexlEngine class that gets a property with a Jexl expression. + */ +class JexlEngineGetPropertyMethod extends Method { + JexlEngineGetPropertyMethod() { + getDeclaringType() instanceof JexlEngine and + hasName("getProperty") + } +} + +/** + * A method in the JexlEngine class that sets a property with a Jexl expression. + */ +class JexlEngineSetPropertyMethod extends Method { + JexlEngineSetPropertyMethod() { + getDeclaringType() instanceof JexlEngine and + hasName("setProperty") + } +} + +/** + * A method in the JexlEngine class that creates a Jexl script. + */ +class JexlEngineCreateScriptMethod extends Method { + JexlEngineCreateScriptMethod() { + getDeclaringType() instanceof JexlEngine and + hasName("createScript") + } +} + +/** + * A method in the JexlScript class that executes a Jexl script. + */ +class JexlScriptExecuteMethod extends Method { + JexlScriptExecuteMethod() { + getDeclaringType() instanceof JexlScript and + hasName("execute") + } +} + +/** + * A method in the JexlScript class that creates a Callable for a Jexl expression. + */ +class JexlExpressionCallableMethod extends Method { + JexlExpressionCallableMethod() { + getDeclaringType() instanceof JexlExpression and + hasName("callable") + } +} + +/** + * A method in the JexlScript class that creates a Callable for a Jexl script. + */ +class JexlScriptCallableMethod extends Method { + JexlScriptCallableMethod() { + getDeclaringType() instanceof JexlScript and + hasName("callable") + } +} + +/** + * A method in the Callable class that executes the Callable. + */ +class CallableCallMethod extends Method { + CallableCallMethod() { + getDeclaringType() instanceof CallableInterface and + hasName("call") + } +} + +/** + * A method in the JxltEngine class that creates an expression. + */ +class JxltEngineCreateExpressionMethod extends Method { + JxltEngineCreateExpressionMethod() { + getDeclaringType() instanceof JxltEngine and + hasName("createExpression") + } +} + +/** + * A method in the JxltEngine class that creates a template. + */ +class JxltEngineCreateTemplateMethod extends Method { + JxltEngineCreateTemplateMethod() { + getDeclaringType() instanceof JxltEngine and + hasName("createTemplate") + } +} + +/** + * A method in the JxltEngine.Expression class that evaluates an expression. + */ +class JxltEngineExpressionEvaluateMethod extends Method { + JxltEngineExpressionEvaluateMethod() { + getDeclaringType() instanceof JxltEngineExpression and + hasName("evaluate") + } +} + +/** + * A method in the JxltEngine.Expression class that evaluates the immediate sub-expressions. + */ +class JxltEngineExpressionPrepareMethod extends Method { + JxltEngineExpressionPrepareMethod() { + getDeclaringType() instanceof JxltEngineExpression and + hasName("prepare") + } +} + +/** + * A method in the JxltEngine.Template class that evaluates a template. + */ +class JxltEngineTemplateEvaluateMethod extends Method { + JxltEngineTemplateEvaluateMethod() { + getDeclaringType() instanceof JxltEngineTemplate and + hasName("evaluate") + } +} + +/** + * A method in the UnifiedJEXL class that creates an expression. + */ +class UnifiedJexlParseMethod extends Method { + UnifiedJexlParseMethod() { + getDeclaringType() instanceof UnifiedJexl and + hasName("parse") + } +} + +/** + * A method in the UnifiedJEXL class that creates a template. + */ +class UnifiedJexlCreateTemplateMethod extends Method { + UnifiedJexlCreateTemplateMethod() { + getDeclaringType() instanceof UnifiedJexl and + hasName("createTemplate") + } +} + +/** + * A method in the UnifiedJEXL.Expression class that evaluates a template. + */ +class UnifiedJexlExpressionEvaluateMethod extends Method { + UnifiedJexlExpressionEvaluateMethod() { + getDeclaringType() instanceof UnifiedJexlExpression and + hasName("evaluate") + } +} + +/** + * A method in the UnifiedJEXL.Expression class that evaluates the immediate sub-expressions. + */ +class UnifiedJexlExpressionPrepareMethod extends Method { + UnifiedJexlExpressionPrepareMethod() { + getDeclaringType() instanceof UnifiedJexlExpression and + hasName("prepare") + } +} + +/** + * A method in the UnifiedJEXL.Template class that evaluates a template. + */ +class UnifiedJexlTemplateEvaluateMethod extends Method { + UnifiedJexlTemplateEvaluateMethod() { + getDeclaringType() instanceof UnifiedJexlTemplate and + hasName("evaluate") + } +} + +class JexlExpression extends RefType { + JexlExpression() { + hasQualifiedName("org.apache.commons.jexl3", "JexlExpression") or + hasQualifiedName("org.apache.commons.jexl2", "Expression") + } +} + +class JexlScript extends RefType { + JexlScript() { + hasQualifiedName("org.apache.commons.jexl3", "JexlScript") or + hasQualifiedName("org.apache.commons.jexl2", "Script") + } +} + +class JexlEngine extends RefType { + JexlEngine() { + hasQualifiedName("org.apache.commons.jexl3", "JexlEngine") or + hasQualifiedName("org.apache.commons.jexl2", "JexlEngine") + } +} + +class JxltEngine extends RefType { + JxltEngine() { hasQualifiedName("org.apache.commons.jexl3", "JxltEngine") } +} + +class UnifiedJexl extends RefType { + UnifiedJexl() { hasQualifiedName("org.apache.commons.jexl2", "UnifiedJEXL") } +} + +class JxltEngineExpression extends NestedType { + JxltEngineExpression() { + getEnclosingType() instanceof JxltEngine and + hasName("Expression") + } +} + +class JxltEngineTemplate extends NestedType { + JxltEngineTemplate() { + getEnclosingType() instanceof JxltEngine and + hasName("Template") + } +} + +class UnifiedJexlExpression extends NestedType { + UnifiedJexlExpression() { + getEnclosingType() instanceof UnifiedJexl and + hasName("Expression") + } +} + +class UnifiedJexlTemplate extends NestedType { + UnifiedJexlTemplate() { + getEnclosingType() instanceof UnifiedJexl and + hasName("Template") + } +} + +class CallableInterface extends RefType { + CallableInterface() { + getSourceDeclaration() + .getASourceSupertype*() + .hasQualifiedName("java.util.concurrent", "Callable") + } +} + +class Reader extends RefType { + Reader() { hasQualifiedName("java.io", "Reader") } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java new file mode 100644 index 00000000000..ed238765737 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java @@ -0,0 +1,11 @@ +public void evaluate(Socket socket) throws IOException { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream()))) { + + String input = reader.readLine(); + JexlEngine jexl = new JexlBuilder().create(); + JexlExpression expression = jexl.createExpression(input); + JexlContext context = new MapContext(); + expression.evaluate(context); + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/Jexl2Injection.java b/java/ql/test/experimental/query-tests/security/CWE-094/Jexl2Injection.java new file mode 100644 index 00000000000..438e9b036b6 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/Jexl2Injection.java @@ -0,0 +1,120 @@ +import org.apache.commons.jexl2.*; + +import java.io.StringWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.function.Consumer; + +public class Jexl2Injection { + + private static void runJexlExpression(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + Expression e = jexl.createExpression(jexlExpr); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static void runJexlExpressionWithJexlInfo(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + Expression e = jexl.createExpression( + jexlExpr, new DebugInfo("unknown", 0, 0)); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static void runJexlScript(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + Script script = jexl.createScript(jexlExpr); + JexlContext jc = new MapContext(); + script.execute(jc); + } + + private static void runJexlScriptViaCallable(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + Script script = jexl.createScript(jexlExpr); + JexlContext jc = new MapContext(); + + try { + script.callable(jc).call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void runJexlExpressionViaGetProperty(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + jexl.getProperty(new Object(), jexlExpr); + } + + private static void runJexlExpressionViaSetProperty(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + jexl.setProperty(new Object(), jexlExpr, new Object()); + } + + private static void runJexlExpressionViaUnifiedJEXLParseAndEvaluate(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + UnifiedJEXL unifiedJEXL = new UnifiedJEXL(jexl); + unifiedJEXL.parse(jexlExpr).evaluate(new MapContext()); + } + + private static void runJexlExpressionViaUnifiedJEXLParseAndPrepare(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + UnifiedJEXL unifiedJEXL = new UnifiedJEXL(jexl); + unifiedJEXL.parse(jexlExpr).prepare(new MapContext()); + } + + private static void runJexlExpressionViaUnifiedJEXLTemplateEvaluate(String jexlExpr) { + JexlEngine jexl = new JexlEngine(); + UnifiedJEXL unifiedJEXL = new UnifiedJEXL(jexl); + unifiedJEXL.createTemplate(jexlExpr).evaluate(new MapContext(), new StringWriter()); + } + + private static void testWithSocket(Consumer action) throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0)) { + try (Socket socket = serverSocket.accept()) { + byte[] bytes = new byte[1024]; + int n = socket.getInputStream().read(bytes); + String jexlExpr = new String(bytes, 0, n); + action.accept(jexlExpr); + } + } + } + + // below are tests for the query + + public static void testWithJexlExpressionEvaluate() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpression); + } + + public static void testWithJexlExpressionEvaluateWithInfo() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpressionWithJexlInfo); + } + + public static void testWithJexlScriptExecute() throws Exception { + testWithSocket(Jexl2Injection::runJexlScript); + } + + public static void testWithJexlScriptCallable() throws Exception { + testWithSocket(Jexl2Injection::runJexlScriptViaCallable); + } + + public static void testWithJexlEngineGetProperty() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpressionViaGetProperty); + } + + public static void testWithJexlEngineSetProperty() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpressionViaSetProperty); + } + + public static void testWithUnifiedJEXLParseAndEvaluate() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpressionViaUnifiedJEXLParseAndEvaluate); + } + + public static void testWithUnifiedJEXLParseAndPrepare() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpressionViaUnifiedJEXLParseAndPrepare); + } + + public static void testWithUnifiedJEXLTemplateEvaluate() throws Exception { + testWithSocket(Jexl2Injection::runJexlExpressionViaUnifiedJEXLTemplateEvaluate); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java b/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java new file mode 100644 index 00000000000..0f3147e6c3b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java @@ -0,0 +1,135 @@ +import java.io.StringWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.function.Consumer; + +import org.apache.commons.jexl3.*; + +public class Jexl3Injection { + + private static void runJexlExpression(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JexlExpression e = jexl.createExpression(jexlExpr); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static void runJexlExpressionWithJexlInfo(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JexlExpression e = jexl.createExpression(new JexlInfo("unknown", 0, 0), jexlExpr); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static void runJexlScript(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JexlScript script = jexl.createScript(jexlExpr); + JexlContext jc = new MapContext(); + script.execute(jc); + } + + private static void runJexlScriptViaCallable(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JexlScript script = jexl.createScript(jexlExpr); + JexlContext jc = new MapContext(); + + try { + script.callable(jc).call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void runJexlExpressionViaGetProperty(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + jexl.getProperty(new Object(), jexlExpr); + } + + private static void runJexlExpressionViaSetProperty(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + jexl.setProperty(new Object(), jexlExpr, new Object()); + } + + private static void runJexlExpressionViaJxltEngineExpressionEvaluate(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JxltEngine jxlt = jexl.createJxltEngine(); + jxlt.createExpression(jexlExpr).evaluate(new MapContext()); + } + + private static void runJexlExpressionViaJxltEngineExpressionPrepare(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JxltEngine jxlt = jexl.createJxltEngine(); + jxlt.createExpression(jexlExpr).prepare(new MapContext()); + } + + private static void runJexlExpressionViaJxltEngineTemplateEvaluate(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JxltEngine jxlt = jexl.createJxltEngine(); + jxlt.createTemplate(jexlExpr).evaluate(new MapContext(), new StringWriter()); + } + + private static void runJexlExpressionViaCallable(String jexlExpr) { + JexlEngine jexl = new JexlBuilder().create(); + JexlExpression e = jexl.createExpression(jexlExpr); + JexlContext jc = new MapContext(); + + try { + e.callable(jc).call(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static void testWithSocket(Consumer action) throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0)) { + try (Socket socket = serverSocket.accept()) { + byte[] bytes = new byte[1024]; + int n = socket.getInputStream().read(bytes); + String jexlExpr = new String(bytes, 0, n); + action.accept(jexlExpr); + } + } + } + + // below are tests for the query + + public static void testWithJexlExpressionEvaluate() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpression); + } + + public static void testWithJexlExpressionEvaluateWithInfo() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionWithJexlInfo); + } + + public static void testWithJexlScriptExecute() throws Exception { + testWithSocket(Jexl3Injection::runJexlScript); + } + + public static void testWithJexlScriptCallable() throws Exception { + testWithSocket(Jexl3Injection::runJexlScriptViaCallable); + } + + public static void testWithJexlEngineGetProperty() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionViaGetProperty); + } + + public static void testWithJexlEngineSetProperty() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionViaSetProperty); + } + + public static void testWithJxltEngineExpressionEvaluate() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionViaJxltEngineExpressionEvaluate); + } + + public static void testWithJxltEngineExpressionPrepare() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionViaJxltEngineExpressionPrepare); + } + + public static void testWithJxltEngineTemplateEvaluate() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionViaJxltEngineTemplateEvaluate); + } + + public static void testWithJexlExpressionCallable() throws Exception { + testWithSocket(Jexl3Injection::runJexlExpressionViaCallable); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected new file mode 100644 index 00000000000..ba6891e4103 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected @@ -0,0 +1,180 @@ +edges +| Jexl2Injection.java:10:43:10:57 | jexlExpr : String | Jexl2Injection.java:14:9:14:9 | e | +| Jexl2Injection.java:17:55:17:69 | jexlExpr : String | Jexl2Injection.java:22:9:22:9 | e | +| Jexl2Injection.java:25:39:25:53 | jexlExpr : String | Jexl2Injection.java:29:9:29:14 | script | +| Jexl2Injection.java:32:50:32:64 | jexlExpr : String | Jexl2Injection.java:38:13:38:31 | callable(...) | +| Jexl2Injection.java:44:57:44:71 | jexlExpr : String | Jexl2Injection.java:46:40:46:47 | jexlExpr | +| Jexl2Injection.java:49:57:49:71 | jexlExpr : String | Jexl2Injection.java:51:40:51:47 | jexlExpr | +| Jexl2Injection.java:54:73:54:87 | jexlExpr : String | Jexl2Injection.java:57:9:57:35 | parse(...) | +| Jexl2Injection.java:60:72:60:86 | jexlExpr : String | Jexl2Injection.java:63:9:63:35 | parse(...) | +| Jexl2Injection.java:66:73:66:87 | jexlExpr : String | Jexl2Injection.java:69:9:69:44 | createTemplate(...) | +| Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:78:31:78:38 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:86:24:86:56 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:90:24:90:68 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:94:24:94:52 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:98:24:98:63 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:102:24:102:70 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:106:24:106:70 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:110:24:110:86 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:114:24:114:85 | jexlExpr : String | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | +| Jexl2Injection.java:86:24:86:56 | jexlExpr : String | Jexl2Injection.java:10:43:10:57 | jexlExpr : String | +| Jexl2Injection.java:86:24:86:56 | jexlExpr : String | Jexl2Injection.java:86:24:86:56 | jexlExpr : String | +| Jexl2Injection.java:90:24:90:68 | jexlExpr : String | Jexl2Injection.java:17:55:17:69 | jexlExpr : String | +| Jexl2Injection.java:90:24:90:68 | jexlExpr : String | Jexl2Injection.java:90:24:90:68 | jexlExpr : String | +| Jexl2Injection.java:94:24:94:52 | jexlExpr : String | Jexl2Injection.java:25:39:25:53 | jexlExpr : String | +| Jexl2Injection.java:94:24:94:52 | jexlExpr : String | Jexl2Injection.java:94:24:94:52 | jexlExpr : String | +| Jexl2Injection.java:98:24:98:63 | jexlExpr : String | Jexl2Injection.java:32:50:32:64 | jexlExpr : String | +| Jexl2Injection.java:98:24:98:63 | jexlExpr : String | Jexl2Injection.java:98:24:98:63 | jexlExpr : String | +| Jexl2Injection.java:102:24:102:70 | jexlExpr : String | Jexl2Injection.java:44:57:44:71 | jexlExpr : String | +| Jexl2Injection.java:102:24:102:70 | jexlExpr : String | Jexl2Injection.java:102:24:102:70 | jexlExpr : String | +| Jexl2Injection.java:106:24:106:70 | jexlExpr : String | Jexl2Injection.java:49:57:49:71 | jexlExpr : String | +| Jexl2Injection.java:106:24:106:70 | jexlExpr : String | Jexl2Injection.java:106:24:106:70 | jexlExpr : String | +| Jexl2Injection.java:110:24:110:86 | jexlExpr : String | Jexl2Injection.java:54:73:54:87 | jexlExpr : String | +| Jexl2Injection.java:110:24:110:86 | jexlExpr : String | Jexl2Injection.java:110:24:110:86 | jexlExpr : String | +| Jexl2Injection.java:114:24:114:85 | jexlExpr : String | Jexl2Injection.java:60:72:60:86 | jexlExpr : String | +| Jexl2Injection.java:114:24:114:85 | jexlExpr : String | Jexl2Injection.java:114:24:114:85 | jexlExpr : String | +| Jexl2Injection.java:118:24:118:86 | jexlExpr : String | Jexl2Injection.java:66:73:66:87 | jexlExpr : String | +| Jexl2Injection.java:118:24:118:86 | jexlExpr : String | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | +| Jexl3Injection.java:10:43:10:57 | jexlExpr : String | Jexl3Injection.java:14:9:14:9 | e | +| Jexl3Injection.java:17:55:17:69 | jexlExpr : String | Jexl3Injection.java:21:9:21:9 | e | +| Jexl3Injection.java:24:39:24:53 | jexlExpr : String | Jexl3Injection.java:28:9:28:14 | script | +| Jexl3Injection.java:31:50:31:64 | jexlExpr : String | Jexl3Injection.java:37:13:37:31 | callable(...) | +| Jexl3Injection.java:43:57:43:71 | jexlExpr : String | Jexl3Injection.java:45:40:45:47 | jexlExpr | +| Jexl3Injection.java:48:57:48:71 | jexlExpr : String | Jexl3Injection.java:50:40:50:47 | jexlExpr | +| Jexl3Injection.java:53:74:53:88 | jexlExpr : String | Jexl3Injection.java:56:9:56:39 | createExpression(...) | +| Jexl3Injection.java:59:73:59:87 | jexlExpr : String | Jexl3Injection.java:62:9:62:39 | createExpression(...) | +| Jexl3Injection.java:65:72:65:86 | jexlExpr : String | Jexl3Injection.java:68:9:68:37 | createTemplate(...) | +| Jexl3Injection.java:71:54:71:68 | jexlExpr : String | Jexl3Injection.java:77:13:77:26 | callable(...) | +| Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:89:31:89:38 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:97:24:97:56 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:101:24:101:68 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:105:24:105:52 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:109:24:109:63 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:113:24:113:70 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:117:24:117:70 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:121:24:121:87 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:125:24:125:86 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:129:24:129:85 | jexlExpr : String | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:133:24:133:67 | jexlExpr : String | +| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | Jexl3Injection.java:10:43:10:57 | jexlExpr : String | +| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | Jexl3Injection.java:97:24:97:56 | jexlExpr : String | +| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | Jexl3Injection.java:17:55:17:69 | jexlExpr : String | +| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | Jexl3Injection.java:101:24:101:68 | jexlExpr : String | +| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | Jexl3Injection.java:24:39:24:53 | jexlExpr : String | +| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | Jexl3Injection.java:105:24:105:52 | jexlExpr : String | +| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | Jexl3Injection.java:31:50:31:64 | jexlExpr : String | +| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | Jexl3Injection.java:109:24:109:63 | jexlExpr : String | +| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | Jexl3Injection.java:43:57:43:71 | jexlExpr : String | +| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | Jexl3Injection.java:113:24:113:70 | jexlExpr : String | +| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | +| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | Jexl3Injection.java:117:24:117:70 | jexlExpr : String | +| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | Jexl3Injection.java:53:74:53:88 | jexlExpr : String | +| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | Jexl3Injection.java:121:24:121:87 | jexlExpr : String | +| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | Jexl3Injection.java:59:73:59:87 | jexlExpr : String | +| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | Jexl3Injection.java:125:24:125:86 | jexlExpr : String | +| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | Jexl3Injection.java:65:72:65:86 | jexlExpr : String | +| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | Jexl3Injection.java:129:24:129:85 | jexlExpr : String | +| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | Jexl3Injection.java:71:54:71:68 | jexlExpr : String | +| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | Jexl3Injection.java:133:24:133:67 | jexlExpr : String | +nodes +| Jexl2Injection.java:10:43:10:57 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:14:9:14:9 | e | semmle.label | e | +| Jexl2Injection.java:17:55:17:69 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:22:9:22:9 | e | semmle.label | e | +| Jexl2Injection.java:25:39:25:53 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:29:9:29:14 | script | semmle.label | script | +| Jexl2Injection.java:32:50:32:64 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:38:13:38:31 | callable(...) | semmle.label | callable(...) | +| Jexl2Injection.java:44:57:44:71 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:46:40:46:47 | jexlExpr | semmle.label | jexlExpr | +| Jexl2Injection.java:49:57:49:71 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:51:40:51:47 | jexlExpr | semmle.label | jexlExpr | +| Jexl2Injection.java:54:73:54:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:57:9:57:35 | parse(...) | semmle.label | parse(...) | +| Jexl2Injection.java:60:72:60:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:63:9:63:35 | parse(...) | semmle.label | parse(...) | +| Jexl2Injection.java:66:73:66:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:69:9:69:44 | createTemplate(...) | semmle.label | createTemplate(...) | +| Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | +| Jexl2Injection.java:78:31:78:38 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:86:24:86:56 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:86:24:86:56 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:90:24:90:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:90:24:90:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:94:24:94:52 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:94:24:94:52 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:98:24:98:63 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:98:24:98:63 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:102:24:102:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:102:24:102:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:106:24:106:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:106:24:106:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:110:24:110:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:110:24:110:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:114:24:114:85 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:114:24:114:85 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:118:24:118:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl2Injection.java:118:24:118:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:10:43:10:57 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:14:9:14:9 | e | semmle.label | e | +| Jexl3Injection.java:17:55:17:69 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:21:9:21:9 | e | semmle.label | e | +| Jexl3Injection.java:24:39:24:53 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:28:9:28:14 | script | semmle.label | script | +| Jexl3Injection.java:31:50:31:64 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:37:13:37:31 | callable(...) | semmle.label | callable(...) | +| Jexl3Injection.java:43:57:43:71 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:45:40:45:47 | jexlExpr | semmle.label | jexlExpr | +| Jexl3Injection.java:48:57:48:71 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:50:40:50:47 | jexlExpr | semmle.label | jexlExpr | +| Jexl3Injection.java:53:74:53:88 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:56:9:56:39 | createExpression(...) | semmle.label | createExpression(...) | +| Jexl3Injection.java:59:73:59:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:62:9:62:39 | createExpression(...) | semmle.label | createExpression(...) | +| Jexl3Injection.java:65:72:65:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:68:9:68:37 | createTemplate(...) | semmle.label | createTemplate(...) | +| Jexl3Injection.java:71:54:71:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:77:13:77:26 | callable(...) | semmle.label | callable(...) | +| Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | +| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | semmle.label | jexlExpr : String | +#select +| Jexl2Injection.java:14:9:14:9 | e | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:14:9:14:9 | e | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:22:9:22:9 | e | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:22:9:22:9 | e | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:29:9:29:14 | script | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:29:9:29:14 | script | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:38:13:38:31 | callable(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:38:13:38:31 | callable(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:46:40:46:47 | jexlExpr | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:46:40:46:47 | jexlExpr | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:51:40:51:47 | jexlExpr | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:51:40:51:47 | jexlExpr | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:57:9:57:35 | parse(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:57:9:57:35 | parse(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:63:9:63:35 | parse(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:63:9:63:35 | parse(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:69:9:69:44 | createTemplate(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:69:9:69:44 | createTemplate(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:14:9:14:9 | e | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:14:9:14:9 | e | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:21:9:21:9 | e | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:21:9:21:9 | e | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:28:9:28:14 | script | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:28:9:28:14 | script | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:37:13:37:31 | callable(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:37:13:37:31 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:45:40:45:47 | jexlExpr | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:45:40:45:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:56:9:56:39 | createExpression(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:56:9:56:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:62:9:62:39 | createExpression(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:62:9:62:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:68:9:68:37 | createTemplate(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:68:9:68:37 | createTemplate(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:77:13:77:26 | callable(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:77:13:77:26 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.qlref new file mode 100644 index 00000000000..82ad87b1751 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-094/JexlInjection.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/options b/java/ql/test/experimental/query-tests/security/CWE-094/options index 51fae354cec..5db775470d8 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/options +++ b/java/ql/test/experimental/query-tests/security/CWE-094/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1 diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/DebugInfo.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/DebugInfo.java new file mode 100644 index 00000000000..8d699d9f3b9 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/DebugInfo.java @@ -0,0 +1,14 @@ +package org.apache.commons.jexl2; + +public class DebugInfo implements JexlInfo { + + public DebugInfo(String tn, int l, int c) {} + + public String debugString() { + return null; + } + + public DebugInfo debugInfo() { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Expression.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Expression.java new file mode 100644 index 00000000000..798035f5cfb --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Expression.java @@ -0,0 +1,7 @@ +package org.apache.commons.jexl2; + +public interface Expression { + Object evaluate(JexlContext var1); + String getExpression(); + String dump(); +} diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlContext.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlContext.java new file mode 100644 index 00000000000..1bafabaa287 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlContext.java @@ -0,0 +1,7 @@ +package org.apache.commons.jexl2; + +public interface JexlContext { + Object get(String var1); + void set(String var1, Object var2); + boolean has(String var1); +} diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java new file mode 100644 index 00000000000..5d49850fe7c --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java @@ -0,0 +1,42 @@ +package org.apache.commons.jexl2; + +public class JexlEngine { + + public Expression createExpression(String expression) { + return null; + } + + public Expression createExpression(String expression, JexlInfo info) { + return null; + } + + public Script createScript(String scriptText) { + return null; + } + + public Script createScript(String scriptText, JexlInfo info) { + return null; + } + + public Script createScript(String scriptText, String... names) { + return null; + } + + public Script createScript(String scriptText, JexlInfo info, String[] names) { + return null; + } + + public Object getProperty(Object bean, String expr) { + return null; + } + + public Object getProperty(JexlContext context, Object bean, String expr) { + return null; + } + + public void setProperty(Object bean, String expr, Object value) {} + + public void setProperty(JexlContext context, Object bean, String expr, Object value) {} + + +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlInfo.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlInfo.java new file mode 100644 index 00000000000..8ee979713d0 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlInfo.java @@ -0,0 +1,6 @@ +package org.apache.commons.jexl2; + +public interface JexlInfo { + String debugString(); + DebugInfo debugInfo(); +} diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/MapContext.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/MapContext.java new file mode 100644 index 00000000000..5b96ef5a7c7 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/MapContext.java @@ -0,0 +1,14 @@ +package org.apache.commons.jexl2; + +public class MapContext implements JexlContext { + + public Object get(String var1) { + return null; + } + + public void set(String var1, Object var2) {} + + public boolean has(String var1) { + return false; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Script.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Script.java new file mode 100644 index 00000000000..5104915302b --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/Script.java @@ -0,0 +1,24 @@ +package org.apache.commons.jexl2; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; + +public interface Script { + + Object execute(JexlContext var1); + + Object execute(JexlContext var1, Object... var2); + + String getText(); + + String[] getParameters(); + + String[] getLocalVariables(); + + Set> getVariables(); + + Callable callable(JexlContext var1); + + Callable callable(JexlContext var1, Object... var2); +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/UnifiedJEXL.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/UnifiedJEXL.java new file mode 100644 index 00000000000..4e1e97f6c0b --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/UnifiedJEXL.java @@ -0,0 +1,47 @@ +package org.apache.commons.jexl2; + +import java.io.Writer; +import java.io.Reader; + +public final class UnifiedJEXL { + + public UnifiedJEXL(JexlEngine jexl) {} + + public UnifiedJEXL.Expression parse(String expression) { + return null; + } + + public UnifiedJEXL.Template createTemplate(String prefix, Reader source, String... parms) { + return null; + } + + public UnifiedJEXL.Template createTemplate(String source, String... parms) { + return null; + } + + public UnifiedJEXL.Template createTemplate(String source) { + return null; + } + + public final class Template { + + public UnifiedJEXL.Template prepare(JexlContext context) { + return null; + } + + public void evaluate(JexlContext context, Writer writer) {} + + public void evaluate(JexlContext context, Writer writer, Object... args) {} + } + + public abstract class Expression { + + public UnifiedJEXL.Expression prepare(JexlContext context) { + return null; + } + + public Object evaluate(JexlContext context) { + return null; + } + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java new file mode 100644 index 00000000000..cba900c25b9 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java @@ -0,0 +1,8 @@ +package org.apache.commons.jexl3; + +public class JexlBuilder { + + public JexlEngine create() { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlContext.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlContext.java new file mode 100644 index 00000000000..5130359a956 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlContext.java @@ -0,0 +1,3 @@ +package org.apache.commons.jexl3; + +public interface JexlContext {} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java new file mode 100644 index 00000000000..e0674da8654 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java @@ -0,0 +1,34 @@ +package org.apache.commons.jexl3; + +public abstract class JexlEngine { + + public JexlExpression createExpression(JexlInfo info, String expression) { + return null; + } + + public JexlExpression createExpression(String expression) { + return null; + } + + public JexlScript createScript(JexlInfo info, String source, String[] names) { + return null; + } + + public JexlScript createScript(String scriptText) { + return null; + } + + public JexlScript createScript(String scriptText, String... names) { + return null; + } + + public JxltEngine createJxltEngine() { + return null; + } + + public void setProperty(Object bean, String expr, Object value) {} + + public Object getProperty(Object bean, String expr) { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlExpression.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlExpression.java new file mode 100644 index 00000000000..1dc3f90f1ff --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlExpression.java @@ -0,0 +1,8 @@ +package org.apache.commons.jexl3; + +import java.util.concurrent.Callable; + +public interface JexlExpression { + Object evaluate(JexlContext context); + Callable callable(JexlContext context); +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlInfo.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlInfo.java new file mode 100644 index 00000000000..f88c4968077 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlInfo.java @@ -0,0 +1,5 @@ +package org.apache.commons.jexl3; + +public class JexlInfo { + public JexlInfo(String source, int l, int c) {} +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlScript.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlScript.java new file mode 100644 index 00000000000..2a74a189531 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlScript.java @@ -0,0 +1,11 @@ +package org.apache.commons.jexl3; + +import java.util.concurrent.Callable; + +public interface JexlScript { + + Object execute(JexlContext context); + Object execute(JexlContext context, Object... args); + Callable callable(JexlContext context); + Callable callable(JexlContext context, Object... args); +} diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JxltEngine.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JxltEngine.java new file mode 100644 index 00000000000..3344d10463c --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JxltEngine.java @@ -0,0 +1,25 @@ +package org.apache.commons.jexl3; + +import java.io.Writer; + +public class JxltEngine { + + public Expression createExpression(String expression) { + return null; + } + + public Template createTemplate(String source) { + return null; + } + + public interface Expression { + Object evaluate(JexlContext context); + Expression prepare(JexlContext context); + } + + public interface Template { + void evaluate(JexlContext context, Writer writer); + void evaluate(JexlContext context, Writer writer, Object... args); + Template prepare(JexlContext context); + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/MapContext.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/MapContext.java new file mode 100644 index 00000000000..394278a6216 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/MapContext.java @@ -0,0 +1,3 @@ +package org.apache.commons.jexl3; + +public class MapContext implements JexlContext {} \ No newline at end of file From 7d2d27394b187bee1dd5e9259a5d010473306535 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sun, 17 Jan 2021 22:28:42 +0100 Subject: [PATCH 017/757] Java: Added a source and a taint step for JexlInjectionConfig - Added TaintedSpringRequestBody source - Added returningTaintedDataFromBean() taint step - Added tests --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 26 ++- .../security/CWE-094/Jexl3Injection.java | 59 ++++++ .../security/CWE-094/JexlInjection.expected | 195 ++++++++++-------- .../springframework/http/ResponseEntity.java | 4 + .../web/bind/annotation/PathVariable.java | 14 ++ .../web/bind/annotation/PostMapping.java | 16 ++ .../web/bind/annotation/RequestBody.java | 15 ++ .../web/bind/annotation/RequestMapping.java | 5 +- .../web/bind/annotation/RequestMethod.java | 6 + 9 files changed, 248 insertions(+), 92 deletions(-) create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PathVariable.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PostMapping.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestBody.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMethod.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index f208267a4f8..97c9eb840a1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -11,6 +11,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration { JexlInjectionConfig() { this = "JexlInjectionConfig" } override predicate isSource(DataFlow::Node source) { + source instanceof TaintedSpringRequestBody or source instanceof RemoteFlowSource or source instanceof UserInput or source instanceof EnvInput @@ -22,7 +23,18 @@ class JexlInjectionConfig extends TaintTracking::Configuration { creatingTaintedJexlExpression(node1, node2) or creatingTaintedJexlTemplate(node1, node2) or creatingTaintedJexlScript(node1, node2) or - creatingTaintedJexlCallable(node1, node2) + creatingTaintedJexlCallable(node1, node2) or + returningTaintedDataFromBean(node1, node2) + } +} + +/** + * A data flow source for parameters that have + * a Spring framework annotation indicating remote user input from servlets. + */ +class TaintedSpringRequestBody extends DataFlow::Node { + TaintedSpringRequestBody() { + exists(SpringServletInputAnnotation a | this.asParameter().getAnAnnotation() = a) } } @@ -119,6 +131,18 @@ predicate creatingTaintedJexlCallable(DataFlow::Node node1, DataFlow::Node node2 ) } +/** + * Holds if `node1` to `node2` is a dataflow step that returns data from + * a tainted bean by calling one of its getters. + */ +predicate returningTaintedDataFromBean(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, Method m | ma.getMethod() = m | + m instanceof GetterMethod and + ma.getQualifier() = node1.asExpr() and + ma = node2.asExpr() + ) +} + /** * Holds if `expr` is a call to one of the methods that execute a Jexl script. */ diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java b/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java index 0f3147e6c3b..67089546dd4 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java +++ b/java/ql/test/experimental/query-tests/security/CWE-094/Jexl3Injection.java @@ -4,6 +4,11 @@ import java.net.Socket; import java.util.function.Consumer; import org.apache.commons.jexl3.*; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; public class Jexl3Injection { @@ -132,4 +137,58 @@ public class Jexl3Injection { public static void testWithJexlExpressionCallable() throws Exception { testWithSocket(Jexl3Injection::runJexlExpressionViaCallable); } + + @PostMapping("/request") + public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromPathVariable( + @PathVariable String expr) { + + runJexlExpression(expr); + return ResponseEntity.ok(HttpStatus.OK); + } + + @PostMapping("/request") + public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromRequestBody( + @RequestBody Data data) { + + String expr = data.getExpr(); + runJexlExpression(expr); + + return ResponseEntity.ok(HttpStatus.OK); + } + + @PostMapping("/request") + public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects( + @RequestBody CustomRequest customRequest) { + + String expr = customRequest.getData().getExpr(); + runJexlExpression(expr); + + return ResponseEntity.ok(HttpStatus.OK); + } + + public static class CustomRequest { + + private Data data; + + CustomRequest(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + } + + public static class Data { + + private String expr; + + Data(String expr) { + this.expr = expr; + } + + public String getExpr() { + return expr; + } + } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected index ba6891e4103..0fc01073a43 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected @@ -36,47 +36,53 @@ edges | Jexl2Injection.java:114:24:114:85 | jexlExpr : String | Jexl2Injection.java:114:24:114:85 | jexlExpr : String | | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | Jexl2Injection.java:66:73:66:87 | jexlExpr : String | | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | -| Jexl3Injection.java:10:43:10:57 | jexlExpr : String | Jexl3Injection.java:14:9:14:9 | e | -| Jexl3Injection.java:17:55:17:69 | jexlExpr : String | Jexl3Injection.java:21:9:21:9 | e | -| Jexl3Injection.java:24:39:24:53 | jexlExpr : String | Jexl3Injection.java:28:9:28:14 | script | -| Jexl3Injection.java:31:50:31:64 | jexlExpr : String | Jexl3Injection.java:37:13:37:31 | callable(...) | -| Jexl3Injection.java:43:57:43:71 | jexlExpr : String | Jexl3Injection.java:45:40:45:47 | jexlExpr | +| Jexl3Injection.java:15:43:15:57 | jexlExpr : String | Jexl3Injection.java:19:9:19:9 | e | +| Jexl3Injection.java:22:55:22:69 | jexlExpr : String | Jexl3Injection.java:26:9:26:9 | e | +| Jexl3Injection.java:29:39:29:53 | jexlExpr : String | Jexl3Injection.java:33:9:33:14 | script | +| Jexl3Injection.java:36:50:36:64 | jexlExpr : String | Jexl3Injection.java:42:13:42:31 | callable(...) | | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | Jexl3Injection.java:50:40:50:47 | jexlExpr | -| Jexl3Injection.java:53:74:53:88 | jexlExpr : String | Jexl3Injection.java:56:9:56:39 | createExpression(...) | -| Jexl3Injection.java:59:73:59:87 | jexlExpr : String | Jexl3Injection.java:62:9:62:39 | createExpression(...) | -| Jexl3Injection.java:65:72:65:86 | jexlExpr : String | Jexl3Injection.java:68:9:68:37 | createTemplate(...) | -| Jexl3Injection.java:71:54:71:68 | jexlExpr : String | Jexl3Injection.java:77:13:77:26 | callable(...) | -| Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:89:31:89:38 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:97:24:97:56 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:101:24:101:68 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:105:24:105:52 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:109:24:109:63 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:113:24:113:70 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:117:24:117:70 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:121:24:121:87 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:125:24:125:86 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:129:24:129:85 | jexlExpr : String | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | Jexl3Injection.java:133:24:133:67 | jexlExpr : String | -| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | Jexl3Injection.java:10:43:10:57 | jexlExpr : String | -| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | Jexl3Injection.java:97:24:97:56 | jexlExpr : String | -| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | Jexl3Injection.java:17:55:17:69 | jexlExpr : String | -| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | Jexl3Injection.java:101:24:101:68 | jexlExpr : String | -| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | Jexl3Injection.java:24:39:24:53 | jexlExpr : String | -| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | Jexl3Injection.java:105:24:105:52 | jexlExpr : String | -| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | Jexl3Injection.java:31:50:31:64 | jexlExpr : String | -| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | Jexl3Injection.java:109:24:109:63 | jexlExpr : String | -| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | Jexl3Injection.java:43:57:43:71 | jexlExpr : String | -| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | Jexl3Injection.java:113:24:113:70 | jexlExpr : String | -| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | -| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | Jexl3Injection.java:117:24:117:70 | jexlExpr : String | -| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | Jexl3Injection.java:53:74:53:88 | jexlExpr : String | -| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | Jexl3Injection.java:121:24:121:87 | jexlExpr : String | -| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | Jexl3Injection.java:59:73:59:87 | jexlExpr : String | -| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | Jexl3Injection.java:125:24:125:86 | jexlExpr : String | -| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | Jexl3Injection.java:65:72:65:86 | jexlExpr : String | -| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | Jexl3Injection.java:129:24:129:85 | jexlExpr : String | -| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | Jexl3Injection.java:71:54:71:68 | jexlExpr : String | -| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | Jexl3Injection.java:133:24:133:67 | jexlExpr : String | +| Jexl3Injection.java:53:57:53:71 | jexlExpr : String | Jexl3Injection.java:55:40:55:47 | jexlExpr | +| Jexl3Injection.java:58:74:58:88 | jexlExpr : String | Jexl3Injection.java:61:9:61:39 | createExpression(...) | +| Jexl3Injection.java:64:73:64:87 | jexlExpr : String | Jexl3Injection.java:67:9:67:39 | createExpression(...) | +| Jexl3Injection.java:70:72:70:86 | jexlExpr : String | Jexl3Injection.java:73:9:73:37 | createTemplate(...) | +| Jexl3Injection.java:76:54:76:68 | jexlExpr : String | Jexl3Injection.java:82:13:82:26 | callable(...) | +| Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:94:31:94:38 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:102:24:102:56 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:106:24:106:68 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:110:24:110:52 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:114:24:114:63 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:118:24:118:70 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:122:24:122:70 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:126:24:126:87 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:130:24:130:86 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:134:24:134:85 | jexlExpr : String | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:138:24:138:67 | jexlExpr : String | +| Jexl3Injection.java:102:24:102:56 | jexlExpr : String | Jexl3Injection.java:15:43:15:57 | jexlExpr : String | +| Jexl3Injection.java:102:24:102:56 | jexlExpr : String | Jexl3Injection.java:102:24:102:56 | jexlExpr : String | +| Jexl3Injection.java:106:24:106:68 | jexlExpr : String | Jexl3Injection.java:22:55:22:69 | jexlExpr : String | +| Jexl3Injection.java:106:24:106:68 | jexlExpr : String | Jexl3Injection.java:106:24:106:68 | jexlExpr : String | +| Jexl3Injection.java:110:24:110:52 | jexlExpr : String | Jexl3Injection.java:29:39:29:53 | jexlExpr : String | +| Jexl3Injection.java:110:24:110:52 | jexlExpr : String | Jexl3Injection.java:110:24:110:52 | jexlExpr : String | +| Jexl3Injection.java:114:24:114:63 | jexlExpr : String | Jexl3Injection.java:36:50:36:64 | jexlExpr : String | +| Jexl3Injection.java:114:24:114:63 | jexlExpr : String | Jexl3Injection.java:114:24:114:63 | jexlExpr : String | +| Jexl3Injection.java:118:24:118:70 | jexlExpr : String | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | +| Jexl3Injection.java:118:24:118:70 | jexlExpr : String | Jexl3Injection.java:118:24:118:70 | jexlExpr : String | +| Jexl3Injection.java:122:24:122:70 | jexlExpr : String | Jexl3Injection.java:53:57:53:71 | jexlExpr : String | +| Jexl3Injection.java:122:24:122:70 | jexlExpr : String | Jexl3Injection.java:122:24:122:70 | jexlExpr : String | +| Jexl3Injection.java:126:24:126:87 | jexlExpr : String | Jexl3Injection.java:58:74:58:88 | jexlExpr : String | +| Jexl3Injection.java:126:24:126:87 | jexlExpr : String | Jexl3Injection.java:126:24:126:87 | jexlExpr : String | +| Jexl3Injection.java:130:24:130:86 | jexlExpr : String | Jexl3Injection.java:64:73:64:87 | jexlExpr : String | +| Jexl3Injection.java:130:24:130:86 | jexlExpr : String | Jexl3Injection.java:130:24:130:86 | jexlExpr : String | +| Jexl3Injection.java:134:24:134:85 | jexlExpr : String | Jexl3Injection.java:70:72:70:86 | jexlExpr : String | +| Jexl3Injection.java:134:24:134:85 | jexlExpr : String | Jexl3Injection.java:134:24:134:85 | jexlExpr : String | +| Jexl3Injection.java:138:24:138:67 | jexlExpr : String | Jexl3Injection.java:76:54:76:68 | jexlExpr : String | +| Jexl3Injection.java:138:24:138:67 | jexlExpr : String | Jexl3Injection.java:138:24:138:67 | jexlExpr : String | +| Jexl3Injection.java:143:13:143:37 | expr : String | Jexl3Injection.java:145:27:145:30 | expr : String | +| Jexl3Injection.java:145:27:145:30 | expr : String | Jexl3Injection.java:15:43:15:57 | jexlExpr : String | +| Jexl3Injection.java:151:13:151:34 | data : Data | Jexl3Injection.java:154:27:154:30 | expr : String | +| Jexl3Injection.java:154:27:154:30 | expr : String | Jexl3Injection.java:15:43:15:57 | jexlExpr : String | +| Jexl3Injection.java:161:13:161:52 | customRequest : CustomRequest | Jexl3Injection.java:164:27:164:30 | expr : String | +| Jexl3Injection.java:164:27:164:30 | expr : String | Jexl3Injection.java:15:43:15:57 | jexlExpr : String | nodes | Jexl2Injection.java:10:43:10:57 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl2Injection.java:14:9:14:9 | e | semmle.label | e | @@ -116,48 +122,54 @@ nodes | Jexl2Injection.java:114:24:114:85 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl2Injection.java:118:24:118:86 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:10:43:10:57 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:14:9:14:9 | e | semmle.label | e | -| Jexl3Injection.java:17:55:17:69 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:21:9:21:9 | e | semmle.label | e | -| Jexl3Injection.java:24:39:24:53 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:28:9:28:14 | script | semmle.label | script | -| Jexl3Injection.java:31:50:31:64 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:37:13:37:31 | callable(...) | semmle.label | callable(...) | -| Jexl3Injection.java:43:57:43:71 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:45:40:45:47 | jexlExpr | semmle.label | jexlExpr | +| Jexl3Injection.java:15:43:15:57 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:19:9:19:9 | e | semmle.label | e | +| Jexl3Injection.java:22:55:22:69 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:26:9:26:9 | e | semmle.label | e | +| Jexl3Injection.java:29:39:29:53 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:33:9:33:14 | script | semmle.label | script | +| Jexl3Injection.java:36:50:36:64 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:42:13:42:31 | callable(...) | semmle.label | callable(...) | | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl3Injection.java:50:40:50:47 | jexlExpr | semmle.label | jexlExpr | -| Jexl3Injection.java:53:74:53:88 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:56:9:56:39 | createExpression(...) | semmle.label | createExpression(...) | -| Jexl3Injection.java:59:73:59:87 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:62:9:62:39 | createExpression(...) | semmle.label | createExpression(...) | -| Jexl3Injection.java:65:72:65:86 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:68:9:68:37 | createTemplate(...) | semmle.label | createTemplate(...) | -| Jexl3Injection.java:71:54:71:68 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:77:13:77:26 | callable(...) | semmle.label | callable(...) | -| Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| Jexl3Injection.java:89:31:89:38 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:97:24:97:56 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:101:24:101:68 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:105:24:105:52 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:109:24:109:63 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:113:24:113:70 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:117:24:117:70 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:121:24:121:87 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:125:24:125:86 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:129:24:129:85 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:133:24:133:67 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:53:57:53:71 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:55:40:55:47 | jexlExpr | semmle.label | jexlExpr | +| Jexl3Injection.java:58:74:58:88 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:61:9:61:39 | createExpression(...) | semmle.label | createExpression(...) | +| Jexl3Injection.java:64:73:64:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:67:9:67:39 | createExpression(...) | semmle.label | createExpression(...) | +| Jexl3Injection.java:70:72:70:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:73:9:73:37 | createTemplate(...) | semmle.label | createTemplate(...) | +| Jexl3Injection.java:76:54:76:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:82:13:82:26 | callable(...) | semmle.label | callable(...) | +| Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | +| Jexl3Injection.java:94:31:94:38 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:102:24:102:56 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:102:24:102:56 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:106:24:106:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:106:24:106:68 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:110:24:110:52 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:110:24:110:52 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:114:24:114:63 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:114:24:114:63 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:118:24:118:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:118:24:118:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:122:24:122:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:122:24:122:70 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:126:24:126:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:126:24:126:87 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:130:24:130:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:130:24:130:86 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:134:24:134:85 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:134:24:134:85 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:138:24:138:67 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:138:24:138:67 | jexlExpr : String | semmle.label | jexlExpr : String | +| Jexl3Injection.java:143:13:143:37 | expr : String | semmle.label | expr : String | +| Jexl3Injection.java:145:27:145:30 | expr : String | semmle.label | expr : String | +| Jexl3Injection.java:151:13:151:34 | data : Data | semmle.label | data : Data | +| Jexl3Injection.java:154:27:154:30 | expr : String | semmle.label | expr : String | +| Jexl3Injection.java:161:13:161:52 | customRequest : CustomRequest | semmle.label | customRequest : CustomRequest | +| Jexl3Injection.java:164:27:164:30 | expr : String | semmle.label | expr : String | #select | Jexl2Injection.java:14:9:14:9 | e | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:14:9:14:9 | e | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:22:9:22:9 | e | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:22:9:22:9 | e | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | @@ -168,13 +180,16 @@ nodes | Jexl2Injection.java:57:9:57:35 | parse(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:57:9:57:35 | parse(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:63:9:63:35 | parse(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:63:9:63:35 | parse(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:69:9:69:44 | createTemplate(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:69:9:69:44 | createTemplate(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:14:9:14:9 | e | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:14:9:14:9 | e | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:21:9:21:9 | e | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:21:9:21:9 | e | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:28:9:28:14 | script | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:28:9:28:14 | script | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:37:13:37:31 | callable(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:37:13:37:31 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:45:40:45:47 | jexlExpr | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:45:40:45:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:56:9:56:39 | createExpression(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:56:9:56:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:62:9:62:39 | createExpression(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:62:9:62:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:68:9:68:37 | createTemplate(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:68:9:68:37 | createTemplate(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:77:13:77:26 | callable(...) | Jexl3Injection.java:87:25:87:47 | getInputStream(...) : InputStream | Jexl3Injection.java:77:13:77:26 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:87:25:87:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:19:9:19:9 | e | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:19:9:19:9 | e | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:19:9:19:9 | e | Jexl3Injection.java:143:13:143:37 | expr : String | Jexl3Injection.java:19:9:19:9 | e | Jexl injection from $@. | Jexl3Injection.java:143:13:143:37 | expr | this user input | +| Jexl3Injection.java:19:9:19:9 | e | Jexl3Injection.java:151:13:151:34 | data : Data | Jexl3Injection.java:19:9:19:9 | e | Jexl injection from $@. | Jexl3Injection.java:151:13:151:34 | data | this user input | +| Jexl3Injection.java:19:9:19:9 | e | Jexl3Injection.java:161:13:161:52 | customRequest : CustomRequest | Jexl3Injection.java:19:9:19:9 | e | Jexl injection from $@. | Jexl3Injection.java:161:13:161:52 | customRequest | this user input | +| Jexl3Injection.java:26:9:26:9 | e | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:26:9:26:9 | e | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:33:9:33:14 | script | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:33:9:33:14 | script | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:42:13:42:31 | callable(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:42:13:42:31 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:55:40:55:47 | jexlExpr | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:55:40:55:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:61:9:61:39 | createExpression(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:61:9:61:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:67:9:67:39 | createExpression(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:67:9:67:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:73:9:73:37 | createTemplate(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:73:9:73:37 | createTemplate(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:82:13:82:26 | callable(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:82:13:82:26 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/http/ResponseEntity.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/http/ResponseEntity.java index 602b0eb2b5c..f19f687a32a 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/http/ResponseEntity.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/http/ResponseEntity.java @@ -9,4 +9,8 @@ public class ResponseEntity extends org.springframework.http.HttpEntity { // public ResponseEntity(T body, org.springframework.http.HttpStatus status) { // } + + public static ResponseEntity ok(T body) { + return null; + } } \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PathVariable.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PathVariable.java new file mode 100644 index 00000000000..52411ebce00 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PathVariable.java @@ -0,0 +1,14 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PathVariable { + +} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PostMapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PostMapping.java new file mode 100644 index 00000000000..960d715cecd --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/PostMapping.java @@ -0,0 +1,16 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.POST) +public @interface PostMapping { + + String[] value() default {}; +} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestBody.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestBody.java new file mode 100644 index 00000000000..63ef792e172 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestBody.java @@ -0,0 +1,15 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestBody { + + boolean required() default true; +} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java index 2d90f95346a..73ff8d1b03a 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java @@ -5,4 +5,7 @@ import java.lang.annotation.*; @Target(value={ElementType.METHOD,ElementType.TYPE}) @Retention(value=RetentionPolicy.RUNTIME) @Documented -public @interface RequestMapping { } +public @interface RequestMapping { + + RequestMethod[] method() default {}; +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMethod.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMethod.java new file mode 100644 index 00000000000..703ee96c264 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMethod.java @@ -0,0 +1,6 @@ +package org.springframework.web.bind.annotation; + +public enum RequestMethod { + + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE +} \ No newline at end of file From 7df813354aeeb6003ebaeb3044d71b590f2f8797 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 20 Jan 2021 20:26:48 +0100 Subject: [PATCH 018/757] Improved JexlInjectionLib.qll --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 97c9eb840a1..e760e561e97 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -20,11 +20,11 @@ class JexlInjectionConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { - creatingTaintedJexlExpression(node1, node2) or - creatingTaintedJexlTemplate(node1, node2) or - creatingTaintedJexlScript(node1, node2) or - creatingTaintedJexlCallable(node1, node2) or - returningTaintedDataFromBean(node1, node2) + createsJexlExpression(node1, node2) or + createsJexlTemplate(node1, node2) or + createsJexlScript(node1, node2) or + createsJexlCallable(node1, node2) or + returnsDataFromBean(node1, node2) } } @@ -55,7 +55,7 @@ class JexlEvaluationSink extends DataFlow::ExprNode { /** * Holds if `node1` to `node2` is a dataflow step that creates a Jexl expression. */ -predicate creatingTaintedJexlExpression(DataFlow::Node node1, DataFlow::Node node2) { +predicate createsJexlExpression(DataFlow::Node node1, DataFlow::Node node2) { exists(MethodAccess ma, Method m | ma.getMethod() = m | ( m instanceof JxltEngineCreateExpressionMethod or @@ -68,41 +68,30 @@ predicate creatingTaintedJexlExpression(DataFlow::Node node1, DataFlow::Node nod ) } -/** - * Holds if `node1` to `node2` is a dataflow step that creates a Jexl expression. - */ -predicate creatingTaintedJxltEngineExpression(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, Method m | ma.getMethod() = m | - (m instanceof JxltEngineCreateExpressionMethod or m instanceof UnifiedJexlParseMethod) and - ma.getAnArgument().getType() instanceof TypeString and - ma.getAnArgument() = node1.asExpr() and - node2.asExpr() = ma - ) -} - /** * Holds if `node1` to `node2` is a dataflow step that creates a Jexl template. */ -predicate creatingTaintedJexlTemplate(DataFlow::Node node1, DataFlow::Node node2) { +predicate createsJexlTemplate(DataFlow::Node node1, DataFlow::Node node2) { exists(MethodAccess ma, Method m | ma.getMethod() = m | (m instanceof JxltEngineCreateTemplateMethod or m instanceof UnifiedJexlCreateTemplateMethod) and ( - isCreateTemplateSourceArg(ma, 0, node1.asExpr()) or - isCreateTemplateSourceArg(ma, 1, node1.asExpr()) + node1.asExpr().getType() instanceof TypeString or + node1.asExpr().getType() instanceof Reader ) and + ma.getArgument([0, 1]) = node1.asExpr() and node2.asExpr() = ma ) } /** * Holds if: - * - `expr` is an argument with the `index` + * - `expr` is the `index`th argument to `ma` * - `expr` is a string or an instance of `Reader` */ -predicate isCreateTemplateSourceArg(MethodAccess ma, int index, Expr expr) { +predicate toberemoved(MethodAccess ma, int index, Expr expr) { ( - ma.getArgument(index).getType() instanceof TypeString or - ma.getArgument(index).getType() instanceof Reader + expr.getType() instanceof TypeString or + expr.getType() instanceof Reader ) and ma.getArgument(index) = expr } @@ -110,7 +99,7 @@ predicate isCreateTemplateSourceArg(MethodAccess ma, int index, Expr expr) { /** * Holds if `node1` to `node2` is a dataflow step that creates a Jexl script. */ -predicate creatingTaintedJexlScript(DataFlow::Node node1, DataFlow::Node node2) { +predicate createsJexlScript(DataFlow::Node node1, DataFlow::Node node2) { exists(MethodAccess ma, Method m | ma.getMethod() = m | m instanceof JexlEngineCreateScriptMethod and ma.getArgument(0).getType() instanceof TypeString and @@ -123,7 +112,7 @@ predicate creatingTaintedJexlScript(DataFlow::Node node1, DataFlow::Node node2) * Holds if `node1` to `node2` is a dataflow step * that creates a callable from a Jexl expression or script. */ -predicate creatingTaintedJexlCallable(DataFlow::Node node1, DataFlow::Node node2) { +predicate createsJexlCallable(DataFlow::Node node1, DataFlow::Node node2) { exists(MethodAccess ma, Method m | ma.getMethod() = m | (m instanceof JexlExpressionCallableMethod or m instanceof JexlScriptCallableMethod) and ma.getQualifier() = node1.asExpr() and @@ -135,7 +124,7 @@ predicate creatingTaintedJexlCallable(DataFlow::Node node1, DataFlow::Node node2 * Holds if `node1` to `node2` is a dataflow step that returns data from * a tainted bean by calling one of its getters. */ -predicate returningTaintedDataFromBean(DataFlow::Node node1, DataFlow::Node node2) { +predicate returnsDataFromBean(DataFlow::Node node1, DataFlow::Node node2) { exists(MethodAccess ma, Method m | ma.getMethod() = m | m instanceof GetterMethod and ma.getQualifier() = node1.asExpr() and @@ -144,7 +133,7 @@ predicate returningTaintedDataFromBean(DataFlow::Node node1, DataFlow::Node node } /** - * Holds if `expr` is a call to one of the methods that execute a Jexl script. + * Holds if `expr` calls one of the methods that execute a Jexl script against qualifier `expr`. */ predicate isJexlScriptExecuteCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | @@ -154,7 +143,8 @@ predicate isJexlScriptExecuteCall(Expr expr) { } /** - * Holds if `expr` is a call of the `Callable.call()` method. + * Holds if `expr` is the qualifier when calling the `Callable.call()` method + * such as `expr.call()`. */ predicate isCallableCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | From a56dd60baa3e882d5e85a3479fecae8cc4c6ac19 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 21 Jan 2021 19:18:10 +0800 Subject: [PATCH 019/757] *)add CWE-652 XQueryInjection detection --- .../Security/CWE/CWE-652/XQueryInjection.java | 35 +++++++++++++++++++ .../CWE/CWE-652/XQueryInjection.qhelp | 34 ++++++++++++++++++ .../Security/CWE/CWE-652/XQueryInjection.ql | 21 +++++++++++ .../CWE/CWE-652/XQueryInjectionLib.qll | 35 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 java/ql/src/Security/CWE/CWE-652/XQueryInjection.java create mode 100644 java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql create mode 100644 java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java new file mode 100644 index 00000000000..a60adb37db2 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java @@ -0,0 +1,35 @@ +import javax.servlet.http.HttpServletRequest; +import javax.xml.namespace.QName; +import javax.xml.xquery.XQConnection; +import javax.xml.xquery.XQDataSource; +import javax.xml.xquery.XQException; +import javax.xml.xquery.XQItemType; +import javax.xml.xquery.XQPreparedExpression; +import javax.xml.xquery.XQResultSequence; +import net.sf.saxon.xqj.SaxonXQDataSource; + +public void bad(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + XQPreparedExpression xqpe = conn.prepareExpression(query); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } +} + +public void good(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + String query = "declare variable $name as xs:string external;" + + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; + XQPreparedExpression xqpe = conn.prepareExpression(query); + xqpe.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp new file mode 100644 index 00000000000..e05e24706cf --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp @@ -0,0 +1,34 @@ + + + +

    The software uses external input to dynamically construct an XQuery expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. +This allows an attacker to control the structure of the query.

    + +
    + + +

    Use parameterized queries. This will help ensure separation between data plane and control plane.

    + +
    + + +

    This example is a comparison of unused parameterized query and using parameterized query. +Parameterized query through bindString.

    + + + +
    + + +
  • cwe description: +XQuery Injection.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql new file mode 100644 index 00000000000..01a29e047f5 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -0,0 +1,21 @@ +/** + * @name XQuery query built from user-controlled sources + * @description Building an XQuery query from user-controlled sources is vulnerable to insertion of + * malicious XQuery code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/XQuery-injection + * @tags security + * external/cwe/cwe-652 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import XQueryInjectionLib +import DataFlow::PathGraph + +from DataFlow::PathNode source, DataFlow::PathNode sink, XQueryInjectionConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "XQuery query might include code from $@.", source.getNode(), + "this user input" diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll new file mode 100644 index 00000000000..06d04760986 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -0,0 +1,35 @@ +import java +import semmle.code.java.dataflow.FlowSources + +class XQueryInjectionConfig extends TaintTracking::Configuration { + XQueryInjectionConfig() { this = "XQueryInjectionConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof XQueryInjectionSink } +} + +/*Find if the executeQuery method is finally called.*/ +predicate executeQuery(MethodAccess ma) { + exists(LocalVariableDeclExpr lvd, MethodAccess ma1, Method m | lvd.getAChildExpr() = ma | + m = ma1.getMethod() and + m.hasName("executeQuery") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") and + ma1.getQualifier() = lvd.getAnAccess() + ) +} + +class XQueryInjectionSink extends DataFlow::ExprNode { + XQueryInjectionSink() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasName("prepareExpression") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQConnection") and + executeQuery(ma) and + asExpr() = ma.getArgument(0) + ) + } +} From 8166e269ec61a596f8dae1874a259262caaebf83 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 21 Jan 2021 20:53:15 +0100 Subject: [PATCH 020/757] Added examples of a sandbox for JEXL expressions --- .../Security/CWE/CWE-094/JexlInjection.qhelp | 21 ++++- ...erJexlExpressionEvaluationWithSandbox.java | 14 +++ ...ressionEvaluationWithUberspectSandbox.java | 90 +++++++++++++++++++ .../UnsafeJexlExpressionEvaluation.java | 14 +-- 4 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp index 94d74d1f6de..69a22fae9a3 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp @@ -14,7 +14,9 @@ and then evaluated, then it may allow the attacker to run arbitrary code.

    -Including user input in a JEXL expression should be avoided. +Including untrusted input in a JEXL expression should be avoided. If it is not possible, +JEXL expressions should be run in a sandbox that allows accessing only +explicitly allowed classes.

    @@ -23,6 +25,23 @@ Including user input in a JEXL expression should be avoided. The following example uses untrusted data to build and run a JEXL expression.

    + +

    +The next example shows how an untrusted JEXL expression can be run +in a sandbox that allows accessing only methods in the `java.lang.Math` class. +The sandbox is implemented using `JexlSandbox` class that is provided by +Apache Commons JEXL 3. +However, it's recommended to avoid using untrusted input in JEXL expressions. +

    + + +

    +The next example shows another way how a sandbox can be implemented. +It uses a custom implememtation of `JexlUberspect` +that checks if callees are instances of allowed classes. +Again, it's recommended to avoid using untrusted input in JEXL expressions. +

    + diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java b/java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java new file mode 100644 index 00000000000..43b3acfb65e --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithSandbox.java @@ -0,0 +1,14 @@ +public void evaluate(Socket socket) throws IOException { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream()))) { + + JexlSandbox onlyMath = new JexlSandbox(false); + onlyMath.white("java.lang.Math"); + JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create(); + + String input = reader.readLine(); + JexlExpression expression = jexl.createExpression(input); + JexlContext context = new MapContext(); + expression.evaluate(context); + } +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java b/java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java new file mode 100644 index 00000000000..5952668b8b3 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/SaferJexlExpressionEvaluationWithUberspectSandbox.java @@ -0,0 +1,90 @@ +public void evaluate(Socket socket) throws IOException { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream()))) { + + JexlUberspect sandbox = new JexlUberspectSandbox(); + JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create(); + + String input = reader.readLine(); + JexlExpression expression = jexl.createExpression(input); + JexlContext context = new MapContext(); + expression.evaluate(context); + } + + private static class JexlUberspectSandbox implements JexlUberspect { + + private static final List ALLOWED_CLASSES = + Arrays.asList("java.lang.Math", "java.util.Random"); + + private final JexlUberspect uberspect = new JexlBuilder().create().getUberspect(); + + private void checkAccess(Object obj) { + if (!ALLOWED_CLASSES.contains(obj.getClass().getCanonicalName())) { + throw new AccessControlException("Not allowed"); + } + } + + @Override + public JexlMethod getMethod(Object obj, String method, Object... args) { + checkAccess(obj); + return uberspect.getMethod(obj, method, args); + } + + @Override + public List getResolvers(JexlOperator op, Object obj) { + checkAccess(obj); + return uberspect.getResolvers(op, obj); + } + + @Override + public void setClassLoader(ClassLoader loader) { + uberspect.setClassLoader(loader); + } + + @Override + public int getVersion() { + return uberspect.getVersion(); + } + + @Override + public JexlMethod getConstructor(Object obj, Object... args) { + checkAccess(obj); + return uberspect.getConstructor(obj, args); + } + + @Override + public JexlPropertyGet getPropertyGet(Object obj, Object identifier) { + checkAccess(obj); + return uberspect.getPropertyGet(obj, identifier); + } + + @Override + public JexlPropertyGet getPropertyGet(List resolvers, Object obj, Object identifier) { + checkAccess(obj); + return uberspect.getPropertyGet(resolvers, obj, identifier); + } + + @Override + public JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg) { + checkAccess(obj); + return uberspect.getPropertySet(obj, identifier, arg); + } + + @Override + public JexlPropertySet getPropertySet(List resolvers, Object obj, Object identifier, Object arg) { + checkAccess(obj); + return uberspect.getPropertySet(resolvers, obj, identifier, arg); + } + + @Override + public Iterator getIterator(Object obj) { + checkAccess(obj); + return uberspect.getIterator(obj); + } + + @Override + public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) { + return uberspect.getArithmetic(arithmetic); + } + } +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java index ed238765737..986fa9833cb 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java +++ b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeJexlExpressionEvaluation.java @@ -1,11 +1,11 @@ public void evaluate(Socket socket) throws IOException { - try (BufferedReader reader = new BufferedReader( + try (BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream()))) { - String input = reader.readLine(); - JexlEngine jexl = new JexlBuilder().create(); - JexlExpression expression = jexl.createExpression(input); - JexlContext context = new MapContext(); - expression.evaluate(context); - } + String input = reader.readLine(); + JexlEngine jexl = new JexlBuilder().create(); + JexlExpression expression = jexl.createExpression(input); + JexlContext context = new MapContext(); + expression.evaluate(context); + } } \ No newline at end of file From ee6d28b5625b13287d263e9be96bd98281aea4a5 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 21 Jan 2021 22:46:18 +0100 Subject: [PATCH 021/757] Use LocalUserInput when looking for JEXL injections --- .../src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index e760e561e97..74a1f53bb44 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -13,8 +13,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof TaintedSpringRequestBody or source instanceof RemoteFlowSource or - source instanceof UserInput or - source instanceof EnvInput + source instanceof LocalUserInput } override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } From 73c8338e52a7dde7fbd69afe100f6922c5a8a718 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 21 Jan 2021 22:49:36 +0100 Subject: [PATCH 022/757] Use tag in JexlInjection.qhelp --- .../experimental/Security/CWE/CWE-094/JexlInjection.qhelp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp index 69a22fae9a3..1b9da908864 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjection.qhelp @@ -28,8 +28,8 @@ The following example uses untrusted data to build and run a JEXL expression.

    The next example shows how an untrusted JEXL expression can be run -in a sandbox that allows accessing only methods in the `java.lang.Math` class. -The sandbox is implemented using `JexlSandbox` class that is provided by +in a sandbox that allows accessing only methods in the java.lang.Math class. +The sandbox is implemented using JexlSandbox class that is provided by Apache Commons JEXL 3. However, it's recommended to avoid using untrusted input in JEXL expressions.

    @@ -37,7 +37,7 @@ However, it's recommended to avoid using untrusted input in JEXL expressions.

    The next example shows another way how a sandbox can be implemented. -It uses a custom implememtation of `JexlUberspect` +It uses a custom implememtation of JexlUberspect that checks if callees are instances of allowed classes. Again, it's recommended to avoid using untrusted input in JEXL expressions.

    From ec4c155043198aba71bd42920f6c957cfd82643b Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 23 Jan 2021 18:26:15 +0800 Subject: [PATCH 023/757] *)update XQueryInjection.qhelp --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp index e05e24706cf..2b6433d6ef3 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp @@ -9,20 +9,19 @@ This allows an attacker to control the structure of the query.

    -

    Use parameterized queries. This will help ensure separation between data plane and control plane.

    +

    Use parameterized queries. This will help ensure the program retains control of the query structure.

    -

    This example is a comparison of unused parameterized query and using parameterized query. -Parameterized query through bindString.

    +

    The following example compares building a query by string concatenation (bad) vs. using bindString to parameterize the query (good).

    -
  • cwe description: +
  • CWE description: XQuery Injection.
  • From 44d99f8cd4a895c180ace4871450233d5609668b Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 23 Jan 2021 18:26:58 +0800 Subject: [PATCH 024/757] *)update XQueryInjection.ql --- .../Security/CWE/CWE-652/XQueryInjection.ql | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index 01a29e047f5..b4fcac80a43 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -15,6 +15,28 @@ import semmle.code.java.dataflow.FlowSources import XQueryInjectionLib import DataFlow::PathGraph +class XQueryInjectionConfig extends DataFlow::Configuration { + XQueryInjectionConfig() { this = "XQueryInjectionConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof XQueryInjectionSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof XQueryInjectionSink } + + override predicate isBarrier(DataFlow::Node node) { + exists(MethodAccess ma, Method m, BindParameterRemoteFlowConf conf, DataFlow::Node node1 | + m = ma.getMethod() + | + node.asExpr() = ma and + m.hasName("bindString") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQDynamicContext") and + ma.getArgument(1) = node1.asExpr() and + conf.hasFlowTo(node1) + ) + } +} + from DataFlow::PathNode source, DataFlow::PathNode sink, XQueryInjectionConfig conf where conf.hasFlowPath(source, sink) select sink.getNode(), source, sink, "XQuery query might include code from $@.", source.getNode(), From 0b326aae202c05d7b04f0999df0c72eb2034c2e8 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 23 Jan 2021 18:27:38 +0800 Subject: [PATCH 025/757] *)update XQueryInjectionLib.qll --- .../CWE/CWE-652/XQueryInjectionLib.qll | 97 +++++++++++++++---- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index 06d04760986..808e5a984a5 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -1,35 +1,90 @@ import java import semmle.code.java.dataflow.FlowSources +import semmle.code.java.dataflow.TaintTracking2 +import DataFlow::PathGraph -class XQueryInjectionConfig extends TaintTracking::Configuration { - XQueryInjectionConfig() { this = "XQueryInjectionConfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } - - override predicate isSink(DataFlow::Node sink) { sink instanceof XQueryInjectionSink } +/** A call to `XQConnection.prepareExpression`. */ +class XQueryParserCall extends MethodAccess { + XQueryParserCall() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQConnection") and + m.hasName("prepareExpression") + ) + } + // return the first parameter of the `bindString` method and use it as a sink + Expr getSink() { result = this.getArgument(0) } } -/*Find if the executeQuery method is finally called.*/ -predicate executeQuery(MethodAccess ma) { - exists(LocalVariableDeclExpr lvd, MethodAccess ma1, Method m | lvd.getAChildExpr() = ma | - m = ma1.getMethod() and - m.hasName("executeQuery") and - m.getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") and - ma1.getQualifier() = lvd.getAnAccess() - ) +/** A call to `XQDynamicContext.bindString`. */ +class XQueryBindStringCall extends MethodAccess { + XQueryBindStringCall() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQDynamicContext") and + m.hasName("bindString") + ) + } + // return the second parameter of the `bindString` method and use it as a sink + Expr getSink() { result = this.getArgument(1) } } -class XQueryInjectionSink extends DataFlow::ExprNode { - XQueryInjectionSink() { - exists(MethodAccess ma, Method m | m = ma.getMethod() | +/** Used to determine whether to call the `prepareExpression` method, and the first parameter value can be remotely controlled. */ +class ParserParameterRemoteFlowConf extends TaintTracking2::Configuration { + ParserParameterRemoteFlowConf() { this = "ParserParameterRemoteFlowConf" } + + override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(XQueryParserCall xqpc | xqpc.getSink() = sink.asExpr()) + } +} + +/** Used to determine whether to call the `bindString` method, and the second parameter value can be controlled remotely. */ +class BindParameterRemoteFlowConf extends TaintTracking2::Configuration { + BindParameterRemoteFlowConf() { this = "BindParameterRemoteFlowConf" } + + override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(XQueryBindStringCall xqbsc | xqbsc.getSink() = sink.asExpr()) + } +} + +/** + * A data flow source for XQuery injection vulnerability. + * 1. `prepareExpression` call as sink. + * 2. Determine whether the `var1` parameter of `prepareExpression` method can be controlled remotely. + */ +class XQueryInjectionSource extends DataFlow::ExprNode { + XQueryInjectionSource() { + exists(MethodAccess ma, Method m, ParserParameterRemoteFlowConf conf, DataFlow::Node node | + m = ma.getMethod() + | m.hasName("prepareExpression") and m.getDeclaringType() .getASourceSupertype*() .hasQualifiedName("javax.xml.xquery", "XQConnection") and - executeQuery(ma) and - asExpr() = ma.getArgument(0) + asExpr() = ma and + node.asExpr() = ma.getArgument(0) and + conf.hasFlowTo(node) + ) + } +} + +/** A data flow sink for XQuery injection vulnerability. */ +class XQueryInjectionSink extends DataFlow::Node { + XQueryInjectionSink() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasName("executeQuery") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") and + asExpr() = ma.getQualifier() ) } } From 28ebbee61d205565f2b1e5f668bc0cd24c37cf39 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 23 Jan 2021 17:42:04 +0100 Subject: [PATCH 026/757] Added TaintPropagatingJexlMethodCall class --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 281 +++++++----------- 1 file changed, 103 insertions(+), 178 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 74a1f53bb44..0cc81c084c8 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -8,6 +8,8 @@ import semmle.code.java.dataflow.TaintTracking * It supports both Jexl2 and Jexl3. */ class JexlInjectionConfig extends TaintTracking::Configuration { + TaintPropagatingJexlMethodCall taintPropagatingJexlMethodCall; + JexlInjectionConfig() { this = "JexlInjectionConfig" } override predicate isSource(DataFlow::Node source) { @@ -18,12 +20,9 @@ class JexlInjectionConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } - override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { - createsJexlExpression(node1, node2) or - createsJexlTemplate(node1, node2) or - createsJexlScript(node1, node2) or - createsJexlCallable(node1, node2) or - returnsDataFromBean(node1, node2) + override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + taintPropagatingJexlMethodCall.taintFlow(fromNode, toNode) or + returnsDataFromBean(fromNode, toNode) } } @@ -31,7 +30,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration { * A data flow source for parameters that have * a Spring framework annotation indicating remote user input from servlets. */ -class TaintedSpringRequestBody extends DataFlow::Node { +private class TaintedSpringRequestBody extends DataFlow::Node { TaintedSpringRequestBody() { exists(SpringServletInputAnnotation a | this.asParameter().getAnAnnotation() = a) } @@ -41,7 +40,7 @@ class TaintedSpringRequestBody extends DataFlow::Node { * A sink for Expresssion Language injection vulnerabilities via Jexl, * i.e. methods that run evaluation of a Jexl expression. */ -class JexlEvaluationSink extends DataFlow::ExprNode { +private class JexlEvaluationSink extends DataFlow::ExprNode { JexlEvaluationSink() { isJexlExpressionEvaluationCall(asExpr()) or isJexlTemplateEvaluationCall(asExpr()) or @@ -52,89 +51,95 @@ class JexlEvaluationSink extends DataFlow::ExprNode { } /** - * Holds if `node1` to `node2` is a dataflow step that creates a Jexl expression. + * Defines method calls that propagate tainted data via one of the methods + * from Jexl library. */ -predicate createsJexlExpression(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, Method m | ma.getMethod() = m | - ( - m instanceof JxltEngineCreateExpressionMethod or - m instanceof UnifiedJexlParseMethod or - m instanceof JexlEngineCreateExpressionMethod - ) and - ma.getAnArgument().getType() instanceof TypeString and - ma.getAnArgument() = node1.asExpr() and - node2.asExpr() = ma - ) +private class TaintPropagatingJexlMethodCall extends MethodAccess { + string methodName; + RefType instanceType; + Expr taintFromExpr; + + TaintPropagatingJexlMethodCall() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() = instanceType and + m.hasName(methodName) + | + isMethodForCreatingJexlScript(instanceType, methodName) and + taintFromExpr = this.getArgument(0) and + taintFromExpr.getType() instanceof TypeString + or + isMethodForCreatingJexlCallable(instanceType, methodName) and + taintFromExpr = this.getQualifier() + or + isMethodForCreatingJexlExpression(instanceType, methodName) and + taintFromExpr = this.getAnArgument() and + taintFromExpr.getType() instanceof TypeString + or + isMethodForCreatingJexlTemplate(instanceType, methodName) and + (taintFromExpr.getType() instanceof TypeString or taintFromExpr.getType() instanceof Reader) and + taintFromExpr = this.getArgument([0, 1]) + ) + } + + /** + * Holds if `fromNode` to `toNode` is a dataflow step that propagates + * tainted data. + */ + predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) { + fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this + } } /** - * Holds if `node1` to `node2` is a dataflow step that creates a Jexl template. + * Checks if `instanceType.methodName()` method creates a Jexl script. */ -predicate createsJexlTemplate(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, Method m | ma.getMethod() = m | - (m instanceof JxltEngineCreateTemplateMethod or m instanceof UnifiedJexlCreateTemplateMethod) and - ( - node1.asExpr().getType() instanceof TypeString or - node1.asExpr().getType() instanceof Reader - ) and - ma.getArgument([0, 1]) = node1.asExpr() and - node2.asExpr() = ma - ) +private predicate isMethodForCreatingJexlScript(RefType instanceType, string methodName) { + instanceType instanceof JexlEngine and methodName = "createScript" } /** - * Holds if: - * - `expr` is the `index`th argument to `ma` - * - `expr` is a string or an instance of `Reader` + * Checks if `instanceType.methodName()` method creates a `Callable` for a Jexl expression or script. */ -predicate toberemoved(MethodAccess ma, int index, Expr expr) { - ( - expr.getType() instanceof TypeString or - expr.getType() instanceof Reader - ) and - ma.getArgument(index) = expr +private predicate isMethodForCreatingJexlCallable(RefType instanceType, string methodName) { + (instanceType instanceof JexlExpression or instanceType instanceof JexlScript) and + methodName = "callable" } /** - * Holds if `node1` to `node2` is a dataflow step that creates a Jexl script. + * Checks if `instanceType.methodName()` method creates a Jexl template. */ -predicate createsJexlScript(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, Method m | ma.getMethod() = m | - m instanceof JexlEngineCreateScriptMethod and - ma.getArgument(0).getType() instanceof TypeString and - ma.getArgument(0) = node1.asExpr() and - node2.asExpr() = ma - ) +private predicate isMethodForCreatingJexlTemplate(RefType instanceType, string methodName) { + (instanceType instanceof JxltEngine or instanceType instanceof UnifiedJexl) and + methodName = "createTemplate" } /** - * Holds if `node1` to `node2` is a dataflow step - * that creates a callable from a Jexl expression or script. + * Checks if `instanceType.methodName()` method creates a Jexl expression. */ -predicate createsJexlCallable(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, Method m | ma.getMethod() = m | - (m instanceof JexlExpressionCallableMethod or m instanceof JexlScriptCallableMethod) and - ma.getQualifier() = node1.asExpr() and - node2.asExpr() = ma - ) +private predicate isMethodForCreatingJexlExpression(RefType instanceType, string methodName) { + (instanceType instanceof JexlEngine or instanceType instanceof JxltEngine) and + methodName = "createExpression" + or + instanceType instanceof UnifiedJexl and methodName = "parse" } /** - * Holds if `node1` to `node2` is a dataflow step that returns data from + * Holds if `fromNode` to `toNode` is a dataflow step that returns data from * a tainted bean by calling one of its getters. */ -predicate returnsDataFromBean(DataFlow::Node node1, DataFlow::Node node2) { +private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) { exists(MethodAccess ma, Method m | ma.getMethod() = m | m instanceof GetterMethod and - ma.getQualifier() = node1.asExpr() and - ma = node2.asExpr() + ma.getQualifier() = fromNode.asExpr() and + ma = toNode.asExpr() ) } /** * Holds if `expr` calls one of the methods that execute a Jexl script against qualifier `expr`. */ -predicate isJexlScriptExecuteCall(Expr expr) { +private predicate isJexlScriptExecuteCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | m instanceof JexlScriptExecuteMethod and ma.getQualifier() = expr @@ -145,7 +150,7 @@ predicate isJexlScriptExecuteCall(Expr expr) { * Holds if `expr` is the qualifier when calling the `Callable.call()` method * such as `expr.call()`. */ -predicate isCallableCall(Expr expr) { +private predicate isCallableCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | m instanceof CallableCallMethod and ma.getQualifier() = expr @@ -156,7 +161,7 @@ predicate isCallableCall(Expr expr) { * Holds if `expr` is an argument in a call to one of the methods * that get or set a property via a Jexl expression. */ -predicate isJexlGetSetPropertyCall(Expr expr) { +private predicate isJexlGetSetPropertyCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | (m instanceof JexlEngineGetPropertyMethod or m instanceof JexlEngineSetPropertyMethod) and ma.getAnArgument().getType() instanceof TypeString and @@ -167,7 +172,7 @@ predicate isJexlGetSetPropertyCall(Expr expr) { /** * Holds if `expr` is a call to one of the methods that trigger evaluation of a Jexl expression. */ -predicate isJexlExpressionEvaluationCall(Expr expr) { +private predicate isJexlExpressionEvaluationCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | ( m instanceof JexlExpressionEvaluateMethod or @@ -183,7 +188,7 @@ predicate isJexlExpressionEvaluationCall(Expr expr) { /** * Holds if `expr` is a call to one of the methods that evaluates a Jexl template. */ -predicate isJexlTemplateEvaluationCall(Expr expr) { +private predicate isJexlTemplateEvaluationCall(Expr expr) { exists(MethodAccess ma, Method m | m = ma.getMethod() | ( m instanceof JxltEngineTemplateEvaluateMethod or @@ -194,9 +199,9 @@ predicate isJexlTemplateEvaluationCall(Expr expr) { } /** - * A method in the JexlExpression class that evaluates a Jexl expression. + * A method in the `JexlExpression` class that evaluates a Jexl expression. */ -class JexlExpressionEvaluateMethod extends Method { +private class JexlExpressionEvaluateMethod extends Method { JexlExpressionEvaluateMethod() { getDeclaringType() instanceof JexlExpression and hasName("evaluate") @@ -204,19 +209,9 @@ class JexlExpressionEvaluateMethod extends Method { } /** - * A method in the JexlEngine class that creates a Jexl expression. + * A method in the `JexlEngine` class that gets a property with a Jexl expression. */ -class JexlEngineCreateExpressionMethod extends Method { - JexlEngineCreateExpressionMethod() { - getDeclaringType() instanceof JexlEngine and - hasName("createExpression") - } -} - -/** - * A method in the JexlEngine class that gets a property with a Jexl expression. - */ -class JexlEngineGetPropertyMethod extends Method { +private class JexlEngineGetPropertyMethod extends Method { JexlEngineGetPropertyMethod() { getDeclaringType() instanceof JexlEngine and hasName("getProperty") @@ -224,9 +219,9 @@ class JexlEngineGetPropertyMethod extends Method { } /** - * A method in the JexlEngine class that sets a property with a Jexl expression. + * A method in the `JexlEngine` class that sets a property with a Jexl expression. */ -class JexlEngineSetPropertyMethod extends Method { +private class JexlEngineSetPropertyMethod extends Method { JexlEngineSetPropertyMethod() { getDeclaringType() instanceof JexlEngine and hasName("setProperty") @@ -234,19 +229,9 @@ class JexlEngineSetPropertyMethod extends Method { } /** - * A method in the JexlEngine class that creates a Jexl script. + * A method in the `JexlScript` class that executes a Jexl script. */ -class JexlEngineCreateScriptMethod extends Method { - JexlEngineCreateScriptMethod() { - getDeclaringType() instanceof JexlEngine and - hasName("createScript") - } -} - -/** - * A method in the JexlScript class that executes a Jexl script. - */ -class JexlScriptExecuteMethod extends Method { +private class JexlScriptExecuteMethod extends Method { JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") @@ -254,29 +239,9 @@ class JexlScriptExecuteMethod extends Method { } /** - * A method in the JexlScript class that creates a Callable for a Jexl expression. + * A method in the `Callable` class that executes the `Callable`. */ -class JexlExpressionCallableMethod extends Method { - JexlExpressionCallableMethod() { - getDeclaringType() instanceof JexlExpression and - hasName("callable") - } -} - -/** - * A method in the JexlScript class that creates a Callable for a Jexl script. - */ -class JexlScriptCallableMethod extends Method { - JexlScriptCallableMethod() { - getDeclaringType() instanceof JexlScript and - hasName("callable") - } -} - -/** - * A method in the Callable class that executes the Callable. - */ -class CallableCallMethod extends Method { +private class CallableCallMethod extends Method { CallableCallMethod() { getDeclaringType() instanceof CallableInterface and hasName("call") @@ -284,29 +249,9 @@ class CallableCallMethod extends Method { } /** - * A method in the JxltEngine class that creates an expression. + * A method in the `JxltEngine.Expression` class that evaluates an expression. */ -class JxltEngineCreateExpressionMethod extends Method { - JxltEngineCreateExpressionMethod() { - getDeclaringType() instanceof JxltEngine and - hasName("createExpression") - } -} - -/** - * A method in the JxltEngine class that creates a template. - */ -class JxltEngineCreateTemplateMethod extends Method { - JxltEngineCreateTemplateMethod() { - getDeclaringType() instanceof JxltEngine and - hasName("createTemplate") - } -} - -/** - * A method in the JxltEngine.Expression class that evaluates an expression. - */ -class JxltEngineExpressionEvaluateMethod extends Method { +private class JxltEngineExpressionEvaluateMethod extends Method { JxltEngineExpressionEvaluateMethod() { getDeclaringType() instanceof JxltEngineExpression and hasName("evaluate") @@ -314,9 +259,9 @@ class JxltEngineExpressionEvaluateMethod extends Method { } /** - * A method in the JxltEngine.Expression class that evaluates the immediate sub-expressions. + * A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions. */ -class JxltEngineExpressionPrepareMethod extends Method { +private class JxltEngineExpressionPrepareMethod extends Method { JxltEngineExpressionPrepareMethod() { getDeclaringType() instanceof JxltEngineExpression and hasName("prepare") @@ -324,9 +269,9 @@ class JxltEngineExpressionPrepareMethod extends Method { } /** - * A method in the JxltEngine.Template class that evaluates a template. + * A method in the `JxltEngine.Template` class that evaluates a template. */ -class JxltEngineTemplateEvaluateMethod extends Method { +private class JxltEngineTemplateEvaluateMethod extends Method { JxltEngineTemplateEvaluateMethod() { getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") @@ -334,29 +279,9 @@ class JxltEngineTemplateEvaluateMethod extends Method { } /** - * A method in the UnifiedJEXL class that creates an expression. + * A method in the `UnifiedJEXL.Expression` class that evaluates a template. */ -class UnifiedJexlParseMethod extends Method { - UnifiedJexlParseMethod() { - getDeclaringType() instanceof UnifiedJexl and - hasName("parse") - } -} - -/** - * A method in the UnifiedJEXL class that creates a template. - */ -class UnifiedJexlCreateTemplateMethod extends Method { - UnifiedJexlCreateTemplateMethod() { - getDeclaringType() instanceof UnifiedJexl and - hasName("createTemplate") - } -} - -/** - * A method in the UnifiedJEXL.Expression class that evaluates a template. - */ -class UnifiedJexlExpressionEvaluateMethod extends Method { +private class UnifiedJexlExpressionEvaluateMethod extends Method { UnifiedJexlExpressionEvaluateMethod() { getDeclaringType() instanceof UnifiedJexlExpression and hasName("evaluate") @@ -364,9 +289,9 @@ class UnifiedJexlExpressionEvaluateMethod extends Method { } /** - * A method in the UnifiedJEXL.Expression class that evaluates the immediate sub-expressions. + * A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions. */ -class UnifiedJexlExpressionPrepareMethod extends Method { +private class UnifiedJexlExpressionPrepareMethod extends Method { UnifiedJexlExpressionPrepareMethod() { getDeclaringType() instanceof UnifiedJexlExpression and hasName("prepare") @@ -374,73 +299,73 @@ class UnifiedJexlExpressionPrepareMethod extends Method { } /** - * A method in the UnifiedJEXL.Template class that evaluates a template. + * A method in the `UnifiedJEXL.Template` class that evaluates a template. */ -class UnifiedJexlTemplateEvaluateMethod extends Method { +private class UnifiedJexlTemplateEvaluateMethod extends Method { UnifiedJexlTemplateEvaluateMethod() { getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate") } } -class JexlExpression extends RefType { +private class JexlExpression extends RefType { JexlExpression() { hasQualifiedName("org.apache.commons.jexl3", "JexlExpression") or hasQualifiedName("org.apache.commons.jexl2", "Expression") } } -class JexlScript extends RefType { +private class JexlScript extends RefType { JexlScript() { hasQualifiedName("org.apache.commons.jexl3", "JexlScript") or hasQualifiedName("org.apache.commons.jexl2", "Script") } } -class JexlEngine extends RefType { +private class JexlEngine extends RefType { JexlEngine() { hasQualifiedName("org.apache.commons.jexl3", "JexlEngine") or hasQualifiedName("org.apache.commons.jexl2", "JexlEngine") } } -class JxltEngine extends RefType { +private class JxltEngine extends RefType { JxltEngine() { hasQualifiedName("org.apache.commons.jexl3", "JxltEngine") } } -class UnifiedJexl extends RefType { +private class UnifiedJexl extends RefType { UnifiedJexl() { hasQualifiedName("org.apache.commons.jexl2", "UnifiedJEXL") } } -class JxltEngineExpression extends NestedType { +private class JxltEngineExpression extends NestedType { JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") } } -class JxltEngineTemplate extends NestedType { +private class JxltEngineTemplate extends NestedType { JxltEngineTemplate() { getEnclosingType() instanceof JxltEngine and hasName("Template") } } -class UnifiedJexlExpression extends NestedType { +private class UnifiedJexlExpression extends NestedType { UnifiedJexlExpression() { getEnclosingType() instanceof UnifiedJexl and hasName("Expression") } } -class UnifiedJexlTemplate extends NestedType { +private class UnifiedJexlTemplate extends NestedType { UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") } } -class CallableInterface extends RefType { +private class CallableInterface extends RefType { CallableInterface() { getSourceDeclaration() .getASourceSupertype*() @@ -448,6 +373,6 @@ class CallableInterface extends RefType { } } -class Reader extends RefType { +private class Reader extends RefType { Reader() { hasQualifiedName("java.io", "Reader") } } From a47147bc5e1f93543faa1cdde2e79e68d2f12fdb Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 23 Jan 2021 19:22:43 +0100 Subject: [PATCH 027/757] Simplify sinks in JexlInjectionLib.qll --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 223 +++++++----------- 1 file changed, 80 insertions(+), 143 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 0cc81c084c8..51eb6120b9f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -38,15 +38,19 @@ private class TaintedSpringRequestBody extends DataFlow::Node { /** * A sink for Expresssion Language injection vulnerabilities via Jexl, - * i.e. methods that run evaluation of a Jexl expression. + * i.e. method calls that run evaluation of a Jexl expression. */ private class JexlEvaluationSink extends DataFlow::ExprNode { JexlEvaluationSink() { - isJexlExpressionEvaluationCall(asExpr()) or - isJexlTemplateEvaluationCall(asExpr()) or - isJexlScriptExecuteCall(asExpr()) or - isJexlGetSetPropertyCall(asExpr()) or - isCallableCall(asExpr()) + exists(MethodAccess ma, Method m, Expr tainted | ma.getMethod() = m and tainted = asExpr() | + m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = tainted + or + m instanceof CallableCallMethod and ma.getQualifier() = tainted + or + m instanceof JexlEngineGetSetPropertyMethod and + ma.getAnArgument().getType() instanceof TypeString and + ma.getAnArgument() = tainted + ) } } @@ -137,107 +141,100 @@ private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node to } /** - * Holds if `expr` calls one of the methods that execute a Jexl script against qualifier `expr`. + * Method in the `JexlEngine` class that get or set a property with a Jexl expression. */ -private predicate isJexlScriptExecuteCall(Expr expr) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m instanceof JexlScriptExecuteMethod and - ma.getQualifier() = expr - ) +private class JexlEngineGetSetPropertyMethod extends Method { + JexlEngineGetSetPropertyMethod() { + getDeclaringType() instanceof JexlEngine and + hasName(["getProperty", "setProperty"]) + } } /** - * Holds if `expr` is the qualifier when calling the `Callable.call()` method - * such as `expr.call()`. + * Defines methods that triggers direct evaluation of Jexl expressions. */ -private predicate isCallableCall(Expr expr) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m instanceof CallableCallMethod and - ma.getQualifier() = expr - ) -} - -/** - * Holds if `expr` is an argument in a call to one of the methods - * that get or set a property via a Jexl expression. - */ -private predicate isJexlGetSetPropertyCall(Expr expr) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - (m instanceof JexlEngineGetPropertyMethod or m instanceof JexlEngineSetPropertyMethod) and - ma.getAnArgument().getType() instanceof TypeString and - ma.getAnArgument() = expr - ) -} - -/** - * Holds if `expr` is a call to one of the methods that trigger evaluation of a Jexl expression. - */ -private predicate isJexlExpressionEvaluationCall(Expr expr) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - ( - m instanceof JexlExpressionEvaluateMethod or - m instanceof JxltEngineExpressionEvaluateMethod or - m instanceof JxltEngineExpressionPrepareMethod or - m instanceof UnifiedJexlExpressionEvaluateMethod or - m instanceof UnifiedJexlExpressionPrepareMethod - ) and - ma.getQualifier() = expr - ) -} - -/** - * Holds if `expr` is a call to one of the methods that evaluates a Jexl template. - */ -private predicate isJexlTemplateEvaluationCall(Expr expr) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - ( - m instanceof JxltEngineTemplateEvaluateMethod or - m instanceof UnifiedJexlTemplateEvaluateMethod - ) and - ma.getQualifier() = expr - ) -} +abstract private class DirectJexlEvaluationMethod extends Method { } /** * A method in the `JexlExpression` class that evaluates a Jexl expression. */ -private class JexlExpressionEvaluateMethod extends Method { +private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { JexlExpressionEvaluateMethod() { getDeclaringType() instanceof JexlExpression and hasName("evaluate") } } -/** - * A method in the `JexlEngine` class that gets a property with a Jexl expression. - */ -private class JexlEngineGetPropertyMethod extends Method { - JexlEngineGetPropertyMethod() { - getDeclaringType() instanceof JexlEngine and - hasName("getProperty") - } -} - -/** - * A method in the `JexlEngine` class that sets a property with a Jexl expression. - */ -private class JexlEngineSetPropertyMethod extends Method { - JexlEngineSetPropertyMethod() { - getDeclaringType() instanceof JexlEngine and - hasName("setProperty") - } -} - /** * A method in the `JexlScript` class that executes a Jexl script. */ -private class JexlScriptExecuteMethod extends Method { +private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod { JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") } } +/** + * A method in the `JxltEngine.Expression` class that evaluates an expression. + */ +private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMethod { + JxltEngineExpressionEvaluateMethod() { + getDeclaringType() instanceof JxltEngineExpression and + hasName("evaluate") + } +} + +/** + * A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions. + */ +private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMethod { + JxltEngineExpressionPrepareMethod() { + getDeclaringType() instanceof JxltEngineExpression and + hasName("prepare") + } +} + +/** + * A method in the `JxltEngine.Template` class that evaluates a template. + */ +private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMethod { + JxltEngineTemplateEvaluateMethod() { + getDeclaringType() instanceof JxltEngineTemplate and + hasName("evaluate") + } +} + +/** + * A method in the `UnifiedJEXL.Expression` class that evaluates a template. + */ +private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { + UnifiedJexlExpressionEvaluateMethod() { + getDeclaringType() instanceof UnifiedJexlExpression and + hasName("evaluate") + } +} + +/** + * A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions. + */ +private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMethod { + UnifiedJexlExpressionPrepareMethod() { + getDeclaringType() instanceof UnifiedJexlExpression and + hasName("prepare") + } +} + +/** + * A method in the `UnifiedJEXL.Template` class that evaluates a template. + */ +private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMethod { + UnifiedJexlTemplateEvaluateMethod() { + getDeclaringType() instanceof UnifiedJexlTemplate and + hasName("evaluate") + } +} + /** * A method in the `Callable` class that executes the `Callable`. */ @@ -248,66 +245,6 @@ private class CallableCallMethod extends Method { } } -/** - * A method in the `JxltEngine.Expression` class that evaluates an expression. - */ -private class JxltEngineExpressionEvaluateMethod extends Method { - JxltEngineExpressionEvaluateMethod() { - getDeclaringType() instanceof JxltEngineExpression and - hasName("evaluate") - } -} - -/** - * A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions. - */ -private class JxltEngineExpressionPrepareMethod extends Method { - JxltEngineExpressionPrepareMethod() { - getDeclaringType() instanceof JxltEngineExpression and - hasName("prepare") - } -} - -/** - * A method in the `JxltEngine.Template` class that evaluates a template. - */ -private class JxltEngineTemplateEvaluateMethod extends Method { - JxltEngineTemplateEvaluateMethod() { - getDeclaringType() instanceof JxltEngineTemplate and - hasName("evaluate") - } -} - -/** - * A method in the `UnifiedJEXL.Expression` class that evaluates a template. - */ -private class UnifiedJexlExpressionEvaluateMethod extends Method { - UnifiedJexlExpressionEvaluateMethod() { - getDeclaringType() instanceof UnifiedJexlExpression and - hasName("evaluate") - } -} - -/** - * A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions. - */ -private class UnifiedJexlExpressionPrepareMethod extends Method { - UnifiedJexlExpressionPrepareMethod() { - getDeclaringType() instanceof UnifiedJexlExpression and - hasName("prepare") - } -} - -/** - * A method in the `UnifiedJEXL.Template` class that evaluates a template. - */ -private class UnifiedJexlTemplateEvaluateMethod extends Method { - UnifiedJexlTemplateEvaluateMethod() { - getDeclaringType() instanceof UnifiedJexlTemplate and - hasName("evaluate") - } -} - private class JexlExpression extends RefType { JexlExpression() { hasQualifiedName("org.apache.commons.jexl3", "JexlExpression") or From 03348b18b5438df437b2d5b8e9a56c10a7db52ae Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 23 Jan 2021 19:41:14 +0100 Subject: [PATCH 028/757] Simplified TaintPropagatingJexlMethodCall --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 130 +++++++----------- 1 file changed, 48 insertions(+), 82 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 51eb6120b9f..f6f387ed738 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -59,29 +59,26 @@ private class JexlEvaluationSink extends DataFlow::ExprNode { * from Jexl library. */ private class TaintPropagatingJexlMethodCall extends MethodAccess { - string methodName; - RefType instanceType; Expr taintFromExpr; TaintPropagatingJexlMethodCall() { - exists(Method m | + exists(Method m, RefType taintType | this.getMethod() = m and - m.getDeclaringType() = instanceType and - m.hasName(methodName) + taintType = taintFromExpr.getType() | - isMethodForCreatingJexlScript(instanceType, methodName) and + m instanceof CreateJexlScriptMethod and taintFromExpr = this.getArgument(0) and - taintFromExpr.getType() instanceof TypeString + taintType instanceof TypeString or - isMethodForCreatingJexlCallable(instanceType, methodName) and + m instanceof CreateJexlCallableMethod and taintFromExpr = this.getQualifier() or - isMethodForCreatingJexlExpression(instanceType, methodName) and + m instanceof CreateJexlExpressionMethod and taintFromExpr = this.getAnArgument() and - taintFromExpr.getType() instanceof TypeString + taintType instanceof TypeString or - isMethodForCreatingJexlTemplate(instanceType, methodName) and - (taintFromExpr.getType() instanceof TypeString or taintFromExpr.getType() instanceof Reader) and + m instanceof CreateJexlTemplateMethod and + (taintType instanceof TypeString or taintType instanceof Reader) and taintFromExpr = this.getArgument([0, 1]) ) } @@ -95,39 +92,6 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess { } } -/** - * Checks if `instanceType.methodName()` method creates a Jexl script. - */ -private predicate isMethodForCreatingJexlScript(RefType instanceType, string methodName) { - instanceType instanceof JexlEngine and methodName = "createScript" -} - -/** - * Checks if `instanceType.methodName()` method creates a `Callable` for a Jexl expression or script. - */ -private predicate isMethodForCreatingJexlCallable(RefType instanceType, string methodName) { - (instanceType instanceof JexlExpression or instanceType instanceof JexlScript) and - methodName = "callable" -} - -/** - * Checks if `instanceType.methodName()` method creates a Jexl template. - */ -private predicate isMethodForCreatingJexlTemplate(RefType instanceType, string methodName) { - (instanceType instanceof JxltEngine or instanceType instanceof UnifiedJexl) and - methodName = "createTemplate" -} - -/** - * Checks if `instanceType.methodName()` method creates a Jexl expression. - */ -private predicate isMethodForCreatingJexlExpression(RefType instanceType, string methodName) { - (instanceType instanceof JexlEngine or instanceType instanceof JxltEngine) and - methodName = "createExpression" - or - instanceType instanceof UnifiedJexl and methodName = "parse" -} - /** * Holds if `fromNode` to `toNode` is a dataflow step that returns data from * a tainted bean by calling one of its getters. @@ -160,8 +124,7 @@ abstract private class DirectJexlEvaluationMethod extends Method { } */ private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { JexlExpressionEvaluateMethod() { - getDeclaringType() instanceof JexlExpression and - hasName("evaluate") + getDeclaringType() instanceof JexlExpression and hasName("evaluate") } } @@ -169,10 +132,7 @@ private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { * A method in the `JexlScript` class that executes a Jexl script. */ private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod { - JexlScriptExecuteMethod() { - getDeclaringType() instanceof JexlScript and - hasName("execute") - } + JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") } } /** @@ -180,8 +140,7 @@ private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod { */ private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMethod { JxltEngineExpressionEvaluateMethod() { - getDeclaringType() instanceof JxltEngineExpression and - hasName("evaluate") + getDeclaringType() instanceof JxltEngineExpression and hasName("evaluate") } } @@ -190,8 +149,7 @@ private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMet */ private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMethod { JxltEngineExpressionPrepareMethod() { - getDeclaringType() instanceof JxltEngineExpression and - hasName("prepare") + getDeclaringType() instanceof JxltEngineExpression and hasName("prepare") } } @@ -200,8 +158,7 @@ private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMeth */ private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMethod { JxltEngineTemplateEvaluateMethod() { - getDeclaringType() instanceof JxltEngineTemplate and - hasName("evaluate") + getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") } } @@ -210,8 +167,7 @@ private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMetho */ private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { UnifiedJexlExpressionEvaluateMethod() { - getDeclaringType() instanceof UnifiedJexlExpression and - hasName("evaluate") + getDeclaringType() instanceof UnifiedJexlExpression and hasName("evaluate") } } @@ -220,8 +176,7 @@ private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMe */ private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMethod { UnifiedJexlExpressionPrepareMethod() { - getDeclaringType() instanceof UnifiedJexlExpression and - hasName("prepare") + getDeclaringType() instanceof UnifiedJexlExpression and hasName("prepare") } } @@ -230,8 +185,7 @@ private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMet */ private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMethod { UnifiedJexlTemplateEvaluateMethod() { - getDeclaringType() instanceof UnifiedJexlTemplate and - hasName("evaluate") + getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate") } } @@ -239,9 +193,33 @@ private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMeth * A method in the `Callable` class that executes the `Callable`. */ private class CallableCallMethod extends Method { - CallableCallMethod() { - getDeclaringType() instanceof CallableInterface and - hasName("call") + CallableCallMethod() { getDeclaringType() instanceof CallableInterface and hasName("call") } +} + +private class CreateJexlScriptMethod extends Method { + CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") } +} + +private class CreateJexlCallableMethod extends Method { + CreateJexlCallableMethod() { + (getDeclaringType() instanceof JexlExpression or getDeclaringType() instanceof JexlScript) and + hasName("callable") + } +} + +private class CreateJexlTemplateMethod extends Method { + CreateJexlTemplateMethod() { + (getDeclaringType() instanceof JxltEngine or getDeclaringType() instanceof UnifiedJexl) and + hasName("createTemplate") + } +} + +private class CreateJexlExpressionMethod extends Method { + CreateJexlExpressionMethod() { + (getDeclaringType() instanceof JexlEngine or getDeclaringType() instanceof JxltEngine) and + hasName("createExpression") + or + getDeclaringType() instanceof UnifiedJexl and hasName("parse") } } @@ -275,31 +253,19 @@ private class UnifiedJexl extends RefType { } private class JxltEngineExpression extends NestedType { - JxltEngineExpression() { - getEnclosingType() instanceof JxltEngine and - hasName("Expression") - } + JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") } } private class JxltEngineTemplate extends NestedType { - JxltEngineTemplate() { - getEnclosingType() instanceof JxltEngine and - hasName("Template") - } + JxltEngineTemplate() { getEnclosingType() instanceof JxltEngine and hasName("Template") } } private class UnifiedJexlExpression extends NestedType { - UnifiedJexlExpression() { - getEnclosingType() instanceof UnifiedJexl and - hasName("Expression") - } + UnifiedJexlExpression() { getEnclosingType() instanceof UnifiedJexl and hasName("Expression") } } private class UnifiedJexlTemplate extends NestedType { - UnifiedJexlTemplate() { - getEnclosingType() instanceof UnifiedJexl and - hasName("Template") - } + UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") } } private class CallableInterface extends RefType { From 71e5cb45d3e19d37614541c70b1204f94826399f Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 23 Jan 2021 19:50:16 +0100 Subject: [PATCH 029/757] Simplified method and class definitions for JEXL --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index f6f387ed738..25281682326 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -196,10 +196,16 @@ private class CallableCallMethod extends Method { CallableCallMethod() { getDeclaringType() instanceof CallableInterface and hasName("call") } } +/** + * Defines methods that create a Jexl script. + */ private class CreateJexlScriptMethod extends Method { CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") } } +/** + * Defines methods that creates a `Callable` for a Jexl expression or script. + */ private class CreateJexlCallableMethod extends Method { CreateJexlCallableMethod() { (getDeclaringType() instanceof JexlExpression or getDeclaringType() instanceof JexlScript) and @@ -207,6 +213,9 @@ private class CreateJexlCallableMethod extends Method { } } +/** + * Defines methods that create a Jexl template. + */ private class CreateJexlTemplateMethod extends Method { CreateJexlTemplateMethod() { (getDeclaringType() instanceof JxltEngine or getDeclaringType() instanceof UnifiedJexl) and @@ -214,6 +223,9 @@ private class CreateJexlTemplateMethod extends Method { } } +/** + * Defines methods that create a Jexl expression. + */ private class CreateJexlExpressionMethod extends Method { CreateJexlExpressionMethod() { (getDeclaringType() instanceof JexlEngine or getDeclaringType() instanceof JxltEngine) and @@ -223,33 +235,28 @@ private class CreateJexlExpressionMethod extends Method { } } -private class JexlExpression extends RefType { - JexlExpression() { - hasQualifiedName("org.apache.commons.jexl3", "JexlExpression") or - hasQualifiedName("org.apache.commons.jexl2", "Expression") - } +private class JexlRefType extends RefType { + JexlRefType() { getPackage().hasName(["org.apache.commons.jexl2", "org.apache.commons.jexl3"]) } } -private class JexlScript extends RefType { - JexlScript() { - hasQualifiedName("org.apache.commons.jexl3", "JexlScript") or - hasQualifiedName("org.apache.commons.jexl2", "Script") - } +private class JexlExpression extends JexlRefType { + JexlExpression() { hasName(["Expression", "JexlExpression"]) } } -private class JexlEngine extends RefType { - JexlEngine() { - hasQualifiedName("org.apache.commons.jexl3", "JexlEngine") or - hasQualifiedName("org.apache.commons.jexl2", "JexlEngine") - } +private class JexlScript extends JexlRefType { + JexlScript() { hasName(["Script", "JexlScript"]) } } -private class JxltEngine extends RefType { - JxltEngine() { hasQualifiedName("org.apache.commons.jexl3", "JxltEngine") } +private class JexlEngine extends JexlRefType { + JexlEngine() { hasName("JexlEngine") } } -private class UnifiedJexl extends RefType { - UnifiedJexl() { hasQualifiedName("org.apache.commons.jexl2", "UnifiedJEXL") } +private class JxltEngine extends JexlRefType { + JxltEngine() { hasName("JxltEngine") } +} + +private class UnifiedJexl extends JexlRefType { + UnifiedJexl() { hasName("UnifiedJEXL") } } private class JxltEngineExpression extends NestedType { From a64fc2b24ee7e084a0a402d11bb6795dabac6049 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Sun, 24 Jan 2021 18:58:39 +0530 Subject: [PATCH 030/757] Java: Queries to detect remote source flow to CORS header --- .../Security/CWE/CWE-346/UnvalidatedCors.java | 40 ++++++++++ .../CWE/CWE-346/UnvalidatedCors.qhelp | 75 +++++++++++++++++++ .../Security/CWE/CWE-346/UnvalidatedCors.ql | 55 ++++++++++++++ .../security/CWE-346/UnvalidatedCors.expected | 7 ++ .../security/CWE-346/UnvalidatedCors.java | 52 +++++++++++++ .../security/CWE-346/UnvalidatedCors.qlref | 1 + .../test/query-tests/security/CWE-346/options | 1 + .../org/apache/commons/lang3/StringUtils.java | 7 ++ .../servlet-api-2.4/javax/servlet/Filter.java | 11 +++ .../javax/servlet/FilterChain.java | 7 ++ .../javax/servlet/FilterConfig.java | 10 +++ .../servlet/http/HttpServletResponse.java | 6 ++ 12 files changed, 272 insertions(+) create mode 100644 java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java create mode 100644 java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql create mode 100644 java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected create mode 100644 java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java create mode 100644 java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.qlref create mode 100644 java/ql/test/query-tests/security/CWE-346/options create mode 100644 java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/Filter.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterChain.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterConfig.java diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java new file mode 100644 index 00000000000..8f388b13553 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java @@ -0,0 +1,40 @@ +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; + +public class CorsFilter implements Filter { + public void init(FilterConfig filterConfig) throws ServletException { + // init + } + + public void doFilter(ServletRequest req, ServletResponse res, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + String url = request.getHeader("Origin"); + + if (!StringUtils.isEmpty(url)) { + String val = response.getHeader("Access-Control-Allow-Origin"); + + if (StringUtils.isEmpty(val)) { + response.addHeader("Access-Control-Allow-Origin", url); + response.addHeader("Access-Control-Allow-Credentials", "true"); + } + } + + chain.doFilter(req, res); + } + + public void destroy() { + // destroy + } +} diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp new file mode 100644 index 00000000000..3293352c0d5 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp @@ -0,0 +1,75 @@ + + + + +

    + + A server can send the + "Access-Control-Allow-Credentials" CORS header to control + when a browser may send user credentials in Cross-Origin HTTP + requests. + +

    +

    + + When the Access-Control-Allow-Credentials header + is "true", the Access-Control-Allow-Origin + header must have a value different from "*" in order to + make browsers accept the header. Therefore, to allow multiple origins + for Cross-Origin requests with credentials, the server must + dynamically compute the value of the + "Access-Control-Allow-Origin" header. Computing this + header value from information in the request to the server can + therefore potentially allow an attacker to control the origins that + the browser sends credentials to. + +

    + + + +
    + + +

    + + When the Access-Control-Allow-Credentials header + value is "true", a dynamic computation of the + Access-Control-Allow-Origin header must involve + sanitization if it relies on user-controlled input. + + +

    +

    + + Since the "null" origin is easy to obtain for an + attacker, it is never safe to use "null" as the value of + the Access-Control-Allow-Origin header when the + Access-Control-Allow-Credentials header value is + "true". + +

    +
    + + +

    + + In the example below, the server allows the browser to send + user credentials in a Cross-Origin request. The request header + origins controls the allowed origins for such a + Cross-Origin request. + +

    + + + +
    + + +
  • Mozilla Developer Network: CORS, Access-Control-Allow-Origin.
  • +
  • Mozilla Developer Network: CORS, Access-Control-Allow-Credentials.
  • +
  • PortSwigger: Exploiting CORS Misconfigurations for Bitcoins and Bounties
  • +
  • W3C: CORS for developers, Advice for Resource Owners
  • +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql new file mode 100644 index 00000000000..3685ec2209c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -0,0 +1,55 @@ +/** + * @name Cors header being set from remote source + * @description Cors header is being set from remote source, allowing to control the origin. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/unvalidated-cors-origin-set + * @tags security + * external/cwe/cwe-346 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.frameworks.Servlets +import semmle.code.java.dataflow.TaintTracking +import DataFlow::PathGraph + +// Check for Access-Control-Allow-Credentials as well, this ensures fair chances of exploitability. +predicate satisfyAllowCredentials(MethodAccess header, MethodAccess check) { + header.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = + "access-control-allow-credentials" and + header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" and + header.getEnclosingCallable() = check.getEnclosingCallable() +} + +predicate checkAccessControlAllowOriginHeader(Expr expr) { + expr.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin" +} + +class CorsOriginConfig extends TaintTracking::Configuration { + CorsOriginConfig() { this = "CORSOriginConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(ResponseSetHeaderMethod h, MethodAccess m | + m = h.getAReference() and + checkAccessControlAllowOriginHeader(m.getArgument(0)) and + satisfyAllowCredentials(h.getAReference(), m) and + sink.asExpr() = m.getArgument(1) + ) + or + exists(ResponseAddHeaderMethod a, MethodAccess m | + m = a.getAReference() and + checkAccessControlAllowOriginHeader(m.getArgument(0)) and + satisfyAllowCredentials(a.getAReference(), m) and + sink.asExpr() = m.getArgument(1) + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Cors header is being set using user controlled value $@.", + source.getNode(), "user-provided value" diff --git a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected new file mode 100644 index 00000000000..8bb9202b582 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected @@ -0,0 +1,7 @@ +edges +| UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | UnvalidatedCors.java:40:67:40:69 | url | +nodes +| UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UnvalidatedCors.java:40:67:40:69 | url | semmle.label | url | +#select +| UnvalidatedCors.java:40:67:40:69 | url | UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | UnvalidatedCors.java:40:67:40:69 | url | Cors header is being set using user controlled value $@. | UnvalidatedCors.java:34:22:34:48 | getHeader(...) | user-provided value | diff --git a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java new file mode 100644 index 00000000000..b05a88ab432 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java @@ -0,0 +1,52 @@ +package com.mossle.core.servlet; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; + +/** + *
    + * 
    + * 
    + */ +public class UnvalidatedCors implements Filter { + public void init(FilterConfig filterConfig) throws ServletException { + // init + } + + public void doFilter(ServletRequest req, ServletResponse res, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + String url = request.getHeader("Origin"); + + if (!StringUtils.isEmpty(url)) { + String val = response.getHeader("Access-Control-Allow-Origin"); + + if (StringUtils.isEmpty(val)) { + response.addHeader("Access-Control-Allow-Origin", url); + response.addHeader("Access-Control-Allow-Credentials", "true"); + } + } + + chain.doFilter(req, res); + } + + public void destroy() { + // destroy + } +} + diff --git a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.qlref b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.qlref new file mode 100644 index 00000000000..93df7860cb4 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-346/UnvalidatedCors.ql diff --git a/java/ql/test/query-tests/security/CWE-346/options b/java/ql/test/query-tests/security/CWE-346/options new file mode 100644 index 00000000000..4fd2033a3d4 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-346/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/apache-commons-lang3-3.7 diff --git a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java new file mode 100644 index 00000000000..62b13e0e259 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java @@ -0,0 +1,7 @@ +package org.apache.commons.lang3; + +public class StringUtils { + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } +} diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/Filter.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/Filter.java new file mode 100644 index 00000000000..47fb902a3d6 --- /dev/null +++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/Filter.java @@ -0,0 +1,11 @@ +package javax.servlet; + +import java.io.IOException; + +public interface Filter { + default public void init(FilterConfig filterConfig) throws ServletException {} + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) + throws IOException, ServletException; + default public void destroy() {} +} diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterChain.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterChain.java new file mode 100644 index 00000000000..b9f9708be56 --- /dev/null +++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterChain.java @@ -0,0 +1,7 @@ +package javax.servlet; + +import java.io.IOException; + +public interface FilterChain { + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; +} diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterConfig.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterConfig.java new file mode 100644 index 00000000000..85d172231e9 --- /dev/null +++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/FilterConfig.java @@ -0,0 +1,10 @@ +package javax.servlet; + +import java.util.Enumeration; + +public interface FilterConfig { + public String getFilterName(); + public ServletContext getServletContext(); + public String getInitParameter(String name); + public Enumeration getInitParameterNames(); +} diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java index 2971e023390..162ac0db3cc 100644 --- a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java +++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java @@ -24,6 +24,7 @@ package javax.servlet.http; import java.io.IOException; +import java.util.Collection; import javax.servlet.ServletResponse; public interface HttpServletResponse extends ServletResponse { @@ -44,6 +45,11 @@ public interface HttpServletResponse extends ServletResponse { public void addIntHeader(String name, int value); public void setStatus(int sc); public void setStatus(int sc, String sm); + public int getStatus(); + public String getHeader(String name); + public Collection getHeaders(String name); + public Collection getHeaderNames(); + public static final int SC_CONTINUE = 100; public static final int SC_SWITCHING_PROTOCOLS = 101; From 81e372d0782d21d30394e21912db6013c88503b8 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Sun, 24 Jan 2021 20:44:21 +0530 Subject: [PATCH 031/757] Formatting changes --- .../Security/CWE/CWE-346/UnvalidatedCors.java | 10 +++------- .../Security/CWE/CWE-346/UnvalidatedCors.ql | 2 +- .../security/CWE-346/UnvalidatedCors.expected | 8 ++++---- .../security/CWE-346/UnvalidatedCors.java | 19 ++----------------- 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java index 8f388b13553..af07cdd1d8d 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java @@ -12,9 +12,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; public class CorsFilter implements Filter { - public void init(FilterConfig filterConfig) throws ServletException { - // init - } + public void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { @@ -23,7 +21,7 @@ public class CorsFilter implements Filter { String url = request.getHeader("Origin"); if (!StringUtils.isEmpty(url)) { - String val = response.getHeader("Access-Control-Allow-Origin"); + String val = response.getHeader("Access-Control-Allow-Origin"); // BAD -> User controlled CORS header. if (StringUtils.isEmpty(val)) { response.addHeader("Access-Control-Allow-Origin", url); @@ -34,7 +32,5 @@ public class CorsFilter implements Filter { chain.doFilter(req, res); } - public void destroy() { - // destroy - } + public void destroy() {} } diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 3685ec2209c..058bf535b82 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -28,7 +28,7 @@ predicate checkAccessControlAllowOriginHeader(Expr expr) { } class CorsOriginConfig extends TaintTracking::Configuration { - CorsOriginConfig() { this = "CORSOriginConfig" } + CorsOriginConfig() { this = "CorsOriginConfig" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } diff --git a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected index 8bb9202b582..b7954950deb 100644 --- a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected +++ b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected @@ -1,7 +1,7 @@ edges -| UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | UnvalidatedCors.java:40:67:40:69 | url | +| UnvalidatedCors.java:21:22:21:48 | getHeader(...) : String | UnvalidatedCors.java:27:67:27:69 | url | nodes -| UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UnvalidatedCors.java:40:67:40:69 | url | semmle.label | url | +| UnvalidatedCors.java:21:22:21:48 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UnvalidatedCors.java:27:67:27:69 | url | semmle.label | url | #select -| UnvalidatedCors.java:40:67:40:69 | url | UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | UnvalidatedCors.java:40:67:40:69 | url | Cors header is being set using user controlled value $@. | UnvalidatedCors.java:34:22:34:48 | getHeader(...) | user-provided value | +| UnvalidatedCors.java:27:67:27:69 | url | UnvalidatedCors.java:21:22:21:48 | getHeader(...) : String | UnvalidatedCors.java:27:67:27:69 | url | Cors header is being set using user controlled value $@. | UnvalidatedCors.java:21:22:21:48 | getHeader(...) | user-provided value | diff --git a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java index b05a88ab432..9ec3c8466be 100644 --- a/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java +++ b/java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.java @@ -1,5 +1,3 @@ -package com.mossle.core.servlet; - import java.io.IOException; import javax.servlet.Filter; @@ -13,19 +11,8 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; -/** - *
    - * 
    - * 
    - */ public class UnvalidatedCors implements Filter { - public void init(FilterConfig filterConfig) throws ServletException { - // init - } + public void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { @@ -45,8 +32,6 @@ public class UnvalidatedCors implements Filter { chain.doFilter(req, res); } - public void destroy() { - // destroy - } + public void destroy() {} } From 75b79039a1c452de1b78736e8f319537e8fa1d5f Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Sun, 24 Jan 2021 20:46:37 +0530 Subject: [PATCH 032/757] Example fixes --- java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java index af07cdd1d8d..772a64969ea 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java @@ -21,10 +21,10 @@ public class CorsFilter implements Filter { String url = request.getHeader("Origin"); if (!StringUtils.isEmpty(url)) { - String val = response.getHeader("Access-Control-Allow-Origin"); // BAD -> User controlled CORS header. + String val = response.getHeader("Access-Control-Allow-Origin"); if (StringUtils.isEmpty(val)) { - response.addHeader("Access-Control-Allow-Origin", url); + response.addHeader("Access-Control-Allow-Origin", url); // BAD -> User controlled CORS header being set here. response.addHeader("Access-Control-Allow-Credentials", "true"); } } From 14a23eed4f09977b3ce217828b7a40581b78374e Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 25 Jan 2021 19:15:59 +0800 Subject: [PATCH 033/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index 808e5a984a5..10235770b2a 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -14,8 +14,8 @@ class XQueryParserCall extends MethodAccess { m.hasName("prepareExpression") ) } - // return the first parameter of the `bindString` method and use it as a sink - Expr getSink() { result = this.getArgument(0) } + /** Returns the first parameter of the `bindString` method. */ + Expr getInput() { result = this.getArgument(0) } } /** A call to `XQDynamicContext.bindString`. */ From 16308fe557bafc8079a89ed8435b00e27406d730 Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 25 Jan 2021 19:16:18 +0800 Subject: [PATCH 034/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index 10235770b2a..3d5770b5be6 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -29,8 +29,8 @@ class XQueryBindStringCall extends MethodAccess { m.hasName("bindString") ) } - // return the second parameter of the `bindString` method and use it as a sink - Expr getSink() { result = this.getArgument(1) } + /** Returns the second parameter of the `bindString` method. */ + Expr getInput() { result = this.getArgument(1) } } /** Used to determine whether to call the `prepareExpression` method, and the first parameter value can be remotely controlled. */ From d34233b44fd905989191563cd05e303ce3a0eaf6 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 25 Jan 2021 11:10:00 +0000 Subject: [PATCH 035/757] Rewrite XQuery injection to use an additional taint step instead of multiple configurations. Also remove a needless barrier -- the method in question doesn't conduct taint by default, so excluding particular instances of that call is not necessary. --- .../Security/CWE/CWE-652/XQueryInjection.ql | 26 +++--- .../CWE/CWE-652/XQueryInjectionLib.qll | 87 ++++--------------- 2 files changed, 27 insertions(+), 86 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index b4fcac80a43..7163a1fd93d 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -15,25 +15,21 @@ import semmle.code.java.dataflow.FlowSources import XQueryInjectionLib import DataFlow::PathGraph -class XQueryInjectionConfig extends DataFlow::Configuration { +class XQueryInjectionConfig extends TaintTracking::Configuration { XQueryInjectionConfig() { this = "XQueryInjectionConfig" } - override predicate isSource(DataFlow::Node source) { source instanceof XQueryInjectionSource } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } - override predicate isSink(DataFlow::Node sink) { sink instanceof XQueryInjectionSink } + override predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(XQueryExecuteCall execute).getPreparedExpression() + } - override predicate isBarrier(DataFlow::Node node) { - exists(MethodAccess ma, Method m, BindParameterRemoteFlowConf conf, DataFlow::Node node1 | - m = ma.getMethod() - | - node.asExpr() = ma and - m.hasName("bindString") and - m.getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("javax.xml.xquery", "XQDynamicContext") and - ma.getArgument(1) = node1.asExpr() and - conf.hasFlowTo(node1) - ) + /** + * Conveys taint from the input to a `prepareExpression` call to the returned prepared expression. + */ + override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(XQueryParserCall parser | + pred.asExpr() = parser.getInput() and succ.asExpr() = parser) } } diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index 3d5770b5be6..cfdbaaa70da 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -1,7 +1,4 @@ import java -import semmle.code.java.dataflow.FlowSources -import semmle.code.java.dataflow.TaintTracking2 -import DataFlow::PathGraph /** A call to `XQConnection.prepareExpression`. */ class XQueryParserCall extends MethodAccess { @@ -14,77 +11,25 @@ class XQueryParserCall extends MethodAccess { m.hasName("prepareExpression") ) } - /** Returns the first parameter of the `bindString` method. */ + + /** + * Returns the first parameter of the `prepareExpression` method, which provides + * the string, stream or reader to be compiled into a prepared expression. + */ Expr getInput() { result = this.getArgument(0) } } -/** A call to `XQDynamicContext.bindString`. */ -class XQueryBindStringCall extends MethodAccess { - XQueryBindStringCall() { - exists(Method m | - this.getMethod() = m and - m.getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("javax.xml.xquery", "XQDynamicContext") and - m.hasName("bindString") - ) - } - /** Returns the second parameter of the `bindString` method. */ - Expr getInput() { result = this.getArgument(1) } -} - -/** Used to determine whether to call the `prepareExpression` method, and the first parameter value can be remotely controlled. */ -class ParserParameterRemoteFlowConf extends TaintTracking2::Configuration { - ParserParameterRemoteFlowConf() { this = "ParserParameterRemoteFlowConf" } - - override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } - - override predicate isSink(DataFlow::Node sink) { - exists(XQueryParserCall xqpc | xqpc.getSink() = sink.asExpr()) - } -} - -/** Used to determine whether to call the `bindString` method, and the second parameter value can be controlled remotely. */ -class BindParameterRemoteFlowConf extends TaintTracking2::Configuration { - BindParameterRemoteFlowConf() { this = "BindParameterRemoteFlowConf" } - - override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } - - override predicate isSink(DataFlow::Node sink) { - exists(XQueryBindStringCall xqbsc | xqbsc.getSink() = sink.asExpr()) - } -} - -/** - * A data flow source for XQuery injection vulnerability. - * 1. `prepareExpression` call as sink. - * 2. Determine whether the `var1` parameter of `prepareExpression` method can be controlled remotely. - */ -class XQueryInjectionSource extends DataFlow::ExprNode { - XQueryInjectionSource() { - exists(MethodAccess ma, Method m, ParserParameterRemoteFlowConf conf, DataFlow::Node node | - m = ma.getMethod() - | - m.hasName("prepareExpression") and - m.getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("javax.xml.xquery", "XQConnection") and - asExpr() = ma and - node.asExpr() = ma.getArgument(0) and - conf.hasFlowTo(node) - ) - } -} - -/** A data flow sink for XQuery injection vulnerability. */ -class XQueryInjectionSink extends DataFlow::Node { - XQueryInjectionSink() { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasName("executeQuery") and - m.getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") and - asExpr() = ma.getQualifier() +/** A call to `XQPreparedExpression.executeQuery`. */ +class XQueryExecuteCall extends MethodAccess { + XQueryExecuteCall() { + exists(Method m | this.getMethod() = m and + m.hasName("executeQuery") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") ) } + + /** Return this prepared expression. */ + Expr getPreparedExpression() { result = this.getQualifier() } } From 8d701e604a70bdbdda9f2c762edbeffc68b2a5ee Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Mon, 25 Jan 2021 14:17:51 +0100 Subject: [PATCH 036/757] Simplified JexlInjectionLib.qll - Merged multiple method definitions to DirectJexlEvaluationMethod - Don't use TaintPropagatingJexlMethodCall field in JexlInjectionConfig - Better variable names in JexlEvaluationSink --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 90 ++++--------------- 1 file changed, 17 insertions(+), 73 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 25281682326..ea0747f4244 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -8,8 +8,6 @@ import semmle.code.java.dataflow.TaintTracking * It supports both Jexl2 and Jexl3. */ class JexlInjectionConfig extends TaintTracking::Configuration { - TaintPropagatingJexlMethodCall taintPropagatingJexlMethodCall; - JexlInjectionConfig() { this = "JexlInjectionConfig" } override predicate isSource(DataFlow::Node source) { @@ -21,7 +19,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { - taintPropagatingJexlMethodCall.taintFlow(fromNode, toNode) or + any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or returnsDataFromBean(fromNode, toNode) } } @@ -42,14 +40,16 @@ private class TaintedSpringRequestBody extends DataFlow::Node { */ private class JexlEvaluationSink extends DataFlow::ExprNode { JexlEvaluationSink() { - exists(MethodAccess ma, Method m, Expr tainted | ma.getMethod() = m and tainted = asExpr() | - m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = tainted + exists(MethodAccess ma, Method m, Expr taintFrom | + ma.getMethod() = m and taintFrom = this.asExpr() + | + m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom or - m instanceof CallableCallMethod and ma.getQualifier() = tainted + m instanceof CallableCallMethod and ma.getQualifier() = taintFrom or m instanceof JexlEngineGetSetPropertyMethod and ma.getAnArgument().getType() instanceof TypeString and - ma.getAnArgument() = tainted + ma.getAnArgument() = taintFrom ) } } @@ -117,74 +117,18 @@ private class JexlEngineGetSetPropertyMethod extends Method { /** * Defines methods that triggers direct evaluation of Jexl expressions. */ -abstract private class DirectJexlEvaluationMethod extends Method { } - -/** - * A method in the `JexlExpression` class that evaluates a Jexl expression. - */ -private class JexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { - JexlExpressionEvaluateMethod() { +private class DirectJexlEvaluationMethod extends Method { + DirectJexlEvaluationMethod() { getDeclaringType() instanceof JexlExpression and hasName("evaluate") - } -} - -/** - * A method in the `JexlScript` class that executes a Jexl script. - */ -private class JexlScriptExecuteMethod extends DirectJexlEvaluationMethod { - JexlScriptExecuteMethod() { getDeclaringType() instanceof JexlScript and hasName("execute") } -} - -/** - * A method in the `JxltEngine.Expression` class that evaluates an expression. - */ -private class JxltEngineExpressionEvaluateMethod extends DirectJexlEvaluationMethod { - JxltEngineExpressionEvaluateMethod() { - getDeclaringType() instanceof JxltEngineExpression and hasName("evaluate") - } -} - -/** - * A method in the `JxltEngine.Expression` class that evaluates the immediate sub-expressions. - */ -private class JxltEngineExpressionPrepareMethod extends DirectJexlEvaluationMethod { - JxltEngineExpressionPrepareMethod() { - getDeclaringType() instanceof JxltEngineExpression and hasName("prepare") - } -} - -/** - * A method in the `JxltEngine.Template` class that evaluates a template. - */ -private class JxltEngineTemplateEvaluateMethod extends DirectJexlEvaluationMethod { - JxltEngineTemplateEvaluateMethod() { + or + getDeclaringType() instanceof JexlScript and hasName("execute") + or + getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"]) + or getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate") - } -} - -/** - * A method in the `UnifiedJEXL.Expression` class that evaluates a template. - */ -private class UnifiedJexlExpressionEvaluateMethod extends DirectJexlEvaluationMethod { - UnifiedJexlExpressionEvaluateMethod() { - getDeclaringType() instanceof UnifiedJexlExpression and hasName("evaluate") - } -} - -/** - * A method in the `UnifiedJEXL.Expression` class that evaluates the immediate sub-expressions. - */ -private class UnifiedJexlExpressionPrepareMethod extends DirectJexlEvaluationMethod { - UnifiedJexlExpressionPrepareMethod() { - getDeclaringType() instanceof UnifiedJexlExpression and hasName("prepare") - } -} - -/** - * A method in the `UnifiedJEXL.Template` class that evaluates a template. - */ -private class UnifiedJexlTemplateEvaluateMethod extends DirectJexlEvaluationMethod { - UnifiedJexlTemplateEvaluateMethod() { + or + getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"]) + or getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate") } } From 985d3d469aed73369d809befb847140b455a8010 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Mon, 25 Jan 2021 23:26:36 +0530 Subject: [PATCH 037/757] PR feedback integration --- .../CWE/CWE-346/UnvalidatedCors.qhelp | 17 +++++---- .../Security/CWE/CWE-346/UnvalidatedCors.ql | 38 ++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp index 3293352c0d5..b10d2a9150a 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp @@ -7,7 +7,7 @@

    A server can send the - "Access-Control-Allow-Credentials" CORS header to control + Access-Control-Allow-Credentials CORS header to control when a browser may send user credentials in Cross-Origin HTTP requests. @@ -15,12 +15,12 @@

    When the Access-Control-Allow-Credentials header - is "true", the Access-Control-Allow-Origin - header must have a value different from "*" in order to + is true, the Access-Control-Allow-Origin + header must have a value different from * in order to make browsers accept the header. Therefore, to allow multiple origins for Cross-Origin requests with credentials, the server must dynamically compute the value of the - "Access-Control-Allow-Origin" header. Computing this + Access-Control-Allow-Origin header. Computing this header value from information in the request to the server can therefore potentially allow an attacker to control the origins that the browser sends credentials to. @@ -35,7 +35,7 @@

    When the Access-Control-Allow-Credentials header - value is "true", a dynamic computation of the + value is true, a dynamic computation of the Access-Control-Allow-Origin header must involve sanitization if it relies on user-controlled input. @@ -43,11 +43,12 @@

    - Since the "null" origin is easy to obtain for an - attacker, it is never safe to use "null" as the value of + Since the null origin is easy to obtain for an + attacker, it is never safe to use null as the value of the Access-Control-Allow-Origin header when the Access-Control-Allow-Credentials header value is - "true". + true.This can be done using a sandboxed iframe. A more detailed + explanation is available in the portswigger blogpost.

    diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 058bf535b82..17e4cddb87e 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -15,16 +15,17 @@ import semmle.code.java.frameworks.Servlets import semmle.code.java.dataflow.TaintTracking import DataFlow::PathGraph -// Check for Access-Control-Allow-Credentials as well, this ensures fair chances of exploitability. -predicate satisfyAllowCredentials(MethodAccess header, MethodAccess check) { +/** + * Holds if `header` sets `Access-Control-Allow-Credentials` to `true`. This ensures fair chances of exploitability. + */ +private predicate setsAllowCredentials(MethodAccess header) { header.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-credentials" and - header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" and - header.getEnclosingCallable() = check.getEnclosingCallable() + header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" } -predicate checkAccessControlAllowOriginHeader(Expr expr) { - expr.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin" +private Expr getAccessControlAllowOriginHeaderName() { + result.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin" } class CorsOriginConfig extends TaintTracking::Configuration { @@ -33,18 +34,19 @@ class CorsOriginConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(ResponseSetHeaderMethod h, MethodAccess m | - m = h.getAReference() and - checkAccessControlAllowOriginHeader(m.getArgument(0)) and - satisfyAllowCredentials(h.getAReference(), m) and - sink.asExpr() = m.getArgument(1) - ) - or - exists(ResponseAddHeaderMethod a, MethodAccess m | - m = a.getAReference() and - checkAccessControlAllowOriginHeader(m.getArgument(0)) and - satisfyAllowCredentials(a.getAReference(), m) and - sink.asExpr() = m.getArgument(1) + exists(MethodAccess corsheader, MethodAccess allowcredentialsheader | + ( + corsheader.getMethod() instanceof ResponseSetHeaderMethod or + corsheader.getMethod() instanceof ResponseAddHeaderMethod + ) and + ( + allowcredentialsheader.getMethod() instanceof ResponseSetHeaderMethod or + allowcredentialsheader.getMethod() instanceof ResponseAddHeaderMethod + ) and + getAccessControlAllowOriginHeaderName() = corsheader.getArgument(0) and + setsAllowCredentials(allowcredentialsheader) and + corsheader.getEnclosingCallable() = allowcredentialsheader.getEnclosingCallable() and + sink.asExpr() = corsheader.getArgument(1) ) } } From 44bc6d7fdbd206c09909f3fdaf6140c84d514b42 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 25 Jan 2021 17:02:26 -0800 Subject: [PATCH 038/757] C++/C#: add NonPhiMemoryOperand union type This fixes a performance issue where the whole MemoryOperand table was scanned in some predicates that used only NonPhiMemoryOperand --- .../semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll | 2 +- .../semmle/code/cpp/ir/implementation/internal/TOperand.qll | 4 ++++ cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll | 2 +- .../code/cpp/ir/implementation/unaliased_ssa/Operand.qll | 2 +- .../src/experimental/ir/implementation/internal/TOperand.qll | 3 +++ csharp/ql/src/experimental/ir/implementation/raw/Operand.qll | 2 +- .../experimental/ir/implementation/unaliased_ssa/Operand.qll | 2 +- 7 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index 38fc6264133..a35c4a5c286 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -272,7 +272,7 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOperand { override MemoryOperandTag tag; NonPhiMemoryOperand() { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index 243429603c7..4d6141cef06 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -100,6 +100,8 @@ module RawOperands { class TChiOperand = Internal::TNoOperand; + class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; + /** * Returns the Phi operand with the specified parameters. */ @@ -129,6 +131,7 @@ module UnaliasedSSAOperands { class TChiOperand = Internal::TNoOperand; + class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; /** * Returns the Phi operand with the specified parameters. */ @@ -158,6 +161,7 @@ module AliasedSSAOperands { class TChiOperand = Internal::TAliasedChiOperand; + class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; /** * Returns the Phi operand with the specified parameters. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index 38fc6264133..a35c4a5c286 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -272,7 +272,7 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOperand { override MemoryOperandTag tag; NonPhiMemoryOperand() { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index 38fc6264133..a35c4a5c286 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -272,7 +272,7 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOperand { override MemoryOperandTag tag; NonPhiMemoryOperand() { diff --git a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll index 143201eea14..027e37f4dd3 100644 --- a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll +++ b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll @@ -80,6 +80,7 @@ module RawOperands { class TChiOperand = Internal::TNoOperand; + class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; /** * Returns the Phi operand with the specified parameters. */ @@ -109,6 +110,8 @@ module UnaliasedSSAOperands { class TChiOperand = Internal::TNoOperand; + class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; + /** * Returns the Phi operand with the specified parameters. */ diff --git a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll index 38fc6264133..a35c4a5c286 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll @@ -272,7 +272,7 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOperand { override MemoryOperandTag tag; NonPhiMemoryOperand() { diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll index 38fc6264133..a35c4a5c286 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll @@ -272,7 +272,7 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand { /** * A memory operand other than the operand of a `Phi` instruction. */ -class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand { +class NonPhiMemoryOperand extends NonPhiOperand, MemoryOperand, TNonPhiMemoryOperand { override MemoryOperandTag tag; NonPhiMemoryOperand() { From 19872e9aedb7892acf2be3c7f693a085c7cc6de6 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Tue, 26 Jan 2021 17:24:17 +0530 Subject: [PATCH 039/757] More Feedback integration --- java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp | 2 +- java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql | 8 ++++---- .../org/apache/commons/lang3/StringUtils.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp index b10d2a9150a..d01c27c23ca 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp @@ -48,7 +48,7 @@ the Access-Control-Allow-Origin header when the Access-Control-Allow-Credentials header value is true.This can be done using a sandboxed iframe. A more detailed - explanation is available in the portswigger blogpost. + explanation is available in the portswigger blogpost referenced below.

    diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 17e4cddb87e..3d98237b104 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -19,6 +19,10 @@ import DataFlow::PathGraph * Holds if `header` sets `Access-Control-Allow-Credentials` to `true`. This ensures fair chances of exploitability. */ private predicate setsAllowCredentials(MethodAccess header) { + ( + header.getMethod() instanceof ResponseSetHeaderMethod or + header.getMethod() instanceof ResponseAddHeaderMethod + ) and header.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-credentials" and header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" @@ -39,10 +43,6 @@ class CorsOriginConfig extends TaintTracking::Configuration { corsheader.getMethod() instanceof ResponseSetHeaderMethod or corsheader.getMethod() instanceof ResponseAddHeaderMethod ) and - ( - allowcredentialsheader.getMethod() instanceof ResponseSetHeaderMethod or - allowcredentialsheader.getMethod() instanceof ResponseAddHeaderMethod - ) and getAccessControlAllowOriginHeaderName() = corsheader.getArgument(0) and setsAllowCredentials(allowcredentialsheader) and corsheader.getEnclosingCallable() = allowcredentialsheader.getEnclosingCallable() and diff --git a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java index 62b13e0e259..06517fa9615 100644 --- a/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java +++ b/java/ql/test/stubs/apache-commons-lang3-3.7/org/apache/commons/lang3/StringUtils.java @@ -2,6 +2,6 @@ package org.apache.commons.lang3; public class StringUtils { public static boolean isEmpty(final CharSequence cs) { - return cs == null || cs.length() == 0; + return true; } } From 8919e5546b1976e5b575f8dfc0102a8fa1d2d4a0 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 26 Jan 2021 14:12:49 -0800 Subject: [PATCH 040/757] C++ Use dontcare instead of one-use exists Co-authored-by: Mathias Vorreiter Pedersen --- .../src/semmle/code/cpp/ir/implementation/raw/Operand.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index a35c4a5c286..be72eeeabe0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -27,13 +27,13 @@ private class TStageOperand = class Operand extends TStageOperand { Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + this = registerOperand(_, _, _) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + this = nonSSAMemoryOperand(_, _) or - exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + this = phiOperand(_, _, _, _) or - exists(Instruction use | this = chiOperand(use, _)) + this = chiOperand(_, _) } /** Gets a textual representation of this element. */ From b76854a384cb7636e2a396fc399b12213dc2ff69 Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 27 Jan 2021 10:14:33 +0800 Subject: [PATCH 041/757] *)add CWE-652 test case --- .../security/CWE-652/XQueryInjection.expected | 19 ++++ .../security/CWE-652/XQueryInjection.java | 87 +++++++++++++++++++ .../security/CWE-652/XQueryInjection.qlref | 1 + .../query-tests/security/CWE-652/options | 1 + .../javax/xml/xquery/XQConnection.java | 19 ++++ .../javax/xml/xquery/XQDataFactory.java | 5 ++ .../javax/xml/xquery/XQDataSource.java | 5 ++ .../javax/xml/xquery/XQDynamicContext.java | 7 ++ .../javax/xml/xquery/XQException.java | 3 + .../javax/xml/xquery/XQItemAccessor.java | 7 ++ .../javax/xml/xquery/XQItemType.java | 68 +++++++++++++++ .../xml/xquery/XQPreparedExpression.java | 5 ++ .../javax/xml/xquery/XQResultSequence.java | 3 + .../javax/xml/xquery/XQSequence.java | 5 ++ .../javax/xml/xquery/XQSequenceType.java | 3 + .../javax/xml/xquery/XQStaticContext.java | 3 + .../net/sf/saxon/Configuration.java | 6 ++ .../net/sf/saxon/SourceResolver.java | 3 + .../net/sf/saxon/xqj/Closable.java | 3 + .../net/sf/saxon/xqj/SaxonXQConnection.java | 41 +++++++++ .../net/sf/saxon/xqj/SaxonXQDataFactory.java | 11 +++ .../net/sf/saxon/xqj/SaxonXQDataSource.java | 12 +++ .../sf/saxon/xqj/SaxonXQStaticContext.java | 5 ++ 23 files changed, 322 insertions(+) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.qlref create mode 100644 java/ql/test/experimental/query-tests/security/CWE-652/options create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataFactory.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataSource.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDynamicContext.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQException.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemAccessor.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemType.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQPreparedExpression.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQResultSequence.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequence.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequenceType.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQStaticContext.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/Configuration.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/SourceResolver.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/Closable.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataFactory.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataSource.java create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQStaticContext.java diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected new file mode 100644 index 00000000000..1ac4289a99c --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected @@ -0,0 +1,19 @@ +edges +| XQueryInjection.java:26:37:26:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:27:35:27:38 | xqpe | +| XQueryInjection.java:41:37:41:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:42:35:42:38 | xqpe | +| XQueryInjection.java:53:37:53:64 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:54:35:54:38 | xqpe | +| XQueryInjection.java:66:37:66:62 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:67:35:67:38 | xqpe | +nodes +| XQueryInjection.java:26:37:26:65 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:27:35:27:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:41:37:41:65 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:42:35:42:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:53:37:53:64 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:54:35:54:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:66:37:66:62 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | +| XQueryInjection.java:67:35:67:38 | xqpe | semmle.label | xqpe | +#select +| XQueryInjection.java:27:35:27:38 | xqpe | XQueryInjection.java:26:37:26:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:27:35:27:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:26:37:26:65 | prepareExpression(...) | this user input | +| XQueryInjection.java:42:35:42:38 | xqpe | XQueryInjection.java:41:37:41:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:42:35:42:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:41:37:41:65 | prepareExpression(...) | this user input | +| XQueryInjection.java:54:35:54:38 | xqpe | XQueryInjection.java:53:37:53:64 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:54:35:54:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:53:37:53:64 | prepareExpression(...) | this user input | +| XQueryInjection.java:67:35:67:38 | xqpe | XQueryInjection.java:66:37:66:62 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:67:35:67:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:66:37:66:62 | prepareExpression(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java new file mode 100644 index 00000000000..48846ae1bca --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java @@ -0,0 +1,87 @@ +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import javax.servlet.http.HttpServletRequest; +import javax.xml.namespace.QName; +import javax.xml.xquery.XQConnection; +import javax.xml.xquery.XQDataSource; +import javax.xml.xquery.XQException; +import javax.xml.xquery.XQItemType; +import javax.xml.xquery.XQPreparedExpression; +import javax.xml.xquery.XQResultSequence; +import net.sf.saxon.xqj.SaxonXQDataSource; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class XQueryInjection { + + @RequestMapping + public void testRequestbad(HttpServletRequest request) throws Exception { + String name = request.getParameter("name"); + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + XQPreparedExpression xqpe = conn.prepareExpression(query); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + + } + + + @RequestMapping + public void testStringtbad(@RequestParam String nameStr) throws XQException { + String name = nameStr; + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + XQPreparedExpression xqpe = conn.prepareExpression(query); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } + + @RequestMapping + public void testInputStreambad(HttpServletRequest request) throws Exception { + InputStream name = request.getInputStream(); + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + XQPreparedExpression xqpe = conn.prepareExpression(name); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } + + @RequestMapping + public void testReaderbad(HttpServletRequest request) throws Exception { + InputStream name = request.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(name)); + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + XQPreparedExpression xqpe = conn.prepareExpression(br); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } + + @RequestMapping + public void good(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + XQDataSource ds = new SaxonXQDataSource(); + XQConnection conn = ds.getConnection(); + String query = "declare variable $name as xs:string external;" + + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; + XQPreparedExpression xqpe = conn.prepareExpression(query); + xqpe.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + XQResultSequence result = xqpe.executeQuery(); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.qlref new file mode 100644 index 00000000000..9bdeeeffa0f --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-652/XQueryInjection.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/options b/java/ql/test/experimental/query-tests/security/CWE-652/options new file mode 100644 index 00000000000..98e36c19267 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-652/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/saxon-xqj-9.x/:${testdir}/../../../../stubs/springframework-5.2.3/ diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java new file mode 100644 index 00000000000..3cff7592168 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java @@ -0,0 +1,19 @@ +package javax.xml.xquery; + +import java.io.InputStream; +import java.io.Reader; + +public interface XQConnection extends XQDataFactory { + + XQPreparedExpression prepareExpression(String var1) throws XQException; + + XQPreparedExpression prepareExpression(String var1, XQStaticContext var2) throws XQException; + + XQPreparedExpression prepareExpression(Reader var1) throws XQException; + + XQPreparedExpression prepareExpression(Reader var1, XQStaticContext var2) throws XQException; + + XQPreparedExpression prepareExpression(InputStream var1) throws XQException; + + XQPreparedExpression prepareExpression(InputStream var1, XQStaticContext var2) throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataFactory.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataFactory.java new file mode 100644 index 00000000000..6bc2dbade6d --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataFactory.java @@ -0,0 +1,5 @@ +package javax.xml.xquery; + +public interface XQDataFactory { + XQItemType createAtomicType(int var1) throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataSource.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataSource.java new file mode 100644 index 00000000000..7f981a1ff00 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDataSource.java @@ -0,0 +1,5 @@ +package javax.xml.xquery; + +public interface XQDataSource { + XQConnection getConnection() throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDynamicContext.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDynamicContext.java new file mode 100644 index 00000000000..c5058a2f866 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQDynamicContext.java @@ -0,0 +1,7 @@ +package javax.xml.xquery; + +import javax.xml.namespace.QName; + +public interface XQDynamicContext { + void bindString(QName var1, String var2, XQItemType var3) throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQException.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQException.java new file mode 100644 index 00000000000..4d81c322327 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQException.java @@ -0,0 +1,3 @@ +package javax.xml.xquery; + +public class XQException extends Exception {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemAccessor.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemAccessor.java new file mode 100644 index 00000000000..b52f5c16461 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemAccessor.java @@ -0,0 +1,7 @@ +package javax.xml.xquery; + +import java.util.Properties; + +public interface XQItemAccessor { + String getItemAsString(Properties var1) throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemType.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemType.java new file mode 100644 index 00000000000..b6c9cb896b0 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQItemType.java @@ -0,0 +1,68 @@ +package javax.xml.xquery; + +public interface XQItemType extends XQSequenceType { + int XQITEMKIND_ATOMIC = 1; + int XQITEMKIND_ATTRIBUTE = 2; + int XQITEMKIND_COMMENT = 3; + int XQITEMKIND_DOCUMENT = 4; + int XQITEMKIND_DOCUMENT_ELEMENT = 5; + int XQITEMKIND_DOCUMENT_SCHEMA_ELEMENT = 6; + int XQITEMKIND_ELEMENT = 7; + int XQITEMKIND_ITEM = 8; + int XQITEMKIND_NODE = 9; + int XQITEMKIND_PI = 10; + int XQITEMKIND_TEXT = 11; + int XQITEMKIND_SCHEMA_ELEMENT = 12; + int XQITEMKIND_SCHEMA_ATTRIBUTE = 13; + int XQBASETYPE_UNTYPED = 1; + int XQBASETYPE_ANYTYPE = 2; + int XQBASETYPE_ANYSIMPLETYPE = 3; + int XQBASETYPE_ANYATOMICTYPE = 4; + int XQBASETYPE_UNTYPEDATOMIC = 5; + int XQBASETYPE_DAYTIMEDURATION = 6; + int XQBASETYPE_YEARMONTHDURATION = 7; + int XQBASETYPE_ANYURI = 8; + int XQBASETYPE_BASE64BINARY = 9; + int XQBASETYPE_BOOLEAN = 10; + int XQBASETYPE_DATE = 11; + int XQBASETYPE_INT = 12; + int XQBASETYPE_INTEGER = 13; + int XQBASETYPE_SHORT = 14; + int XQBASETYPE_LONG = 15; + int XQBASETYPE_DATETIME = 16; + int XQBASETYPE_DECIMAL = 17; + int XQBASETYPE_DOUBLE = 18; + int XQBASETYPE_DURATION = 19; + int XQBASETYPE_FLOAT = 20; + int XQBASETYPE_GDAY = 21; + int XQBASETYPE_GMONTH = 22; + int XQBASETYPE_GMONTHDAY = 23; + int XQBASETYPE_GYEAR = 24; + int XQBASETYPE_GYEARMONTH = 25; + int XQBASETYPE_HEXBINARY = 26; + int XQBASETYPE_NOTATION = 27; + int XQBASETYPE_QNAME = 28; + int XQBASETYPE_STRING = 29; + int XQBASETYPE_TIME = 30; + int XQBASETYPE_BYTE = 31; + int XQBASETYPE_NONPOSITIVE_INTEGER = 32; + int XQBASETYPE_NONNEGATIVE_INTEGER = 33; + int XQBASETYPE_NEGATIVE_INTEGER = 34; + int XQBASETYPE_POSITIVE_INTEGER = 35; + int XQBASETYPE_UNSIGNED_LONG = 36; + int XQBASETYPE_UNSIGNED_INT = 37; + int XQBASETYPE_UNSIGNED_SHORT = 38; + int XQBASETYPE_UNSIGNED_BYTE = 39; + int XQBASETYPE_NORMALIZED_STRING = 40; + int XQBASETYPE_TOKEN = 41; + int XQBASETYPE_LANGUAGE = 42; + int XQBASETYPE_NAME = 43; + int XQBASETYPE_NCNAME = 44; + int XQBASETYPE_NMTOKEN = 45; + int XQBASETYPE_ID = 46; + int XQBASETYPE_IDREF = 47; + int XQBASETYPE_ENTITY = 48; + int XQBASETYPE_IDREFS = 49; + int XQBASETYPE_ENTITIES = 50; + int XQBASETYPE_NMTOKENS = 51; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQPreparedExpression.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQPreparedExpression.java new file mode 100644 index 00000000000..3ab5024c1e6 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQPreparedExpression.java @@ -0,0 +1,5 @@ +package javax.xml.xquery; + +public interface XQPreparedExpression extends XQDynamicContext { + XQResultSequence executeQuery() throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQResultSequence.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQResultSequence.java new file mode 100644 index 00000000000..c7cd24e8561 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQResultSequence.java @@ -0,0 +1,3 @@ +package javax.xml.xquery; + +public interface XQResultSequence extends XQSequence {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequence.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequence.java new file mode 100644 index 00000000000..b872795a759 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequence.java @@ -0,0 +1,5 @@ +package javax.xml.xquery; + +public interface XQSequence extends XQItemAccessor { + boolean next() throws XQException; +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequenceType.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequenceType.java new file mode 100644 index 00000000000..17d908f9e1c --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQSequenceType.java @@ -0,0 +1,3 @@ +package javax.xml.xquery; + +public interface XQSequenceType {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQStaticContext.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQStaticContext.java new file mode 100644 index 00000000000..60911d4fb72 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQStaticContext.java @@ -0,0 +1,3 @@ +package javax.xml.xquery; + +public interface XQStaticContext {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/Configuration.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/Configuration.java new file mode 100644 index 00000000000..bfd8bf1ff58 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/Configuration.java @@ -0,0 +1,6 @@ +package net.sf.saxon; + +import java.io.Serializable; + + +public class Configuration implements Serializable, SourceResolver {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/SourceResolver.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/SourceResolver.java new file mode 100644 index 00000000000..aa2e2b2fe4d --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/SourceResolver.java @@ -0,0 +1,3 @@ +package net.sf.saxon; + +public interface SourceResolver {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/Closable.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/Closable.java new file mode 100644 index 00000000000..91245a31fc9 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/Closable.java @@ -0,0 +1,3 @@ +package net.sf.saxon.xqj; + +public abstract class Closable {} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java new file mode 100644 index 00000000000..85bae6e7540 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java @@ -0,0 +1,41 @@ +package net.sf.saxon.xqj; + +import java.io.Reader; +import net.sf.saxon.Configuration; +import javax.xml.xquery.XQConnection; +import javax.xml.xquery.XQPreparedExpression; +import javax.xml.xquery.XQException; +import javax.xml.xquery.XQStaticContext; +import java.io.InputStream; + +public class SaxonXQConnection extends SaxonXQDataFactory implements XQConnection { + + private SaxonXQStaticContext staticContext; + + SaxonXQConnection(SaxonXQDataSource dataSource) { + } + + public XQPreparedExpression prepareExpression(InputStream xquery) throws XQException { + return null; + } + + public XQPreparedExpression prepareExpression(InputStream xquery, XQStaticContext properties) throws XQException { + return null; + } + + public XQPreparedExpression prepareExpression(Reader xquery) throws XQException { + return null; + } + + public XQPreparedExpression prepareExpression(Reader xquery, XQStaticContext properties){ + return null; + } + + public XQPreparedExpression prepareExpression(String xquery) throws XQException { + return null; + } + + public XQPreparedExpression prepareExpression(String xquery, XQStaticContext properties) throws XQException { + return null; + } +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataFactory.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataFactory.java new file mode 100644 index 00000000000..2ea1a82541e --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataFactory.java @@ -0,0 +1,11 @@ +package net.sf.saxon.xqj; + +import javax.xml.xquery.XQException; +import javax.xml.xquery.XQDataFactory; +import javax.xml.xquery.XQItemType; + +public abstract class SaxonXQDataFactory extends Closable implements XQDataFactory { + public XQItemType createAtomicType(int baseType) throws XQException { + return null; + } +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataSource.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataSource.java new file mode 100644 index 00000000000..7e2549b6fe8 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQDataSource.java @@ -0,0 +1,12 @@ +package net.sf.saxon.xqj; + +import javax.xml.xquery.XQDataSource; +import javax.xml.xquery.XQException; +import javax.xml.xquery.XQConnection; + +public class SaxonXQDataSource implements XQDataSource { + + public XQConnection getConnection() throws XQException { + return new SaxonXQConnection(this); + } +} diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQStaticContext.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQStaticContext.java new file mode 100644 index 00000000000..e5e19b6ae1f --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQStaticContext.java @@ -0,0 +1,5 @@ +package net.sf.saxon.xqj; + +import javax.xml.xquery.XQStaticContext; + +public class SaxonXQStaticContext implements XQStaticContext {} From b5ae4178515d970dda6ae59931a4ae5b6b2fa2fa Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 27 Jan 2021 10:19:04 +0800 Subject: [PATCH 042/757] *)update CWE-652 qhelp references --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp index 2b6433d6ef3..4e70553557c 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp @@ -21,8 +21,8 @@ This allows an attacker to control the structure of the query.

    -
  • CWE description: -XQuery Injection.
  • +
  • Introduction to XQuery Injection: +Balisage Paper: XQuery Injection.
  • From ca2e6587fe9d14e1fd5a01970f93c2f630a8b5d2 Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 27 Jan 2021 19:46:15 +0800 Subject: [PATCH 043/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp index 4e70553557c..7dfdc846c44 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp @@ -21,8 +21,8 @@ This allows an attacker to control the structure of the query.

    -
  • Introduction to XQuery Injection: -Balisage Paper: XQuery Injection.
  • +
  • Balisage: +XQuery Injection.
  • From 31deca016fd545707e1c269efe96941decc9f0dd Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 27 Jan 2021 19:46:45 +0800 Subject: [PATCH 044/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index 7163a1fd93d..bb18e613b94 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -5,7 +5,7 @@ * @kind path-problem * @problem.severity error * @precision high - * @id java/XQuery-injection + * @id java/xquery-injection * @tags security * external/cwe/cwe-652 */ From 81c56b9bed33f2cd0033f8bc1ba8a8d091db2133 Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 27 Jan 2021 19:47:12 +0800 Subject: [PATCH 045/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index bb18e613b94..b1bca496e5c 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -15,6 +15,9 @@ import semmle.code.java.dataflow.FlowSources import XQueryInjectionLib import DataFlow::PathGraph +/** + * Taint-tracking configuration tracing flow from remote sources, through an XQuery parser, to its eventual execution. + */ class XQueryInjectionConfig extends TaintTracking::Configuration { XQueryInjectionConfig() { this = "XQueryInjectionConfig" } From 59f48ecea3d7f920d7b6b32a64126b6b2a7d3950 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Fri, 29 Jan 2021 12:38:51 +0100 Subject: [PATCH 046/757] Removed LocalUserInput in JexlInjectionLib.ql --- .../src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index ea0747f4244..593ed794557 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -12,8 +12,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof TaintedSpringRequestBody or - source instanceof RemoteFlowSource or - source instanceof LocalUserInput + source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink } From 50edf44e843e393f33454b24784d0f5dd0ba091f Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 2 Feb 2021 09:06:44 -0800 Subject: [PATCH 047/757] C++/C#: autoformat and sync files --- .../code/cpp/ir/implementation/aliased_ssa/Operand.qll | 8 ++++---- .../code/cpp/ir/implementation/internal/TOperand.qll | 2 ++ .../code/cpp/ir/implementation/unaliased_ssa/Operand.qll | 8 ++++---- .../experimental/ir/implementation/internal/TOperand.qll | 1 + .../ql/src/experimental/ir/implementation/raw/Operand.qll | 8 ++++---- .../ir/implementation/unaliased_ssa/Operand.qll | 8 ++++---- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index a35c4a5c286..be72eeeabe0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -27,13 +27,13 @@ private class TStageOperand = class Operand extends TStageOperand { Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + this = registerOperand(_, _, _) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + this = nonSSAMemoryOperand(_, _) or - exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + this = phiOperand(_, _, _, _) or - exists(Instruction use | this = chiOperand(use, _)) + this = chiOperand(_, _) } /** Gets a textual representation of this element. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index 4d6141cef06..90d951a40e5 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -132,6 +132,7 @@ module UnaliasedSSAOperands { class TChiOperand = Internal::TNoOperand; class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; + /** * Returns the Phi operand with the specified parameters. */ @@ -162,6 +163,7 @@ module AliasedSSAOperands { class TChiOperand = Internal::TAliasedChiOperand; class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; + /** * Returns the Phi operand with the specified parameters. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index a35c4a5c286..be72eeeabe0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -27,13 +27,13 @@ private class TStageOperand = class Operand extends TStageOperand { Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + this = registerOperand(_, _, _) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + this = nonSSAMemoryOperand(_, _) or - exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + this = phiOperand(_, _, _, _) or - exists(Instruction use | this = chiOperand(use, _)) + this = chiOperand(_, _) } /** Gets a textual representation of this element. */ diff --git a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll index 027e37f4dd3..9c3e7620186 100644 --- a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll +++ b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll @@ -81,6 +81,7 @@ module RawOperands { class TChiOperand = Internal::TNoOperand; class TNonPhiMemoryOperand = TNonSSAMemoryOperand or TChiOperand; + /** * Returns the Phi operand with the specified parameters. */ diff --git a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll index a35c4a5c286..be72eeeabe0 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll @@ -27,13 +27,13 @@ private class TStageOperand = class Operand extends TStageOperand { Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + this = registerOperand(_, _, _) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + this = nonSSAMemoryOperand(_, _) or - exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + this = phiOperand(_, _, _, _) or - exists(Instruction use | this = chiOperand(use, _)) + this = chiOperand(_, _) } /** Gets a textual representation of this element. */ diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll index a35c4a5c286..be72eeeabe0 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll @@ -27,13 +27,13 @@ private class TStageOperand = class Operand extends TStageOperand { Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + this = registerOperand(_, _, _) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + this = nonSSAMemoryOperand(_, _) or - exists(Instruction use, Instruction def, IRBlock block | this = phiOperand(use, def, block, _)) + this = phiOperand(_, _, _, _) or - exists(Instruction use | this = chiOperand(use, _)) + this = chiOperand(_, _) } /** Gets a textual representation of this element. */ From 631ee28cae1b803585d29d4184db206e15500b71 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 2 Feb 2021 09:11:21 -0800 Subject: [PATCH 048/757] C++: update comments about SSA sharing --- .../semmle/code/cpp/ir/implementation/internal/TOperand.qll | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index 90d951a40e5..0a55587e436 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -43,8 +43,8 @@ private module Internal { } or //// ALIASED //// - // If we share SSA, these will be all the phis there are. Otherwise these - // will add to the ones that are already there. + // Until we share SSA, these will be all the phis there are. With SSA + // sharing, these will add to the ones that are already there. // If we share SSA, be careful with the case where we remove all possible // indirect writes to a variable because they're dead code. In that case it's // important that we use the same definition of "is variable aliased" across @@ -56,7 +56,6 @@ private module Internal { defInstr = AliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) } or TAliasedChiOperand(TAliasedSSAChiInstruction useInstr, ChiOperandTag tag) { - // TODO: any further restrictions here? any() } } From 683233333cf7dc328a1a6fb7726be200ff40ad8f Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Thu, 4 Feb 2021 22:28:10 +0530 Subject: [PATCH 049/757] test case return statements and feedback --- .../PlayActionMethodQueryParameter.expected | 6 +-- .../play/PlayAddCSRFTokenAnnotation.expected | 2 +- .../play/PlayMVCResultsMethods.expected | 6 +-- .../frameworks/play/resources/Resource.java | 51 +++++++++---------- .../playframework-2.6.x/play/libs/F.java | 11 ++-- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/java/ql/test/library-tests/frameworks/play/PlayActionMethodQueryParameter.expected b/java/ql/test/library-tests/frameworks/play/PlayActionMethodQueryParameter.expected index ec118ce4151..c216c07687b 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayActionMethodQueryParameter.expected +++ b/java/ql/test/library-tests/frameworks/play/PlayActionMethodQueryParameter.expected @@ -1,3 +1,3 @@ -| resources/Resource.java:20:39:20:48 | uri | -| resources/Resource.java:25:44:25:55 | token | -| resources/Resource.java:29:58:29:67 | uri | +| resources/Resource.java:19:37:19:46 | uri | +| resources/Resource.java:24:42:24:53 | token | +| resources/Resource.java:28:56:28:65 | uri | diff --git a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected b/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected index 3ee92890816..06f021740be 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected +++ b/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected @@ -1 +1 @@ -| resources/Resource.java:13:5:13:17 | AddCSRFToken | +| resources/Resource.java:12:3:12:15 | AddCSRFToken | diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.expected b/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.expected index 1f5885f6881..6d295a7dd36 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.expected +++ b/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.expected @@ -1,3 +1,3 @@ -| resources/Resource.java:16:16:16:30 | ok(...) | -| resources/Resource.java:26:9:26:17 | ok(...) | -| resources/Resource.java:30:9:30:36 | ok(...) | +| resources/Resource.java:15:12:15:26 | ok(...) | +| resources/Resource.java:25:27:25:35 | ok(...) | +| resources/Resource.java:29:46:29:73 | ok(...) | diff --git a/java/ql/test/library-tests/frameworks/play/resources/Resource.java b/java/ql/test/library-tests/frameworks/play/resources/Resource.java index 3885920f85b..ab20f3cf6e8 100644 --- a/java/ql/test/library-tests/frameworks/play/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/play/resources/Resource.java @@ -1,37 +1,36 @@ +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import play.filters.csrf.AddCSRFToken; +import play.libs.F; +import play.mvc.BodyParser; import play.mvc.Controller; import play.mvc.Http.*; -import play.mvc.Results; import play.mvc.Result; -import play.filters.csrf.AddCSRFToken; -import play.mvc.BodyParser; -import play.libs.F; -import java.util.concurrent.CompletionStage; - public class Resource extends Controller { - @AddCSRFToken - public Result index() { - response().setHeader("X-Play-QL", "1"); - return ok("It works!"); - } + @AddCSRFToken + public Result index() { + response().setHeader("X-Play-QL", "1"); + return ok("It works!"); + } - @BodyParser.Of() - public Result session_redirect_me(String uri) { - String url = request().getQueryString("url"); - redirect(url); - } + @BodyParser.Of() + public Result session_redirect_me(String uri) { + String url = request().getQueryString("url"); + return redirect(url); + } - public F.Promise async_promise(String token) { - ok(token); - } + public F.Promise async_promise(String token) { + return F.Promise.pure(ok(token)); + } - public CompletionStage async_completionstage(String uri) { - ok("Async completion Stage"); - } + public CompletionStage async_completionstage(String uri) { + return CompletableFuture.completedFuture(ok("Async completion Stage")); + } - public String not_playactionmethod(String no_action) { - String return_code = no_action; - return return_code; - } + public String not_playactionmethod(String no_action) { + String return_code = no_action; + return return_code; + } } diff --git a/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java b/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java index f54ff2b5f5b..042fa1da1e7 100644 --- a/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java +++ b/java/ql/test/stubs/playframework-2.6.x/play/libs/F.java @@ -6,11 +6,14 @@ package play.libs; import java.util.*; import java.util.concurrent.*; -import java.util.function.Supplier; -//import scala.concurrent.ExecutionContext; +// import scala.concurrent.ExecutionContext; /** Defines a set of functional programming style helpers. */ public class F { - public static class Promise { } // this is needed for play.libs.F for Play 2.3.x -} \ No newline at end of file + public static class Promise { // this is needed for play.libs.F for Play 2.3.x + public static Promise pure(final A a) { + return null; + } + } +} From a183b00166395b4544782e3b7cdc03a1ca43541c Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Fri, 5 Feb 2021 03:53:01 +0000 Subject: [PATCH 050/757] Query to detect main method in servlets --- .../Security/CWE/CWE-489/ServletMain.java | 10 +++ .../Security/CWE/CWE-489/ServletMain.qhelp | 31 ++++++++ .../Security/CWE/CWE-489/ServletMain.ql | 49 ++++++++++++ .../CWE-489/ServletContextListenerMain.java | 25 +++++++ .../security/CWE-489/ServletMain.expected | 2 + .../security/CWE-489/ServletMain.java | 33 +++++++++ .../security/CWE-489/ServletMain.qlref | 1 + .../query-tests/security/CWE-489/options | 1 + .../javax/servlet/ServletContextEvent.java | 46 ++++++++++++ .../javax/servlet/ServletContextListener.java | 74 +++++++++++++++++++ 10 files changed, 272 insertions(+) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql create mode 100644 java/ql/test/experimental/query-tests/security/CWE-489/ServletContextListenerMain.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.qlref create mode 100644 java/ql/test/experimental/query-tests/security/CWE-489/options create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextEvent.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextListener.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.java b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.java new file mode 100644 index 00000000000..c8ce1136e44 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.java @@ -0,0 +1,10 @@ +public class ServletMain implements Servlet { + // BAD - Implement a main method in servlet. + public static void main(String[] args) throws Exception { + // Connect to my server + URL url = new URL("https://www.example.com"); + url.openConnection(); + } + + // GOOD - Not to have a main method in servlet. +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.qhelp b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.qhelp new file mode 100644 index 00000000000..8dee70e6d36 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.qhelp @@ -0,0 +1,31 @@ + + + + +

    Debug code can create unintended entry points in a deployed web application therefore should never make into production. There is no reason to have a main method in a web application. Having a main method in a web application increases the attack surface that an attacker can exploit to attack the application logic.

    +
    + + +

    Remove the main method from web components including servlets, filters and listeners.

    +
    + + +

    The following example shows two ways of implementing web components. In the 'BAD' case, a main method is implemented. In the 'GOOD' case, no main method is implemented.

    + +
    + + +
  • + Fortify: + J2EE Bad Practices: Leftover Debug Code +
  • +
  • + SonarSource: + Web applications should not have a "main" method +
  • +
  • + Carnegie Mellon University: + ENV06-J. Production code must not contain debugging entry points +
  • +
    + diff --git a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql new file mode 100644 index 00000000000..d8e0f0abb8a --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql @@ -0,0 +1,49 @@ +/** + * @name Main Method in Servlet + * @description Jave EE web applications with a main method. + * @kind problem + * @id java/main-method-in-servlet + * @tags security + * external/cwe-489 + */ + +import java +import semmle.code.java.frameworks.Servlets + +/** The java type `javax.servlet.Filter` */ +class ServletFilterClass extends Class { + ServletFilterClass() { this.getASupertype*().hasQualifiedName("javax.servlet", "Filter") } +} + +/** Listener class in the package `javax.servlet` and `javax.servlet.http` */ +class ServletListenerClass extends Class { + // Various listener classes of Java EE such as ServletContextListener. They all have a name ending with the word "Listener". + ServletListenerClass() { + this.getASupertype*() + .getQualifiedName() + .regexpMatch([ + "javax\\.servlet\\.[a-zA-Z]+Listener", "javax\\.servlet\\.http\\.[a-zA-Z]+Listener" + ]) + } +} + +/** The `main` method in `Servlet`. */ +class ServletMainMethod extends Method { + ServletMainMethod() { + ( + this.getDeclaringType() instanceof ServletClass or + this.getDeclaringType() instanceof ServletFilterClass or + this.getDeclaringType() instanceof ServletListenerClass + ) and + this.hasName("main") and + this.isStatic() and + this.getReturnType() instanceof VoidType and + this.isPublic() and + this.getNumberOfParameters() = 1 and + this.getParameter(0).getType() instanceof Array and + not this.getDeclaringType().getName().matches("%Test%") // Simple check to exclude test classes to reduce FPs + } +} + +from ServletMainMethod sm +select sm, "Web application has a main method." diff --git a/java/ql/test/experimental/query-tests/security/CWE-489/ServletContextListenerMain.java b/java/ql/test/experimental/query-tests/security/CWE-489/ServletContextListenerMain.java new file mode 100644 index 00000000000..38ce153aa5a --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-489/ServletContextListenerMain.java @@ -0,0 +1,25 @@ +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.net.URL; + +public class ServletContextListenerMain implements ServletContextListener { + @Override + public void contextInitialized(ServletContextEvent sce) { + System.out.println("listener starts to work!"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + System.out.println("listener stopped!"); + } + + // BAD - Implement a main method in servlet listener. + public static void main(String[] args) { + try { + URL url = new URL("https://www.example.com"); + url.openConnection(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.expected b/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.expected new file mode 100644 index 00000000000..9f9b86d79a2 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.expected @@ -0,0 +1,2 @@ +| ServletContextListenerMain.java:17:21:17:24 | main | Web application has a main method. | +| ServletMain.java:28:21:28:24 | main | Web application has a main method. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.java b/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.java new file mode 100644 index 00000000000..55b73bd3b72 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.java @@ -0,0 +1,33 @@ +import javax.servlet.Servlet; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.ServletException; +import javax.servlet.ServletConfig; +import java.io.IOException; +import java.net.URL; + +public class ServletMain implements Servlet { + public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { + } + + public void init(ServletConfig servletConfig) throws ServletException { + } + + public ServletConfig getServletConfig() { + return null; + } + + public String getServletInfo() { + return null; + } + + public void destroy() { + } + + // BAD - Implement a main method in servlet. + public static void main(String[] args) throws Exception { + // Connect to my server + URL url = new URL("https://www.example.com"); + url.openConnection(); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.qlref b/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.qlref new file mode 100644 index 00000000000..5cfb5d73d58 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-489/ServletMain.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-489/ServletMain.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-489/options b/java/ql/test/experimental/query-tests/security/CWE-489/options new file mode 100644 index 00000000000..dd125ad0f4e --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-489/options @@ -0,0 +1 @@ +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4 \ No newline at end of file diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextEvent.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextEvent.java new file mode 100644 index 00000000000..aa27badc7fe --- /dev/null +++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved. + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.servlet; + +/** + * This is the event class for notifications about changes to + * the servlet context of a web application. + * @see ServletContextListener + * + * @since Servlet 2.3 + */ + +public class ServletContextEvent extends java.util.EventObject { + + /** Construct a ServletContextEvent from the given context. + * + * @param source - the ServletContext that is sending the event. + */ + public ServletContextEvent(ServletContext source) { + super(source); + } + + /** + * Return the ServletContext that changed. + * + * @return the ServletContext that sent the event. + */ + public ServletContext getServletContext () { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextListener.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextListener.java new file mode 100644 index 00000000000..7eb2019b24d --- /dev/null +++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContextListener.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved. + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.servlet; + +import java.util.EventListener; + +/** + * Interface for receiving notification events about ServletContext + * lifecycle changes. + * + *

    In order to receive these notification events, the implementation + * class must be either declared in the deployment descriptor of the web + * application, annotated with {@link javax.servlet.annotation.WebListener}, + * or registered via one of the addListener methods defined on + * {@link ServletContext}. + * + *

    Implementations of this interface are invoked at their + * {@link #contextInitialized} method in the order in which they have been + * declared, and at their {@link #contextDestroyed} method in reverse + * order. + * + * @see ServletContextEvent + * + * @since Servlet 2.3 + */ +public interface ServletContextListener extends EventListener { + + /** + * Receives notification that the web application initialization + * process is starting. + * + *

    All ServletContextListeners are notified of context + * initialization before any filters or servlets in the web + * application are initialized. + * + * @param sce the ServletContextEvent containing the ServletContext + * that is being initialized + * + * @implSpec + * The default implementation takes no action. + */ + default public void contextInitialized(ServletContextEvent sce) {} + + /** + * Receives notification that the ServletContext is about to be + * shut down. + * + *

    All servlets and filters will have been destroyed before any + * ServletContextListeners are notified of context + * destruction. + * + * @param sce the ServletContextEvent containing the ServletContext + * that is being destroyed + * + * @implSpec + * The default implementation takes no action. + */ + default public void contextDestroyed(ServletContextEvent sce) {} +} From 97690b4eb7e66dac034d1482e3f7b4da50166357 Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 8 Feb 2021 19:15:28 +0800 Subject: [PATCH 051/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp Co-authored-by: Felicity Chapman --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp index 7dfdc846c44..1ca6e780627 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.qhelp @@ -3,8 +3,8 @@ "qhelp.dtd"> -

    The software uses external input to dynamically construct an XQuery expression used to retrieve data from an XML database, but it does not neutralize or incorrectly neutralizes that input. -This allows an attacker to control the structure of the query.

    +

    The software uses external input to dynamically construct an XQuery expression which is then used to retrieve data from an XML database. +However, the input is not neutralized, or is incorrectly neutralized, which allows an attacker to control the structure of the query.

    From 77ae91c47ddc651fa94021bdf4f35fb350727e29 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Tue, 2 Feb 2021 08:58:13 +0100 Subject: [PATCH 052/757] Python: Add concept `ExceptionSource` --- python/ql/src/semmle/python/Concepts.qll | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll index b81817d7f10..a06eee7fdc5 100644 --- a/python/ql/src/semmle/python/Concepts.qll +++ b/python/ql/src/semmle/python/Concepts.qll @@ -293,6 +293,41 @@ module SqlExecution { } } +/** + * A data-flow node that carries information about an error. Such information should + * rarely be exposed directly to the user. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `ErrorInfoSource::Range` instead. + */ +class ErrorInfoSource extends DataFlow::Node { + ErrorInfoSource::Range range; + + ErrorInfoSource() { this = range } +} + +/** Provides a class for modeling new sources of error information, say via APIs. */ +module ErrorInfoSource { + abstract class Range extends DataFlow::Node { } +} + +/** + * A data-flow node that represents the creation or introduction of an exception. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `ExceptionSource::Range` instead. + */ +class ExceptionSource extends ErrorInfoSource::Range { + ExceptionSource::Range range; + + ExceptionSource() { this = range } +} + +/** Provides a class for modeling new sources of exceptions, say via APIs. */ +module ExceptionSource { + abstract class Range extends DataFlow::Node { } +} + /** Provides classes for modeling HTTP-related APIs. */ module HTTP { import semmle.python.web.HttpConstants From 6a45f6e7e0eac6320c2abc038f48cb15703c8476 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Wed, 3 Feb 2021 08:39:41 +0100 Subject: [PATCH 053/757] Python: Port StackTraceExposure query using empty concept implementation --- .../Security/CWE-209/StackTraceExposure.ql | 19 +++++----------- .../security/dataflow/StackTraceExposure.qll | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 python/ql/src/semmle/python/security/dataflow/StackTraceExposure.qll diff --git a/python/ql/src/Security/CWE-209/StackTraceExposure.ql b/python/ql/src/Security/CWE-209/StackTraceExposure.ql index 27d89607a29..a2f3b7540fd 100644 --- a/python/ql/src/Security/CWE-209/StackTraceExposure.ql +++ b/python/ql/src/Security/CWE-209/StackTraceExposure.ql @@ -13,19 +13,10 @@ */ import python -import semmle.python.security.Paths -import semmle.python.security.Exceptions -import semmle.python.web.HttpResponse +import semmle.python.security.dataflow.StackTraceExposure +import DataFlow::PathGraph -class StackTraceExposureConfiguration extends TaintTracking::Configuration { - StackTraceExposureConfiguration() { this = "Stack trace exposure configuration" } - - override predicate isSource(TaintTracking::Source source) { source instanceof ErrorInfoSource } - - override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpResponseTaintSink } -} - -from StackTraceExposureConfiguration config, TaintedPathSource src, TaintedPathSink sink -where config.hasFlowPath(src, sink) -select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(), +from StackTraceExposureConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "$@ may be exposed to an external user", source.getNode(), "Error information" diff --git a/python/ql/src/semmle/python/security/dataflow/StackTraceExposure.qll b/python/ql/src/semmle/python/security/dataflow/StackTraceExposure.qll new file mode 100644 index 00000000000..dbfb51aadc4 --- /dev/null +++ b/python/ql/src/semmle/python/security/dataflow/StackTraceExposure.qll @@ -0,0 +1,22 @@ +/** + * Provides a taint-tracking configuration for detecting SQL injection + * vulnerabilities. + */ + +import python +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking +import semmle.python.Concepts + +/** + * A taint-tracking configuration for detecting SQL injection vulnerabilities. + */ +class StackTraceExposureConfiguration extends TaintTracking::Configuration { + StackTraceExposureConfiguration() { this = "StackTraceExposureConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof ErrorInfoSource } + + override predicate isSink(DataFlow::Node sink) { + sink = any(HTTP::Server::HttpResponse response).getBody() + } +} From 232d9b006a815f9fbdc3b007098838219f7660fb Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Wed, 3 Feb 2021 21:45:18 +0100 Subject: [PATCH 054/757] Python: Implement traceback module Just functions, not the classes for now --- .../src/semmle/python/frameworks/Stdlib.qll | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index 5120534d014..ee412357804 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -1654,6 +1654,32 @@ private module Stdlib { class Sqlite3 extends PEP249Module { Sqlite3() { this = sqlite3() } } + + // --------------------------------------------------------------------------- + // traceback + // --------------------------------------------------------------------------- + /** Gets a reference to the `traceback` module. */ + API::Node traceback() { result = API::moduleImport("traceback") } + + /** + * Gets a reference to the attribute `attr_name` of the `traceback` module. + */ + private API::Node traceback_attr(string attr_name) { result = traceback().getMember(attr_name) } + + /** Provides models for the `traceback` module. */ + module traceback { + private class TracebackFunctionCall extends ErrorInfoSource::Range, DataFlow::CfgNode { + override CallNode node; + + TracebackFunctionCall() { + node.getFunction() = + traceback_attr([ + "extract_tb", "extract_stack", "format_list", "format_exception_only", + "format_exception", "format_exc", "format_tb", "format_stack" + ]).getAUse().asCfgNode() + } + } + } } // --------------------------------------------------------------------------- From 0ea2f457a15ead35a1a1afd7db747eddacd0cd0c Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Wed, 3 Feb 2021 21:47:01 +0100 Subject: [PATCH 055/757] Python: type trackers for exceptions --- .../src/semmle/python/frameworks/Stdlib.qll | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index ee412357804..6a8e2c420b0 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -1682,6 +1682,112 @@ private module Stdlib { } } +/** + * Provides models for the `Stdlib.BaseException` class + * + * See https://docs.python.org/3/library/exceptions.html#BaseException. + */ +module BaseException { + /** Gets a reference to the `Stdlib.BaseException` class. */ + private DataFlow::Node classRef(DataFlow::TypeTracker t) { + t.start() and + result.asExpr().(Name).getId() = "BaseException" + or + exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) + } + + /** Gets a reference to the `Stdlib.BaseException` class. */ + DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } + + /** + * A source of an instance of `Stdlib.BaseException`. + * + * This can include instantiation of the class, return value from function + * calls, or a special parameter that will be set when functions are call by external + * library. + * + * Use `BaseException::instance()` predicate to get references to instances of `Stdlib.BaseException`. + */ + abstract class InstanceSource extends DataFlow::Node { } + + /** A direct instantiation of `Stdlib.BaseException`. */ + private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { + override CallNode node; + + ClassInstantiation() { node.getFunction() = classRef().asCfgNode() } + } + + /** Gets a reference to an instance of `Stdlib.BaseException`. */ + private DataFlow::Node instance(DataFlow::TypeTracker t) { + t.start() and + result instanceof InstanceSource + or + exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) + } + + /** Gets a reference to an instance of `Stdlib.BaseException`. */ + DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } +} + +/** + * Provides models for the `BaseException` class and subclasses. + */ +private module Exception { + /** Gets a reference to the `BaseHTTPRequestHandler` class or any subclass. */ + private DataFlow::Node subclassRef(DataFlow::TypeTracker t) { + t.start() and + result = BaseException::classRef() + or + // subclasses in project code + result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr() + or + exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t)) + } + + /** Gets a reference to the `BaseException` class or any subclass. */ + DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) } + + /** A HTTPRequestHandler class definition (most likely in project code). */ + class ExceptionClassDef extends Class { + ExceptionClassDef() { this.getParent() = subclassRef().asExpr() } + } + + /** + * A source of instances of the `BaseException` class or any subclass, extend this class to model new instances. + * + * This can include instantiations of the class, return values from function + * calls, or a special parameter that will be set when functions are called by an external + * library. + * + * Use the predicate `classname::instance()` to get references to instances of the `Exception` class or any subclass. + */ + abstract class InstanceSource extends DataFlow::Node { } + + /** The `self` parameter in a method on the `BaseException` class or any subclass. */ + private class SelfParam extends InstanceSource, DataFlow::ParameterNode { + SelfParam() { exists(ExceptionClassDef cls | cls.getAMethod().getArg(0) = this.getParameter()) } + } + + /** Gets a reference to an instance of the `BaseException` class or any subclass. */ + private DataFlow::Node instance(DataFlow::TypeTracker t) { + t.start() and + result instanceof InstanceSource + or + exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) + } + + /** Gets a reference to an instance of the `BaseException` class or any subclass. */ + DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } +} + +private class Exception extends ExceptionSource::Range { + Exception() { + this = Exception::instance() + or + this.asExpr() = any(ExceptStmt s).getName() + } +} + // --------------------------------------------------------------------------- // OTHER // --------------------------------------------------------------------------- From e3002aa1bf954c31ed789f5524a61e1cdf5792a0 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Fri, 5 Feb 2021 14:53:37 +0100 Subject: [PATCH 056/757] Python: model for `sys.exc_info` made _easy_ by API graphs :D --- python/ql/src/semmle/python/frameworks/Stdlib.qll | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index 6a8e2c420b0..9b969cba99a 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -7,6 +7,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking private import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.ApiGraphs private import semmle.python.Concepts private import PEP249 @@ -1788,6 +1789,11 @@ private class Exception extends ExceptionSource::Range { } } +/** A call to `sys.exc_info` */ +private class SysExcInfoCall extends ErrorInfoSource::Range, DataFlow::CfgNode { + SysExcInfoCall() { this = API::moduleImport("sys").getMember("exc_info").getACall() } +} + // --------------------------------------------------------------------------- // OTHER // --------------------------------------------------------------------------- From af0f361ac8af79120b38af2da4ef1293f3f9637a Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 10 Feb 2021 22:19:45 +0100 Subject: [PATCH 057/757] Updated JexlInjection.ql to check for sandboxes - Added a dataflow config to track setting a sandbox on JexlBuilder - Added SandboxedJexl3.java test --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 64 ++++++++++++++-- .../security/CWE-094/SandboxedJexl3.java | 76 +++++++++++++++++++ .../org/apache/commons/jexl3/JexlBuilder.java | 10 +++ .../org/apache/commons/jexl3/JexlEngine.java | 6 ++ .../jexl3/introspection/JexlSandbox.java | 16 ++++ .../jexl3/introspection/JexlUberspect.java | 37 +++++++++ 6 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl3.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlSandbox.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlUberspect.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 593ed794557..d8ec721ff5a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -47,8 +47,10 @@ private class JexlEvaluationSink extends DataFlow::ExprNode { m instanceof CallableCallMethod and ma.getQualifier() = taintFrom or m instanceof JexlEngineGetSetPropertyMethod and - ma.getAnArgument().getType() instanceof TypeString and - ma.getAnArgument() = taintFrom + exists(Expr arg, int index | arg = ma.getArgument(index) and index = [1, 2] | + arg.getType() instanceof TypeString and + arg = taintFrom + ) ) } } @@ -67,18 +69,21 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess { | m instanceof CreateJexlScriptMethod and taintFromExpr = this.getArgument(0) and - taintType instanceof TypeString + taintType instanceof TypeString and + isUnsafeEngine(this.getQualifier()) or m instanceof CreateJexlCallableMethod and taintFromExpr = this.getQualifier() or m instanceof CreateJexlExpressionMethod and taintFromExpr = this.getAnArgument() and - taintType instanceof TypeString + taintType instanceof TypeString and + isUnsafeEngine(this.getQualifier()) or m instanceof CreateJexlTemplateMethod and (taintType instanceof TypeString or taintType instanceof Reader) and - taintFromExpr = this.getArgument([0, 1]) + taintFromExpr = this.getArgument([0, 1]) and + isUnsafeEngine(this.getQualifier()) ) } @@ -91,6 +96,51 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess { } } +/** + * Holds if `expr` is one of the Jexl engines that is not configured with a sandbox. + */ +private predicate isUnsafeEngine(Expr expr) { + not exists(SandboxedJexlFlowConfig config | config.hasFlowTo(DataFlow::exprNode(expr))) +} + +/** + * A configuration for a tracking sandboxed Jexl engines. + */ +private class SandboxedJexlFlowConfig extends DataFlow2::Configuration { + SandboxedJexlFlowConfig() { this = "JexlInjection::SandboxedJexlFlowConfig" } + + override predicate isSource(DataFlow::Node node) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType() instanceof JexlBuilder and + m.hasName(["uberspect", "sandbox"]) and + m.getReturnType() instanceof JexlBuilder and + (ma = node.asExpr() or ma.getQualifier() = node.asExpr()) + ) + } + + override predicate isSink(DataFlow::Node node) { + node.asExpr().getType() instanceof JexlEngine or + node.asExpr().getType() instanceof JxltEngine or + node.asExpr().getType() instanceof UnifiedJexl + } + + override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + createsJexlEngine(fromNode, toNode) + } +} + +/** + * Holds if `fromNode` to `toNode` is a dataflow step that creates one of the Jexl engines. + */ +private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + (m.getDeclaringType() instanceof JexlBuilder or m.getDeclaringType() instanceof JexlEngine) and + m.hasName(["create", "createJxltEngine"]) and + ma.getQualifier() = fromNode.asExpr() and + ma = toNode.asExpr() + ) +} + /** * Holds if `fromNode` to `toNode` is a dataflow step that returns data from * a tainted bean by calling one of its getters. @@ -190,6 +240,10 @@ private class JexlScript extends JexlRefType { JexlScript() { hasName(["Script", "JexlScript"]) } } +private class JexlBuilder extends JexlRefType { + JexlBuilder() { hasName("JexlBuilder") } +} + private class JexlEngine extends JexlRefType { JexlEngine() { hasName("JexlEngine") } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl3.java b/java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl3.java new file mode 100644 index 00000000000..4c20ac0c901 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl3.java @@ -0,0 +1,76 @@ +import java.net.ServerSocket; +import java.net.Socket; +import java.security.AccessControlException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.commons.jexl3.*; +import org.apache.commons.jexl3.introspection.*; + +public class SandboxedJexl3 { + + private static void runJexlExpressionWithSandbox(String jexlExpr) { + JexlSandbox sandbox = new JexlSandbox(false); + sandbox.white(SandboxedJexl3.class.getCanonicalName()); + JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create(); + JexlExpression e = jexl.createExpression(jexlExpr); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static void runJexlExpressionWithUberspectSandbox(String jexlExpr) { + JexlUberspect sandbox = new JexlUberspectSandbox(); + JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create(); + JexlExpression e = jexl.createExpression(jexlExpr); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static JexlBuilder STATIC_JEXL_BUILDER; + + static { + JexlSandbox sandbox = new JexlSandbox(false); + sandbox.white(SandboxedJexl3.class.getCanonicalName()); + STATIC_JEXL_BUILDER = new JexlBuilder().sandbox(sandbox); + } + + private static void runJexlExpressionViaJxltEngineWithSandbox(String jexlExpr) { + JexlEngine jexl = STATIC_JEXL_BUILDER.create(); + JxltEngine jxlt = jexl.createJxltEngine(); + jxlt.createExpression(jexlExpr).evaluate(new MapContext()); + } + + private static class JexlUberspectSandbox implements JexlUberspect { + + } + + private static void withSocket(Consumer action) throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0)) { + try (Socket socket = serverSocket.accept()) { + byte[] bytes = new byte[1024]; + int n = socket.getInputStream().read(bytes); + String jexlExpr = new String(bytes, 0, n); + action.accept(jexlExpr); + } + } + } + + // below are examples of safer Jexl usage + + // with JexlSandbox + public static void saferJexlExpressionInSandbox() throws Exception { + withSocket(SandboxedJexl3::runJexlExpressionWithSandbox); + } + + // with a custom sandbox implemented with JexlUberspect + public static void saferJexlExpressionInUberspectSandbox() throws Exception { + withSocket(SandboxedJexl3::runJexlExpressionWithUberspectSandbox); + } + + // with JexlSandbox and JxltEngine + public static void saferJxltExpressionInSandbox() throws Exception { + withSocket(SandboxedJexl3::runJexlExpressionViaJxltEngineWithSandbox); + } +} diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java index cba900c25b9..48e805536cb 100644 --- a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlBuilder.java @@ -1,8 +1,18 @@ package org.apache.commons.jexl3; +import org.apache.commons.jexl3.introspection.*; + public class JexlBuilder { public JexlEngine create() { return null; } + + public JexlBuilder sandbox(JexlSandbox sandbox) { + return null; + } + + public JexlBuilder uberspect(JexlUberspect uberspect) { + return null; + } } \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java index e0674da8654..0d39942c427 100644 --- a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java @@ -1,5 +1,7 @@ package org.apache.commons.jexl3; +import org.apache.commons.jexl3.introspection.*; + public abstract class JexlEngine { public JexlExpression createExpression(JexlInfo info, String expression) { @@ -31,4 +33,8 @@ public abstract class JexlEngine { public Object getProperty(Object bean, String expr) { return null; } + + public JexlUberspect getUberspect() { + return null; + } } \ No newline at end of file diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlSandbox.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlSandbox.java new file mode 100644 index 00000000000..ea6720e20e2 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlSandbox.java @@ -0,0 +1,16 @@ +package org.apache.commons.jexl3.introspection; + +public class JexlSandbox { + + public JexlSandbox() {} + + public JexlSandbox(boolean wb) {} + + public JexlSandbox.Permissions white(String clazz) { + return null; + } + + public static final class Permissions { + + } +} diff --git a/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlUberspect.java b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlUberspect.java new file mode 100644 index 00000000000..ac1c8f945da --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/introspection/JexlUberspect.java @@ -0,0 +1,37 @@ +package org.apache.commons.jexl3.introspection; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public interface JexlUberspect { + /* + interface PropertyResolver {} + + List getResolvers(JexlOperator op, Object obj); + + void setClassLoader(ClassLoader loader); + + ClassLoader getClassLoader(); + + int getVersion(); + + JexlMethod getConstructor(Object ctorHandle, Object... args); + + JexlMethod getMethod(Object obj, String method, Object... args); + + JexlPropertyGet getPropertyGet(Object obj, Object identifier); + + JexlPropertyGet getPropertyGet(List resolvers, Object obj, Object identifier); + + JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg); + + JexlPropertySet getPropertySet(List resolvers, Object obj, Object identifier, Object arg); + + Iterator getIterator(Object obj); + + JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic); + */ +} \ No newline at end of file From a6a0fa28c4d8ecddf1c058276c1d448d52722824 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 11 Feb 2021 16:05:48 +0800 Subject: [PATCH 058/757] *)add XQExpression.executeQuery(0) sink --- .../Security/CWE/CWE-652/XQueryInjection.java | 26 ++++++ .../Security/CWE/CWE-652/XQueryInjection.ql | 6 +- .../CWE/CWE-652/XQueryInjectionLib.qll | 31 +++++-- .../security/CWE-652/XQueryInjection.expected | 48 ++++++---- .../security/CWE-652/XQueryInjection.java | 89 ++++++++++++++++++- .../javax/xml/xquery/XQConnection.java | 2 + .../javax/xml/xquery/XQExpression.java | 25 ++++++ .../net/sf/saxon/xqj/SaxonXQConnection.java | 4 + 8 files changed, 202 insertions(+), 29 deletions(-) create mode 100644 java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQExpression.java diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java index a60adb37db2..f00df84968c 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java @@ -20,6 +20,18 @@ public void bad(HttpServletRequest request) throws XQException { } } +public void bad1(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + XQDataSource xqds = new SaxonXQDataSource(); + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + XQResultSequence result = expr.executeQuery(query); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } +} + public void good(HttpServletRequest request) throws XQException { String name = request.getParameter("name"); XQDataSource ds = new SaxonXQDataSource(); @@ -32,4 +44,18 @@ public void good(HttpServletRequest request) throws XQException { while (result.next()){ System.out.println(result.getItemAsString(null)); } +} + +public void good1(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + String query = "declare variable $name as xs:string external;" + + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + XQResultSequence result = expr.executeQuery(query); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } } \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index b1bca496e5c..796df6b68da 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -24,15 +24,15 @@ class XQueryInjectionConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - sink.asExpr() = any(XQueryExecuteCall execute).getPreparedExpression() + sink.asExpr() = any(XQueryPreparedExecuteCall xpec).getPreparedExpression() or + sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() } /** * Conveys taint from the input to a `prepareExpression` call to the returned prepared expression. */ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { - exists(XQueryParserCall parser | - pred.asExpr() = parser.getInput() and succ.asExpr() = parser) + exists(XQueryParserCall parser | pred.asExpr() = parser.getInput() and succ.asExpr() = parser) } } diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index cfdbaaa70da..756caf2cd75 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -20,16 +20,33 @@ class XQueryParserCall extends MethodAccess { } /** A call to `XQPreparedExpression.executeQuery`. */ -class XQueryExecuteCall extends MethodAccess { - XQueryExecuteCall() { - exists(Method m | this.getMethod() = m and - m.hasName("executeQuery") and - m.getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") +class XQueryPreparedExecuteCall extends MethodAccess { + XQueryPreparedExecuteCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("executeQuery") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQPreparedExpression") ) } /** Return this prepared expression. */ Expr getPreparedExpression() { result = this.getQualifier() } } + +/** A call to `XQExpression.executeQuery`. */ +class XQueryExecuteCall extends MethodAccess { + XQueryExecuteCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("executeQuery") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQExpression") + ) + } + + /** Return this execute query argument. */ + Expr getExecuteQueryArgument() { result = this.getArgument(0) } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected index 1ac4289a99c..e2b04e1e020 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected @@ -1,19 +1,35 @@ edges -| XQueryInjection.java:26:37:26:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:27:35:27:38 | xqpe | -| XQueryInjection.java:41:37:41:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:42:35:42:38 | xqpe | -| XQueryInjection.java:53:37:53:64 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:54:35:54:38 | xqpe | -| XQueryInjection.java:66:37:66:62 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:67:35:67:38 | xqpe | +| XQueryInjection.java:42:23:42:50 | getParameter(...) : String | XQueryInjection.java:47:35:47:38 | xqpe | +| XQueryInjection.java:55:23:55:50 | getParameter(...) : String | XQueryInjection.java:60:53:60:57 | query | +| XQueryInjection.java:68:32:68:59 | nameStr : String | XQueryInjection.java:73:35:73:38 | xqpe | +| XQueryInjection.java:80:33:80:60 | nameStr : String | XQueryInjection.java:85:53:85:57 | query | +| XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:97:35:97:38 | xqpe | +| XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:109:53:109:56 | name | +| XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:122:35:122:38 | xqpe | +| XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:135:53:135:54 | br | nodes -| XQueryInjection.java:26:37:26:65 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | -| XQueryInjection.java:27:35:27:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:41:37:41:65 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | -| XQueryInjection.java:42:35:42:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:53:37:53:64 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | -| XQueryInjection.java:54:35:54:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:66:37:66:62 | prepareExpression(...) : XQPreparedExpression | semmle.label | prepareExpression(...) : XQPreparedExpression | -| XQueryInjection.java:67:35:67:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:42:23:42:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:47:35:47:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:55:23:55:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:60:53:60:57 | query | semmle.label | query | +| XQueryInjection.java:68:32:68:59 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:73:35:73:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:80:33:80:60 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:85:53:85:57 | query | semmle.label | query | +| XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:97:35:97:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:109:53:109:56 | name | semmle.label | name | +| XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:122:35:122:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:135:53:135:54 | br | semmle.label | br | #select -| XQueryInjection.java:27:35:27:38 | xqpe | XQueryInjection.java:26:37:26:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:27:35:27:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:26:37:26:65 | prepareExpression(...) | this user input | -| XQueryInjection.java:42:35:42:38 | xqpe | XQueryInjection.java:41:37:41:65 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:42:35:42:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:41:37:41:65 | prepareExpression(...) | this user input | -| XQueryInjection.java:54:35:54:38 | xqpe | XQueryInjection.java:53:37:53:64 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:54:35:54:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:53:37:53:64 | prepareExpression(...) | this user input | -| XQueryInjection.java:67:35:67:38 | xqpe | XQueryInjection.java:66:37:66:62 | prepareExpression(...) : XQPreparedExpression | XQueryInjection.java:67:35:67:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:66:37:66:62 | prepareExpression(...) | this user input | +| XQueryInjection.java:47:35:47:38 | xqpe | XQueryInjection.java:42:23:42:50 | getParameter(...) : String | XQueryInjection.java:47:35:47:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:42:23:42:50 | getParameter(...) | this user input | +| XQueryInjection.java:60:53:60:57 | query | XQueryInjection.java:55:23:55:50 | getParameter(...) : String | XQueryInjection.java:60:53:60:57 | query | XQuery query might include code from $@. | XQueryInjection.java:55:23:55:50 | getParameter(...) | this user input | +| XQueryInjection.java:73:35:73:38 | xqpe | XQueryInjection.java:68:32:68:59 | nameStr : String | XQueryInjection.java:73:35:73:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:68:32:68:59 | nameStr | this user input | +| XQueryInjection.java:85:53:85:57 | query | XQueryInjection.java:80:33:80:60 | nameStr : String | XQueryInjection.java:85:53:85:57 | query | XQuery query might include code from $@. | XQueryInjection.java:80:33:80:60 | nameStr | this user input | +| XQueryInjection.java:97:35:97:38 | xqpe | XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:97:35:97:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:93:28:93:51 | getInputStream(...) | this user input | +| XQueryInjection.java:109:53:109:56 | name | XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:109:53:109:56 | name | XQuery query might include code from $@. | XQueryInjection.java:105:28:105:51 | getInputStream(...) | this user input | +| XQueryInjection.java:122:35:122:38 | xqpe | XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:122:35:122:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:117:28:117:51 | getInputStream(...) | this user input | +| XQueryInjection.java:135:53:135:54 | br | XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:135:53:135:54 | br | XQuery query might include code from $@. | XQueryInjection.java:130:28:130:51 | getInputStream(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java index 48846ae1bca..36f829d81bd 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java @@ -6,6 +6,7 @@ import javax.xml.namespace.QName; import javax.xml.xquery.XQConnection; import javax.xml.xquery.XQDataSource; import javax.xml.xquery.XQException; +import javax.xml.xquery.XQExpression; import javax.xml.xquery.XQItemType; import javax.xml.xquery.XQPreparedExpression; import javax.xml.xquery.XQResultSequence; @@ -17,6 +18,25 @@ import org.springframework.web.bind.annotation.RequestParam; @Controller public class XQueryInjection { + public static void main(String[] args) throws Exception { + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn; + try { + String name = "admin"; + String query = "declare variable $name as xs:string external;" + + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; + conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + XQResultSequence result = expr.executeQuery(query); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } catch (XQException e) { + e.printStackTrace(); + } + } + @RequestMapping public void testRequestbad(HttpServletRequest request) throws Exception { String name = request.getParameter("name"); @@ -28,16 +48,27 @@ public class XQueryInjection { while (result.next()){ System.out.println(result.getItemAsString(null)); } + } + @RequestMapping + public void testRequestbad1(HttpServletRequest request) throws Exception { + String name = request.getParameter("name"); + XQDataSource xqds = new SaxonXQDataSource(); + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + XQResultSequence result = expr.executeQuery(query); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } } @RequestMapping public void testStringtbad(@RequestParam String nameStr) throws XQException { - String name = nameStr; XQDataSource ds = new SaxonXQDataSource(); XQConnection conn = ds.getConnection(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + "'] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); XQResultSequence result = xqpe.executeQuery(); while (result.next()){ @@ -45,6 +76,18 @@ public class XQueryInjection { } } + @RequestMapping + public void testStringtbad1(@RequestParam String nameStr) throws XQException { + XQDataSource xqds = new SaxonXQDataSource(); + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + "'] return $user/password"; + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + XQResultSequence result = expr.executeQuery(query); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } + @RequestMapping public void testInputStreambad(HttpServletRequest request) throws Exception { InputStream name = request.getInputStream(); @@ -57,6 +100,18 @@ public class XQueryInjection { } } + @RequestMapping + public void testInputStreambad1(HttpServletRequest request) throws Exception { + InputStream name = request.getInputStream(); + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + XQResultSequence result = expr.executeQuery(name); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } + @RequestMapping public void testReaderbad(HttpServletRequest request) throws Exception { InputStream name = request.getInputStream(); @@ -70,6 +125,19 @@ public class XQueryInjection { } } + @RequestMapping + public void testReaderbad1(HttpServletRequest request) throws Exception { + InputStream name = request.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(name)); + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + XQResultSequence result = expr.executeQuery(br); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } + @RequestMapping public void good(HttpServletRequest request) throws XQException { String name = request.getParameter("name"); @@ -84,4 +152,19 @@ public class XQueryInjection { System.out.println(result.getItemAsString(null)); } } -} + + @RequestMapping + public void good1(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + String query = "declare variable $name as xs:string external;" + + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + XQResultSequence result = expr.executeQuery(query); + while (result.next()){ + System.out.println(result.getItemAsString(null)); + } + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java index 3cff7592168..dce756e9646 100644 --- a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQConnection.java @@ -4,6 +4,8 @@ import java.io.InputStream; import java.io.Reader; public interface XQConnection extends XQDataFactory { + + XQExpression createExpression() throws XQException; XQPreparedExpression prepareExpression(String var1) throws XQException; diff --git a/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQExpression.java b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQExpression.java new file mode 100644 index 00000000000..5f2e9235bc5 --- /dev/null +++ b/java/ql/test/stubs/saxon-xqj-9.x/javax/xml/xquery/XQExpression.java @@ -0,0 +1,25 @@ +package javax.xml.xquery; + +import java.io.InputStream; +import java.io.Reader; + +public interface XQExpression extends XQDynamicContext { + + void cancel() throws XQException; + + boolean isClosed(); + + void close() throws XQException; + + void executeCommand(String var1) throws XQException; + + void executeCommand(Reader var1) throws XQException; + + XQResultSequence executeQuery(String var1) throws XQException; + + XQResultSequence executeQuery(Reader var1) throws XQException; + + XQResultSequence executeQuery(InputStream var1) throws XQException; + + XQStaticContext getStaticContext() throws XQException; +} \ No newline at end of file diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java index 85bae6e7540..94bd38fa175 100644 --- a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java @@ -15,6 +15,10 @@ public class SaxonXQConnection extends SaxonXQDataFactory implements XQConnecti SaxonXQConnection(SaxonXQDataSource dataSource) { } + public XQExpression createExpression() throws XQException { + return null; + } + public XQPreparedExpression prepareExpression(InputStream xquery) throws XQException { return null; } From 7543df60da931fde7fc63531b6167f7098b8ab54 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 11 Feb 2021 20:37:23 +0100 Subject: [PATCH 059/757] Callable.call() should not be a sink in JexlInjection.ql --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 61 +++++++------------ .../security/CWE-094/JexlInjection.expected | 18 +++--- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index d8ec721ff5a..7cb791dbbe4 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -36,6 +36,11 @@ private class TaintedSpringRequestBody extends DataFlow::Node { /** * A sink for Expresssion Language injection vulnerabilities via Jexl, * i.e. method calls that run evaluation of a Jexl expression. + * + * Creating a `Callable` from a tainted Jexl expression or script is considered as a sink + * although the tainted expression is not executed at this point. + * Here we assume that it will get executed at some point, + * maybe stored in an object field and then reached by a different flow. */ private class JexlEvaluationSink extends DataFlow::ExprNode { JexlEvaluationSink() { @@ -44,13 +49,11 @@ private class JexlEvaluationSink extends DataFlow::ExprNode { | m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom or - m instanceof CallableCallMethod and ma.getQualifier() = taintFrom + m instanceof CreateJexlCallableMethod and ma.getQualifier() = taintFrom or m instanceof JexlEngineGetSetPropertyMethod and - exists(Expr arg, int index | arg = ma.getArgument(index) and index = [1, 2] | - arg.getType() instanceof TypeString and - arg = taintFrom - ) + taintFrom.getType() instanceof TypeString and + ma.getAnArgument() = taintFrom ) } } @@ -67,23 +70,20 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess { this.getMethod() = m and taintType = taintFromExpr.getType() | - m instanceof CreateJexlScriptMethod and - taintFromExpr = this.getArgument(0) and - taintType instanceof TypeString and - isUnsafeEngine(this.getQualifier()) - or - m instanceof CreateJexlCallableMethod and - taintFromExpr = this.getQualifier() - or - m instanceof CreateJexlExpressionMethod and - taintFromExpr = this.getAnArgument() and - taintType instanceof TypeString and - isUnsafeEngine(this.getQualifier()) - or - m instanceof CreateJexlTemplateMethod and - (taintType instanceof TypeString or taintType instanceof Reader) and - taintFromExpr = this.getArgument([0, 1]) and - isUnsafeEngine(this.getQualifier()) + isUnsafeEngine(this.getQualifier()) and + ( + m instanceof CreateJexlScriptMethod and + taintFromExpr = this.getArgument(0) and + taintType instanceof TypeString + or + m instanceof CreateJexlExpressionMethod and + taintFromExpr = this.getAnArgument() and + taintType instanceof TypeString + or + m instanceof CreateJexlTemplateMethod and + (taintType instanceof TypeString or taintType instanceof Reader) and + taintFromExpr = this.getArgument([0, 1]) + ) ) } @@ -97,7 +97,7 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess { } /** - * Holds if `expr` is one of the Jexl engines that is not configured with a sandbox. + * Holds if `expr` is a Jexl engine that is not configured with a sandbox. */ private predicate isUnsafeEngine(Expr expr) { not exists(SandboxedJexlFlowConfig config | config.hasFlowTo(DataFlow::exprNode(expr))) @@ -182,13 +182,6 @@ private class DirectJexlEvaluationMethod extends Method { } } -/** - * A method in the `Callable` class that executes the `Callable`. - */ -private class CallableCallMethod extends Method { - CallableCallMethod() { getDeclaringType() instanceof CallableInterface and hasName("call") } -} - /** * Defines methods that create a Jexl script. */ @@ -272,14 +265,6 @@ private class UnifiedJexlTemplate extends NestedType { UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") } } -private class CallableInterface extends RefType { - CallableInterface() { - getSourceDeclaration() - .getASourceSupertype*() - .hasQualifiedName("java.util.concurrent", "Callable") - } -} - private class Reader extends RefType { Reader() { hasQualifiedName("java.io", "Reader") } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected index 0fc01073a43..e0f624704f7 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JexlInjection.expected @@ -2,7 +2,7 @@ edges | Jexl2Injection.java:10:43:10:57 | jexlExpr : String | Jexl2Injection.java:14:9:14:9 | e | | Jexl2Injection.java:17:55:17:69 | jexlExpr : String | Jexl2Injection.java:22:9:22:9 | e | | Jexl2Injection.java:25:39:25:53 | jexlExpr : String | Jexl2Injection.java:29:9:29:14 | script | -| Jexl2Injection.java:32:50:32:64 | jexlExpr : String | Jexl2Injection.java:38:13:38:31 | callable(...) | +| Jexl2Injection.java:32:50:32:64 | jexlExpr : String | Jexl2Injection.java:38:13:38:18 | script | | Jexl2Injection.java:44:57:44:71 | jexlExpr : String | Jexl2Injection.java:46:40:46:47 | jexlExpr | | Jexl2Injection.java:49:57:49:71 | jexlExpr : String | Jexl2Injection.java:51:40:51:47 | jexlExpr | | Jexl2Injection.java:54:73:54:87 | jexlExpr : String | Jexl2Injection.java:57:9:57:35 | parse(...) | @@ -39,13 +39,13 @@ edges | Jexl3Injection.java:15:43:15:57 | jexlExpr : String | Jexl3Injection.java:19:9:19:9 | e | | Jexl3Injection.java:22:55:22:69 | jexlExpr : String | Jexl3Injection.java:26:9:26:9 | e | | Jexl3Injection.java:29:39:29:53 | jexlExpr : String | Jexl3Injection.java:33:9:33:14 | script | -| Jexl3Injection.java:36:50:36:64 | jexlExpr : String | Jexl3Injection.java:42:13:42:31 | callable(...) | +| Jexl3Injection.java:36:50:36:64 | jexlExpr : String | Jexl3Injection.java:42:13:42:18 | script | | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | Jexl3Injection.java:50:40:50:47 | jexlExpr | | Jexl3Injection.java:53:57:53:71 | jexlExpr : String | Jexl3Injection.java:55:40:55:47 | jexlExpr | | Jexl3Injection.java:58:74:58:88 | jexlExpr : String | Jexl3Injection.java:61:9:61:39 | createExpression(...) | | Jexl3Injection.java:64:73:64:87 | jexlExpr : String | Jexl3Injection.java:67:9:67:39 | createExpression(...) | | Jexl3Injection.java:70:72:70:86 | jexlExpr : String | Jexl3Injection.java:73:9:73:37 | createTemplate(...) | -| Jexl3Injection.java:76:54:76:68 | jexlExpr : String | Jexl3Injection.java:82:13:82:26 | callable(...) | +| Jexl3Injection.java:76:54:76:68 | jexlExpr : String | Jexl3Injection.java:82:13:82:13 | e | | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:94:31:94:38 | jexlExpr : String | | Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:102:24:102:56 | jexlExpr : String | | Jexl3Injection.java:94:31:94:38 | jexlExpr : String | Jexl3Injection.java:106:24:106:68 | jexlExpr : String | @@ -91,7 +91,7 @@ nodes | Jexl2Injection.java:25:39:25:53 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl2Injection.java:29:9:29:14 | script | semmle.label | script | | Jexl2Injection.java:32:50:32:64 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl2Injection.java:38:13:38:31 | callable(...) | semmle.label | callable(...) | +| Jexl2Injection.java:38:13:38:18 | script | semmle.label | script | | Jexl2Injection.java:44:57:44:71 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl2Injection.java:46:40:46:47 | jexlExpr | semmle.label | jexlExpr | | Jexl2Injection.java:49:57:49:71 | jexlExpr : String | semmle.label | jexlExpr : String | @@ -129,7 +129,7 @@ nodes | Jexl3Injection.java:29:39:29:53 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl3Injection.java:33:9:33:14 | script | semmle.label | script | | Jexl3Injection.java:36:50:36:64 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:42:13:42:31 | callable(...) | semmle.label | callable(...) | +| Jexl3Injection.java:42:13:42:18 | script | semmle.label | script | | Jexl3Injection.java:48:57:48:71 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl3Injection.java:50:40:50:47 | jexlExpr | semmle.label | jexlExpr | | Jexl3Injection.java:53:57:53:71 | jexlExpr : String | semmle.label | jexlExpr : String | @@ -141,7 +141,7 @@ nodes | Jexl3Injection.java:70:72:70:86 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl3Injection.java:73:9:73:37 | createTemplate(...) | semmle.label | createTemplate(...) | | Jexl3Injection.java:76:54:76:68 | jexlExpr : String | semmle.label | jexlExpr : String | -| Jexl3Injection.java:82:13:82:26 | callable(...) | semmle.label | callable(...) | +| Jexl3Injection.java:82:13:82:13 | e | semmle.label | e | | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | | Jexl3Injection.java:94:31:94:38 | jexlExpr : String | semmle.label | jexlExpr : String | | Jexl3Injection.java:102:24:102:56 | jexlExpr : String | semmle.label | jexlExpr : String | @@ -174,7 +174,7 @@ nodes | Jexl2Injection.java:14:9:14:9 | e | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:14:9:14:9 | e | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:22:9:22:9 | e | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:22:9:22:9 | e | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:29:9:29:14 | script | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:29:9:29:14 | script | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | -| Jexl2Injection.java:38:13:38:31 | callable(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:38:13:38:31 | callable(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | +| Jexl2Injection.java:38:13:38:18 | script | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:38:13:38:18 | script | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:46:40:46:47 | jexlExpr | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:46:40:46:47 | jexlExpr | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:51:40:51:47 | jexlExpr | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:51:40:51:47 | jexlExpr | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | | Jexl2Injection.java:57:9:57:35 | parse(...) | Jexl2Injection.java:76:25:76:47 | getInputStream(...) : InputStream | Jexl2Injection.java:57:9:57:35 | parse(...) | Jexl injection from $@. | Jexl2Injection.java:76:25:76:47 | getInputStream(...) | this user input | @@ -186,10 +186,10 @@ nodes | Jexl3Injection.java:19:9:19:9 | e | Jexl3Injection.java:161:13:161:52 | customRequest : CustomRequest | Jexl3Injection.java:19:9:19:9 | e | Jexl injection from $@. | Jexl3Injection.java:161:13:161:52 | customRequest | this user input | | Jexl3Injection.java:26:9:26:9 | e | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:26:9:26:9 | e | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | | Jexl3Injection.java:33:9:33:14 | script | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:33:9:33:14 | script | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:42:13:42:31 | callable(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:42:13:42:31 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:42:13:42:18 | script | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:42:13:42:18 | script | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | | Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:50:40:50:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | | Jexl3Injection.java:55:40:55:47 | jexlExpr | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:55:40:55:47 | jexlExpr | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | | Jexl3Injection.java:61:9:61:39 | createExpression(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:61:9:61:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | | Jexl3Injection.java:67:9:67:39 | createExpression(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:67:9:67:39 | createExpression(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | | Jexl3Injection.java:73:9:73:37 | createTemplate(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:73:9:73:37 | createTemplate(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | -| Jexl3Injection.java:82:13:82:26 | callable(...) | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:82:13:82:26 | callable(...) | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | +| Jexl3Injection.java:82:13:82:13 | e | Jexl3Injection.java:92:25:92:47 | getInputStream(...) : InputStream | Jexl3Injection.java:82:13:82:13 | e | Jexl injection from $@. | Jexl3Injection.java:92:25:92:47 | getInputStream(...) | this user input | From 042c0b005e94671f1726fa7f9a9ef7a1a8f0483e Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 11 Feb 2021 22:57:26 +0100 Subject: [PATCH 060/757] Covered sandboxes for JEXL 2 - Updated SandboxedJexlFlowConfig to cover JEXL 2 - Added SandboxedJexl2 test --- .../Security/CWE/CWE-094/JexlInjectionLib.qll | 42 +++++++++++++---- .../security/CWE-094/SandboxedJexl2.java | 47 +++++++++++++++++++ .../org/apache/commons/jexl2/JexlEngine.java | 7 +++ .../commons/jexl2/introspection/Sandbox.java | 10 ++++ .../introspection/SandboxUberspectImpl.java | 6 +++ .../jexl2/introspection/Uberspect.java | 3 ++ 6 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl2.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Sandbox.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java create mode 100644 java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Uberspect.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll index 7cb791dbbe4..6d9c9bff118 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -109,14 +109,7 @@ private predicate isUnsafeEngine(Expr expr) { private class SandboxedJexlFlowConfig extends DataFlow2::Configuration { SandboxedJexlFlowConfig() { this = "JexlInjection::SandboxedJexlFlowConfig" } - override predicate isSource(DataFlow::Node node) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.getDeclaringType() instanceof JexlBuilder and - m.hasName(["uberspect", "sandbox"]) and - m.getReturnType() instanceof JexlBuilder and - (ma = node.asExpr() or ma.getQualifier() = node.asExpr()) - ) - } + override predicate isSource(DataFlow::Node node) { node instanceof SandboxedJexlSource } override predicate isSink(DataFlow::Node node) { node.asExpr().getType() instanceof JexlEngine or @@ -129,6 +122,26 @@ private class SandboxedJexlFlowConfig extends DataFlow2::Configuration { } } +/** + * Defines a data flow source for Jexl engines configured with a sandbox. + */ +private class SandboxedJexlSource extends DataFlow::ExprNode { + SandboxedJexlSource() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType() instanceof JexlBuilder and + m.hasName(["uberspect", "sandbox"]) and + m.getReturnType() instanceof JexlBuilder and + (ma = this.asExpr() or ma.getQualifier() = this.asExpr()) + ) + or + exists(ConstructorCall cc | + cc.getConstructedType() instanceof JexlEngine and + cc.getArgument(0).getType() instanceof JexlUberspect and + cc = this.asExpr() + ) + } +} + /** * Holds if `fromNode` to `toNode` is a dataflow step that creates one of the Jexl engines. */ @@ -139,6 +152,12 @@ private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNo ma.getQualifier() = fromNode.asExpr() and ma = toNode.asExpr() ) + or + exists(ConstructorCall cc | + cc.getConstructedType() instanceof UnifiedJexl and + cc.getArgument(0) = fromNode.asExpr() and + cc = toNode.asExpr() + ) } /** @@ -249,6 +268,13 @@ private class UnifiedJexl extends JexlRefType { UnifiedJexl() { hasName("UnifiedJEXL") } } +private class JexlUberspect extends Interface { + JexlUberspect() { + hasQualifiedName("org.apache.commons.jexl2.introspection", "Uberspect") or + hasQualifiedName("org.apache.commons.jexl3.introspection", "JexlUberspect") + } +} + private class JxltEngineExpression extends NestedType { JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl2.java b/java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl2.java new file mode 100644 index 00000000000..dd9b113ca0b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/SandboxedJexl2.java @@ -0,0 +1,47 @@ +import org.apache.commons.jexl2.*; +import org.apache.commons.jexl2.introspection.*; + +import java.net.ServerSocket; +import java.net.Socket; +import java.util.function.Consumer; + +public class SandboxedJexl2 { + + private static void runJexlExpressionWithSandbox(String jexlExpr) { + Sandbox sandbox = new Sandbox(); + sandbox.white(SandboxedJexl2.class.getCanonicalName()); + Uberspect uberspect = new SandboxUberspectImpl(null, sandbox); + JexlEngine jexl = new JexlEngine(uberspect, null, null, null); + Expression e = jexl.createExpression(jexlExpr); + JexlContext jc = new MapContext(); + e.evaluate(jc); + } + + private static void runJexlExpressionViaSandboxedUnifiedJexl(String jexlExpr) { + Sandbox sandbox = new Sandbox(); + sandbox.white(SandboxedJexl2.class.getCanonicalName()); + Uberspect uberspect = new SandboxUberspectImpl(null, sandbox); + JexlEngine jexl = new JexlEngine(uberspect, null, null, null); + UnifiedJEXL unifiedJEXL = new UnifiedJEXL(jexl); + unifiedJEXL.parse(jexlExpr).evaluate(new MapContext()); + } + + private static void simpleServer(Consumer action) throws Exception { + try (ServerSocket serverSocket = new ServerSocket(0)) { + try (Socket socket = serverSocket.accept()) { + byte[] bytes = new byte[1024]; + int n = socket.getInputStream().read(bytes); + String jexlExpr = new String(bytes, 0, n); + action.accept(jexlExpr); + } + } + } + + public static void saferJexlExpressionEvaluate() throws Exception { + simpleServer(SandboxedJexl2::runJexlExpressionWithSandbox); + } + + public static void saferJexlExpressionEvaluateViaUnifiedJexl() throws Exception { + simpleServer(SandboxedJexl2::runJexlExpressionViaSandboxedUnifiedJexl); + } +} diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java index 5d49850fe7c..38568b08ac0 100644 --- a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/JexlEngine.java @@ -1,7 +1,14 @@ package org.apache.commons.jexl2; +import java.util.Map; +import org.apache.commons.jexl2.introspection.*; + public class JexlEngine { + public JexlEngine() {} + + public JexlEngine(Uberspect uberspect, Object arithmetic, Map functions, Object log) {} + public Expression createExpression(String expression) { return null; } diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Sandbox.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Sandbox.java new file mode 100644 index 00000000000..c711281210a --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Sandbox.java @@ -0,0 +1,10 @@ +package org.apache.commons.jexl2.introspection; + +public class Sandbox { + + public Permissions white(String clazz) { + return null; + } + + public static class Permissions {} +} diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java new file mode 100644 index 00000000000..6348583530d --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java @@ -0,0 +1,6 @@ +package org.apache.commons.jexl2.introspection; + +public class SandboxUberspectImpl implements Uberspect { + + public SandboxUberspectImpl(Object log, Sandbox sandbox) {} +} diff --git a/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Uberspect.java b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Uberspect.java new file mode 100644 index 00000000000..94bdd21c058 --- /dev/null +++ b/java/ql/test/stubs/apache-commons-jexl-2.1.1/org/apache/commons/jexl2/introspection/Uberspect.java @@ -0,0 +1,3 @@ +package org.apache.commons.jexl2.introspection; + +public interface Uberspect {} From dbb3d458f5488e3d5e71098325f0bfa933bd3d03 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 12 Feb 2021 10:47:41 +0800 Subject: [PATCH 061/757] *)add XQExpression.executeCommand(0) sink --- .../Security/CWE/CWE-652/XQueryInjection.java | 9 +++ .../Security/CWE/CWE-652/XQueryInjection.ql | 3 +- .../CWE/CWE-652/XQueryInjectionLib.qll | 16 +++++ .../security/CWE-652/XQueryInjection.expected | 72 ++++++++++--------- .../security/CWE-652/XQueryInjection.java | 63 +++++++++++----- .../net/sf/saxon/xqj/SaxonXQConnection.java | 1 + 6 files changed, 112 insertions(+), 52 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java index f00df84968c..1b6be030249 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java @@ -32,6 +32,15 @@ public void bad1(HttpServletRequest request) throws XQException { } } +public void bad2(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + //bad code + expr.executeCommand(name); +} + public void good(HttpServletRequest request) throws XQException { String name = request.getParameter("name"); XQDataSource ds = new SaxonXQDataSource(); diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index 796df6b68da..69617cad629 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -25,7 +25,8 @@ class XQueryInjectionConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(XQueryPreparedExecuteCall xpec).getPreparedExpression() or - sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() + sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() or + sink.asExpr() = any(XQueryExecuteCommandCall xecc).getExecuteCommandArgument() } /** diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index 756caf2cd75..2a4019f2c9a 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -50,3 +50,19 @@ class XQueryExecuteCall extends MethodAccess { /** Return this execute query argument. */ Expr getExecuteQueryArgument() { result = this.getArgument(0) } } + +/** A call to `XQExpression.executeCommand`. */ +class XQueryExecuteCommandCall extends MethodAccess { + XQueryExecuteCommandCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("executeCommand") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQExpression") + ) + } + + /** Return this execute command argument. */ + Expr getExecuteCommandArgument() { result = this.getArgument(0) } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected index e2b04e1e020..b3ae0ff90fb 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected @@ -1,35 +1,43 @@ edges -| XQueryInjection.java:42:23:42:50 | getParameter(...) : String | XQueryInjection.java:47:35:47:38 | xqpe | -| XQueryInjection.java:55:23:55:50 | getParameter(...) : String | XQueryInjection.java:60:53:60:57 | query | -| XQueryInjection.java:68:32:68:59 | nameStr : String | XQueryInjection.java:73:35:73:38 | xqpe | -| XQueryInjection.java:80:33:80:60 | nameStr : String | XQueryInjection.java:85:53:85:57 | query | -| XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:97:35:97:38 | xqpe | -| XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:109:53:109:56 | name | -| XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:122:35:122:38 | xqpe | -| XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:135:53:135:54 | br | +| XQueryInjection.java:45:23:45:50 | getParameter(...) : String | XQueryInjection.java:51:35:51:38 | xqpe | +| XQueryInjection.java:59:23:59:50 | getParameter(...) : String | XQueryInjection.java:65:53:65:57 | query | +| XQueryInjection.java:73:32:73:59 | nameStr : String | XQueryInjection.java:79:35:79:38 | xqpe | +| XQueryInjection.java:86:33:86:60 | nameStr : String | XQueryInjection.java:92:53:92:57 | query | +| XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:104:35:104:38 | xqpe | +| XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:116:53:116:56 | name | +| XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:129:35:129:38 | xqpe | +| XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:142:53:142:54 | br | +| XQueryInjection.java:150:23:150:50 | getParameter(...) : String | XQueryInjection.java:155:29:155:32 | name | +| XQueryInjection.java:157:26:157:49 | getInputStream(...) : ServletInputStream | XQueryInjection.java:159:29:159:30 | br | nodes -| XQueryInjection.java:42:23:42:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| XQueryInjection.java:47:35:47:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:55:23:55:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| XQueryInjection.java:60:53:60:57 | query | semmle.label | query | -| XQueryInjection.java:68:32:68:59 | nameStr : String | semmle.label | nameStr : String | -| XQueryInjection.java:73:35:73:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:80:33:80:60 | nameStr : String | semmle.label | nameStr : String | -| XQueryInjection.java:85:53:85:57 | query | semmle.label | query | -| XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:97:35:97:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:109:53:109:56 | name | semmle.label | name | -| XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:122:35:122:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:135:53:135:54 | br | semmle.label | br | +| XQueryInjection.java:45:23:45:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:51:35:51:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:59:23:59:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:65:53:65:57 | query | semmle.label | query | +| XQueryInjection.java:73:32:73:59 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:79:35:79:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:86:33:86:60 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:92:53:92:57 | query | semmle.label | query | +| XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:104:35:104:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:116:53:116:56 | name | semmle.label | name | +| XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:129:35:129:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:142:53:142:54 | br | semmle.label | br | +| XQueryInjection.java:150:23:150:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:155:29:155:32 | name | semmle.label | name | +| XQueryInjection.java:157:26:157:49 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:159:29:159:30 | br | semmle.label | br | #select -| XQueryInjection.java:47:35:47:38 | xqpe | XQueryInjection.java:42:23:42:50 | getParameter(...) : String | XQueryInjection.java:47:35:47:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:42:23:42:50 | getParameter(...) | this user input | -| XQueryInjection.java:60:53:60:57 | query | XQueryInjection.java:55:23:55:50 | getParameter(...) : String | XQueryInjection.java:60:53:60:57 | query | XQuery query might include code from $@. | XQueryInjection.java:55:23:55:50 | getParameter(...) | this user input | -| XQueryInjection.java:73:35:73:38 | xqpe | XQueryInjection.java:68:32:68:59 | nameStr : String | XQueryInjection.java:73:35:73:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:68:32:68:59 | nameStr | this user input | -| XQueryInjection.java:85:53:85:57 | query | XQueryInjection.java:80:33:80:60 | nameStr : String | XQueryInjection.java:85:53:85:57 | query | XQuery query might include code from $@. | XQueryInjection.java:80:33:80:60 | nameStr | this user input | -| XQueryInjection.java:97:35:97:38 | xqpe | XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:97:35:97:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:93:28:93:51 | getInputStream(...) | this user input | -| XQueryInjection.java:109:53:109:56 | name | XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:109:53:109:56 | name | XQuery query might include code from $@. | XQueryInjection.java:105:28:105:51 | getInputStream(...) | this user input | -| XQueryInjection.java:122:35:122:38 | xqpe | XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:122:35:122:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:117:28:117:51 | getInputStream(...) | this user input | -| XQueryInjection.java:135:53:135:54 | br | XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:135:53:135:54 | br | XQuery query might include code from $@. | XQueryInjection.java:130:28:130:51 | getInputStream(...) | this user input | +| XQueryInjection.java:51:35:51:38 | xqpe | XQueryInjection.java:45:23:45:50 | getParameter(...) : String | XQueryInjection.java:51:35:51:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:45:23:45:50 | getParameter(...) | this user input | +| XQueryInjection.java:65:53:65:57 | query | XQueryInjection.java:59:23:59:50 | getParameter(...) : String | XQueryInjection.java:65:53:65:57 | query | XQuery query might include code from $@. | XQueryInjection.java:59:23:59:50 | getParameter(...) | this user input | +| XQueryInjection.java:79:35:79:38 | xqpe | XQueryInjection.java:73:32:73:59 | nameStr : String | XQueryInjection.java:79:35:79:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:73:32:73:59 | nameStr | this user input | +| XQueryInjection.java:92:53:92:57 | query | XQueryInjection.java:86:33:86:60 | nameStr : String | XQueryInjection.java:92:53:92:57 | query | XQuery query might include code from $@. | XQueryInjection.java:86:33:86:60 | nameStr | this user input | +| XQueryInjection.java:104:35:104:38 | xqpe | XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:104:35:104:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:100:28:100:51 | getInputStream(...) | this user input | +| XQueryInjection.java:116:53:116:56 | name | XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:116:53:116:56 | name | XQuery query might include code from $@. | XQueryInjection.java:112:28:112:51 | getInputStream(...) | this user input | +| XQueryInjection.java:129:35:129:38 | xqpe | XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:129:35:129:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:124:28:124:51 | getInputStream(...) | this user input | +| XQueryInjection.java:142:53:142:54 | br | XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:142:53:142:54 | br | XQuery query might include code from $@. | XQueryInjection.java:137:28:137:51 | getInputStream(...) | this user input | +| XQueryInjection.java:155:29:155:32 | name | XQueryInjection.java:150:23:150:50 | getParameter(...) : String | XQueryInjection.java:155:29:155:32 | name | XQuery query might include code from $@. | XQueryInjection.java:150:23:150:50 | getParameter(...) | this user input | +| XQueryInjection.java:159:29:159:30 | br | XQueryInjection.java:157:26:157:49 | getInputStream(...) : ServletInputStream | XQueryInjection.java:159:29:159:30 | br | XQuery query might include code from $@. | XQueryInjection.java:157:26:157:49 | getInputStream(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java index 36f829d81bd..d8df8057cc6 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java @@ -1,3 +1,5 @@ +package com.vuln.v2.controller; + import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; @@ -27,9 +29,10 @@ public class XQueryInjection { + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); - expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + expr.bindString(new QName("name"), name, + conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } catch (XQException e) { @@ -42,10 +45,11 @@ public class XQueryInjection { String name = request.getParameter("name"); XQDataSource ds = new SaxonXQDataSource(); XQConnection conn = ds.getConnection(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + + "'] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -54,11 +58,12 @@ public class XQueryInjection { public void testRequestbad1(HttpServletRequest request) throws Exception { String name = request.getParameter("name"); XQDataSource xqds = new SaxonXQDataSource(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + + "'] return $user/password"; XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -68,10 +73,11 @@ public class XQueryInjection { public void testStringtbad(@RequestParam String nameStr) throws XQException { XQDataSource ds = new SaxonXQDataSource(); XQConnection conn = ds.getConnection(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + + "'] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -79,11 +85,12 @@ public class XQueryInjection { @RequestMapping public void testStringtbad1(@RequestParam String nameStr) throws XQException { XQDataSource xqds = new SaxonXQDataSource(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + + "'] return $user/password"; XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -95,7 +102,7 @@ public class XQueryInjection { XQConnection conn = ds.getConnection(); XQPreparedExpression xqpe = conn.prepareExpression(name); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -107,7 +114,7 @@ public class XQueryInjection { XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(name); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -120,7 +127,7 @@ public class XQueryInjection { XQConnection conn = ds.getConnection(); XQPreparedExpression xqpe = conn.prepareExpression(br); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -133,11 +140,26 @@ public class XQueryInjection { XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(br); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } + @RequestMapping + public void testExecuteCommandbad(HttpServletRequest request) throws Exception { + String name = request.getParameter("name"); + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + //bad code + expr.executeCommand(name); + //bad code + InputStream is = request.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + expr.executeCommand(br); + expr.close(); + } + @RequestMapping public void good(HttpServletRequest request) throws XQException { String name = request.getParameter("name"); @@ -146,9 +168,10 @@ public class XQueryInjection { String query = "declare variable $name as xs:string external;" + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); - xqpe.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + xqpe.bindString(new QName("name"), name, + conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -161,10 +184,12 @@ public class XQueryInjection { XQDataSource xqds = new SaxonXQDataSource(); XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); - expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + expr.bindString(new QName("name"), name, + conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } -} \ No newline at end of file +} + diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java index 94bd38fa175..0263588e8b4 100644 --- a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java @@ -5,6 +5,7 @@ import net.sf.saxon.Configuration; import javax.xml.xquery.XQConnection; import javax.xml.xquery.XQPreparedExpression; import javax.xml.xquery.XQException; +import javax.xml.xquery.XQExpression; import javax.xml.xquery.XQStaticContext; import java.io.InputStream; From 22e741c7a3ae86332d48df3ba64f964e181a9569 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 12 Feb 2021 11:17:42 +0800 Subject: [PATCH 062/757] *)add XQExpression.executeCommand(0) sink --- .../Security/CWE/CWE-652/XQueryInjection.java | 9 +++ .../Security/CWE/CWE-652/XQueryInjection.ql | 3 +- .../CWE/CWE-652/XQueryInjectionLib.qll | 16 +++++ .../security/CWE-652/XQueryInjection.expected | 72 ++++++++++--------- .../security/CWE-652/XQueryInjection.java | 63 +++++++++++----- .../net/sf/saxon/xqj/SaxonXQConnection.java | 1 + 6 files changed, 112 insertions(+), 52 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java index f00df84968c..1b6be030249 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.java @@ -32,6 +32,15 @@ public void bad1(HttpServletRequest request) throws XQException { } } +public void bad2(HttpServletRequest request) throws XQException { + String name = request.getParameter("name"); + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + //bad code + expr.executeCommand(name); +} + public void good(HttpServletRequest request) throws XQException { String name = request.getParameter("name"); XQDataSource ds = new SaxonXQDataSource(); diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index 796df6b68da..69617cad629 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -25,7 +25,8 @@ class XQueryInjectionConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(XQueryPreparedExecuteCall xpec).getPreparedExpression() or - sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() + sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() or + sink.asExpr() = any(XQueryExecuteCommandCall xecc).getExecuteCommandArgument() } /** diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll index 756caf2cd75..2a4019f2c9a 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjectionLib.qll @@ -50,3 +50,19 @@ class XQueryExecuteCall extends MethodAccess { /** Return this execute query argument. */ Expr getExecuteQueryArgument() { result = this.getArgument(0) } } + +/** A call to `XQExpression.executeCommand`. */ +class XQueryExecuteCommandCall extends MethodAccess { + XQueryExecuteCommandCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("executeCommand") and + m.getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("javax.xml.xquery", "XQExpression") + ) + } + + /** Return this execute command argument. */ + Expr getExecuteCommandArgument() { result = this.getArgument(0) } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected index e2b04e1e020..b3ae0ff90fb 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.expected @@ -1,35 +1,43 @@ edges -| XQueryInjection.java:42:23:42:50 | getParameter(...) : String | XQueryInjection.java:47:35:47:38 | xqpe | -| XQueryInjection.java:55:23:55:50 | getParameter(...) : String | XQueryInjection.java:60:53:60:57 | query | -| XQueryInjection.java:68:32:68:59 | nameStr : String | XQueryInjection.java:73:35:73:38 | xqpe | -| XQueryInjection.java:80:33:80:60 | nameStr : String | XQueryInjection.java:85:53:85:57 | query | -| XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:97:35:97:38 | xqpe | -| XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:109:53:109:56 | name | -| XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:122:35:122:38 | xqpe | -| XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:135:53:135:54 | br | +| XQueryInjection.java:45:23:45:50 | getParameter(...) : String | XQueryInjection.java:51:35:51:38 | xqpe | +| XQueryInjection.java:59:23:59:50 | getParameter(...) : String | XQueryInjection.java:65:53:65:57 | query | +| XQueryInjection.java:73:32:73:59 | nameStr : String | XQueryInjection.java:79:35:79:38 | xqpe | +| XQueryInjection.java:86:33:86:60 | nameStr : String | XQueryInjection.java:92:53:92:57 | query | +| XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:104:35:104:38 | xqpe | +| XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:116:53:116:56 | name | +| XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:129:35:129:38 | xqpe | +| XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:142:53:142:54 | br | +| XQueryInjection.java:150:23:150:50 | getParameter(...) : String | XQueryInjection.java:155:29:155:32 | name | +| XQueryInjection.java:157:26:157:49 | getInputStream(...) : ServletInputStream | XQueryInjection.java:159:29:159:30 | br | nodes -| XQueryInjection.java:42:23:42:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| XQueryInjection.java:47:35:47:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:55:23:55:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| XQueryInjection.java:60:53:60:57 | query | semmle.label | query | -| XQueryInjection.java:68:32:68:59 | nameStr : String | semmle.label | nameStr : String | -| XQueryInjection.java:73:35:73:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:80:33:80:60 | nameStr : String | semmle.label | nameStr : String | -| XQueryInjection.java:85:53:85:57 | query | semmle.label | query | -| XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:97:35:97:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:109:53:109:56 | name | semmle.label | name | -| XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:122:35:122:38 | xqpe | semmle.label | xqpe | -| XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | -| XQueryInjection.java:135:53:135:54 | br | semmle.label | br | +| XQueryInjection.java:45:23:45:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:51:35:51:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:59:23:59:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:65:53:65:57 | query | semmle.label | query | +| XQueryInjection.java:73:32:73:59 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:79:35:79:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:86:33:86:60 | nameStr : String | semmle.label | nameStr : String | +| XQueryInjection.java:92:53:92:57 | query | semmle.label | query | +| XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:104:35:104:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:116:53:116:56 | name | semmle.label | name | +| XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:129:35:129:38 | xqpe | semmle.label | xqpe | +| XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:142:53:142:54 | br | semmle.label | br | +| XQueryInjection.java:150:23:150:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| XQueryInjection.java:155:29:155:32 | name | semmle.label | name | +| XQueryInjection.java:157:26:157:49 | getInputStream(...) : ServletInputStream | semmle.label | getInputStream(...) : ServletInputStream | +| XQueryInjection.java:159:29:159:30 | br | semmle.label | br | #select -| XQueryInjection.java:47:35:47:38 | xqpe | XQueryInjection.java:42:23:42:50 | getParameter(...) : String | XQueryInjection.java:47:35:47:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:42:23:42:50 | getParameter(...) | this user input | -| XQueryInjection.java:60:53:60:57 | query | XQueryInjection.java:55:23:55:50 | getParameter(...) : String | XQueryInjection.java:60:53:60:57 | query | XQuery query might include code from $@. | XQueryInjection.java:55:23:55:50 | getParameter(...) | this user input | -| XQueryInjection.java:73:35:73:38 | xqpe | XQueryInjection.java:68:32:68:59 | nameStr : String | XQueryInjection.java:73:35:73:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:68:32:68:59 | nameStr | this user input | -| XQueryInjection.java:85:53:85:57 | query | XQueryInjection.java:80:33:80:60 | nameStr : String | XQueryInjection.java:85:53:85:57 | query | XQuery query might include code from $@. | XQueryInjection.java:80:33:80:60 | nameStr | this user input | -| XQueryInjection.java:97:35:97:38 | xqpe | XQueryInjection.java:93:28:93:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:97:35:97:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:93:28:93:51 | getInputStream(...) | this user input | -| XQueryInjection.java:109:53:109:56 | name | XQueryInjection.java:105:28:105:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:109:53:109:56 | name | XQuery query might include code from $@. | XQueryInjection.java:105:28:105:51 | getInputStream(...) | this user input | -| XQueryInjection.java:122:35:122:38 | xqpe | XQueryInjection.java:117:28:117:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:122:35:122:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:117:28:117:51 | getInputStream(...) | this user input | -| XQueryInjection.java:135:53:135:54 | br | XQueryInjection.java:130:28:130:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:135:53:135:54 | br | XQuery query might include code from $@. | XQueryInjection.java:130:28:130:51 | getInputStream(...) | this user input | +| XQueryInjection.java:51:35:51:38 | xqpe | XQueryInjection.java:45:23:45:50 | getParameter(...) : String | XQueryInjection.java:51:35:51:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:45:23:45:50 | getParameter(...) | this user input | +| XQueryInjection.java:65:53:65:57 | query | XQueryInjection.java:59:23:59:50 | getParameter(...) : String | XQueryInjection.java:65:53:65:57 | query | XQuery query might include code from $@. | XQueryInjection.java:59:23:59:50 | getParameter(...) | this user input | +| XQueryInjection.java:79:35:79:38 | xqpe | XQueryInjection.java:73:32:73:59 | nameStr : String | XQueryInjection.java:79:35:79:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:73:32:73:59 | nameStr | this user input | +| XQueryInjection.java:92:53:92:57 | query | XQueryInjection.java:86:33:86:60 | nameStr : String | XQueryInjection.java:92:53:92:57 | query | XQuery query might include code from $@. | XQueryInjection.java:86:33:86:60 | nameStr | this user input | +| XQueryInjection.java:104:35:104:38 | xqpe | XQueryInjection.java:100:28:100:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:104:35:104:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:100:28:100:51 | getInputStream(...) | this user input | +| XQueryInjection.java:116:53:116:56 | name | XQueryInjection.java:112:28:112:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:116:53:116:56 | name | XQuery query might include code from $@. | XQueryInjection.java:112:28:112:51 | getInputStream(...) | this user input | +| XQueryInjection.java:129:35:129:38 | xqpe | XQueryInjection.java:124:28:124:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:129:35:129:38 | xqpe | XQuery query might include code from $@. | XQueryInjection.java:124:28:124:51 | getInputStream(...) | this user input | +| XQueryInjection.java:142:53:142:54 | br | XQueryInjection.java:137:28:137:51 | getInputStream(...) : ServletInputStream | XQueryInjection.java:142:53:142:54 | br | XQuery query might include code from $@. | XQueryInjection.java:137:28:137:51 | getInputStream(...) | this user input | +| XQueryInjection.java:155:29:155:32 | name | XQueryInjection.java:150:23:150:50 | getParameter(...) : String | XQueryInjection.java:155:29:155:32 | name | XQuery query might include code from $@. | XQueryInjection.java:150:23:150:50 | getParameter(...) | this user input | +| XQueryInjection.java:159:29:159:30 | br | XQueryInjection.java:157:26:157:49 | getInputStream(...) : ServletInputStream | XQueryInjection.java:159:29:159:30 | br | XQuery query might include code from $@. | XQueryInjection.java:157:26:157:49 | getInputStream(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java index 36f829d81bd..d8df8057cc6 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java +++ b/java/ql/test/experimental/query-tests/security/CWE-652/XQueryInjection.java @@ -1,3 +1,5 @@ +package com.vuln.v2.controller; + import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; @@ -27,9 +29,10 @@ public class XQueryInjection { + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); - expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + expr.bindString(new QName("name"), name, + conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } catch (XQException e) { @@ -42,10 +45,11 @@ public class XQueryInjection { String name = request.getParameter("name"); XQDataSource ds = new SaxonXQDataSource(); XQConnection conn = ds.getConnection(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + + "'] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -54,11 +58,12 @@ public class XQueryInjection { public void testRequestbad1(HttpServletRequest request) throws Exception { String name = request.getParameter("name"); XQDataSource xqds = new SaxonXQDataSource(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + name + + "'] return $user/password"; XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -68,10 +73,11 @@ public class XQueryInjection { public void testStringtbad(@RequestParam String nameStr) throws XQException { XQDataSource ds = new SaxonXQDataSource(); XQConnection conn = ds.getConnection(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + + "'] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -79,11 +85,12 @@ public class XQueryInjection { @RequestMapping public void testStringtbad1(@RequestParam String nameStr) throws XQException { XQDataSource xqds = new SaxonXQDataSource(); - String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + "'] return $user/password"; + String query = "for $user in doc(\"users.xml\")/Users/User[name='" + nameStr + + "'] return $user/password"; XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -95,7 +102,7 @@ public class XQueryInjection { XQConnection conn = ds.getConnection(); XQPreparedExpression xqpe = conn.prepareExpression(name); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -107,7 +114,7 @@ public class XQueryInjection { XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(name); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -120,7 +127,7 @@ public class XQueryInjection { XQConnection conn = ds.getConnection(); XQPreparedExpression xqpe = conn.prepareExpression(br); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -133,11 +140,26 @@ public class XQueryInjection { XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); XQResultSequence result = expr.executeQuery(br); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } + @RequestMapping + public void testExecuteCommandbad(HttpServletRequest request) throws Exception { + String name = request.getParameter("name"); + XQDataSource xqds = new SaxonXQDataSource(); + XQConnection conn = xqds.getConnection(); + XQExpression expr = conn.createExpression(); + //bad code + expr.executeCommand(name); + //bad code + InputStream is = request.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + expr.executeCommand(br); + expr.close(); + } + @RequestMapping public void good(HttpServletRequest request) throws XQException { String name = request.getParameter("name"); @@ -146,9 +168,10 @@ public class XQueryInjection { String query = "declare variable $name as xs:string external;" + " for $user in doc(\"users.xml\")/Users/User[name=$name] return $user/password"; XQPreparedExpression xqpe = conn.prepareExpression(query); - xqpe.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + xqpe.bindString(new QName("name"), name, + conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); XQResultSequence result = xqpe.executeQuery(); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } @@ -161,10 +184,12 @@ public class XQueryInjection { XQDataSource xqds = new SaxonXQDataSource(); XQConnection conn = xqds.getConnection(); XQExpression expr = conn.createExpression(); - expr.bindString(new QName("name"), name, conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); + expr.bindString(new QName("name"), name, + conn.createAtomicType(XQItemType.XQBASETYPE_STRING)); XQResultSequence result = expr.executeQuery(query); - while (result.next()){ + while (result.next()) { System.out.println(result.getItemAsString(null)); } } -} \ No newline at end of file +} + diff --git a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java index 94bd38fa175..0263588e8b4 100644 --- a/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java +++ b/java/ql/test/stubs/saxon-xqj-9.x/net/sf/saxon/xqj/SaxonXQConnection.java @@ -5,6 +5,7 @@ import net.sf.saxon.Configuration; import javax.xml.xquery.XQConnection; import javax.xml.xquery.XQPreparedExpression; import javax.xml.xquery.XQException; +import javax.xml.xquery.XQExpression; import javax.xml.xquery.XQStaticContext; import java.io.InputStream; From cfa72af12c206d6ce07df1c12fd00b72111bc3ba Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Fri, 12 Feb 2021 09:30:12 +0100 Subject: [PATCH 063/757] Python: Update test expectation to new format --- .../Security/CWE-209/StackTraceExposure.expected | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected b/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected index 8bf682bf052..857b07eee74 100644 --- a/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected +++ b/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected @@ -1,8 +1,11 @@ edges -| test.py:33:15:33:36 | exception info | test.py:34:29:34:31 | exception info | -| test.py:33:15:33:36 | exception info | test.py:34:29:34:31 | exception info | -| test.py:34:29:34:31 | exception info | test.py:34:16:34:32 | exception info | -| test.py:34:29:34:31 | exception info | test.py:34:16:34:32 | exception info | +| test.py:33:15:33:36 | ControlFlowNode for Attribute() | test.py:34:29:34:31 | ControlFlowNode for err | +| test.py:34:29:34:31 | ControlFlowNode for err | test.py:34:16:34:32 | ControlFlowNode for format_error() | +nodes +| test.py:16:16:16:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:33:15:33:36 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:34:16:34:32 | ControlFlowNode for format_error() | semmle.label | ControlFlowNode for format_error() | +| test.py:34:29:34:31 | ControlFlowNode for err | semmle.label | ControlFlowNode for err | #select -| test.py:16:16:16:37 | Attribute() | test.py:16:16:16:37 | exception info | test.py:16:16:16:37 | exception info | $@ may be exposed to an external user | test.py:16:16:16:37 | Attribute() | Error information | -| test.py:34:16:34:32 | format_error() | test.py:33:15:33:36 | exception info | test.py:34:16:34:32 | exception info | $@ may be exposed to an external user | test.py:33:15:33:36 | Attribute() | Error information | +| test.py:16:16:16:37 | ControlFlowNode for Attribute() | test.py:16:16:16:37 | ControlFlowNode for Attribute() | test.py:16:16:16:37 | ControlFlowNode for Attribute() | $@ may be exposed to an external user | test.py:16:16:16:37 | ControlFlowNode for Attribute() | Error information | +| test.py:34:16:34:32 | ControlFlowNode for format_error() | test.py:33:15:33:36 | ControlFlowNode for Attribute() | test.py:34:16:34:32 | ControlFlowNode for format_error() | $@ may be exposed to an external user | test.py:33:15:33:36 | ControlFlowNode for Attribute() | Error information | From 23f620d255423ace5ede1c1cf7373ffe6018a8bd Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Mon, 15 Feb 2021 05:31:29 +0000 Subject: [PATCH 064/757] Query to detect insecure LDAP endpoint configuration --- .../CWE/CWE-297/InsecureLdapEndpoint.java | 23 +++++++ .../CWE/CWE-297/InsecureLdapEndpoint.qhelp | 32 +++++++++ .../CWE/CWE-297/InsecureLdapEndpoint.ql | 65 +++++++++++++++++++ .../CWE-297/InsecureLdapEndpoint.expected | 2 + .../CWE-297/InsecureLdapEndpoint.java | 52 +++++++++++++++ .../CWE-297/InsecureLdapEndpoint.qlref | 1 + 6 files changed, 175 insertions(+) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql create mode 100644 java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.qlref diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java new file mode 100644 index 00000000000..0af5200a150 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java @@ -0,0 +1,23 @@ +public class InsecureLdapEndpoint { + public Hashtable createConnectionEnv() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // BAD - Test configuration with disabled SSL endpoint check. + { + System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + } + + // GOOD - No configuration to disable SSL endpoint check since it is enabled by default. + { + } + + return env; + } + +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp new file mode 100644 index 00000000000..3aee17f9777 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp @@ -0,0 +1,32 @@ + + + + +

    Java versions 8u181 or greater have enabled LDAPS endpoint identification by default. Nowadays infrastructure services like LDAP are commonly deployed behind load balancers therefore the LDAP server name can be different from the FQDN of the LDAPS endpoint. If a service certificate does not properly contain a matching DNS name as part of the certificate, Java will reject it by default.

    +

    Instead of addressing the issue properly by having a compliant certificate deployed, frequently developers simply disable SSL endpoint check.

    +

    This query checks whether LDAPS endpoint check is disabled in system properties.

    +
    + + +

    Replace any non-conforming LDAP server certificates to include a DNS name in the subjectAltName field of the certificate that matches the FQDN of the service.

    +
    + + +

    The following two examples show two ways of configuring SSL endpoint. In the 'BAD' case, +endpoint check is disabled. In the 'GOOD' case, endpoint check is left enabled through the default Java configuration.

    + +
    + + +
  • + Oracle Java 8 Update 181 (8u181): + Endpoint identification enabled on LDAPS connections +
  • +
  • + IBM: + Fix this LDAP SSL error +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql new file mode 100644 index 00000000000..266b2b999a8 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql @@ -0,0 +1,65 @@ +/** + * @name Insecure LDAP Endpoint Configuration + * @description Java application configured to disable LDAP endpoint identification does not validate the SSL certificate to properly ensure that it is actually associated with that host. + * @kind problem + * @id java/insecure-ldap-endpoint + * @tags security + * external/cwe-297 + */ + +import java + +/** + * The method to set a system property. + */ +class SetSystemPropertyMethod extends Method { + SetSystemPropertyMethod() { + this.hasName("setProperty") and + this.getDeclaringType().hasQualifiedName("java.lang", "System") + } +} + +/** + * The method to set Java properties. + */ +class SetPropertyMethod extends Method { + SetPropertyMethod() { + this.hasName("setProperty") and + this.getDeclaringType().hasQualifiedName("java.util", "Properties") + } +} + +/** + * The method to set system properties. + */ +class SetSystemPropertiesMethod extends Method { + SetSystemPropertiesMethod() { + this.hasName("setProperties") and + this.getDeclaringType().hasQualifiedName("java.lang", "System") + } +} + +/** Holds if `MethodAccess` ma disables SSL endpoint check. */ +predicate isInsecureSSLEndpoint(MethodAccess ma) { + ( + ma.getMethod() instanceof SetSystemPropertyMethod and + ( + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = + "com.sun.jndi.ldap.object.disableEndpointIdentification" and + ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" //com.sun.jndi.ldap.object.disableEndpointIdentification=true + ) + or + ma.getMethod() instanceof SetSystemPropertiesMethod and + exists(MethodAccess ma2 | + ma2.getMethod() instanceof SetPropertyMethod and + ma2.getArgument(0).(CompileTimeConstantExpr).getStringValue() = + "com.sun.jndi.ldap.object.disableEndpointIdentification" and + ma2.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" and //com.sun.jndi.ldap.object.disableEndpointIdentification=true + ma2.getQualifier().(VarAccess).getVariable().getAnAccess() = ma.getArgument(0) // systemProps.setProperties(properties) + ) + ) +} + +from MethodAccess ma +where isInsecureSSLEndpoint(ma) +select ma, "SSL configuration allows insecure endpoint configuration" diff --git a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected new file mode 100644 index 00000000000..29eb44937ee --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected @@ -0,0 +1,2 @@ +| InsecureLdapEndpoint.java:17:9:17:92 | setProperty(...) | SSL configuration allows insecure endpoint configuration | +| InsecureLdapEndpoint.java:48:3:48:34 | setProperties(...) | SSL configuration allows insecure endpoint configuration | diff --git a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java new file mode 100644 index 00000000000..44c37864af0 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java @@ -0,0 +1,52 @@ +import java.util.Hashtable; +import java.util.Properties; +import javax.naming.Context; + +public class InsecureLdapEndpoint { + // BAD - Test configuration with disabled SSL endpoint check. + public Hashtable createConnectionEnv() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // Disable SSL endpoint check + System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + + return env; + } + + // GOOD - Test configuration without disabling SSL endpoint check. + public Hashtable createConnectionEnv2() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + return env; + } + + // BAD - Test configuration with disabled SSL endpoint check. + public Hashtable createConnectionEnv3() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // Disable SSL endpoint check + Properties properties = new Properties(); + properties.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + System.setProperties(properties); + + return env; + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.qlref b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.qlref new file mode 100644 index 00000000000..1c4d99bb6a3 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql From 409d95c5222a35e35f9d383f6021fd3472c3f963 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Mon, 15 Feb 2021 14:01:14 +0530 Subject: [PATCH 065/757] Sanitizer checks to decrease FP --- .../Security/CWE/CWE-346/UnvalidatedCors.ql | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 3d98237b104..17cf94d2c1e 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -28,6 +28,14 @@ private predicate setsAllowCredentials(MethodAccess header) { header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" } +class CorsProbableCheckAccess extends MethodAccess { + CorsProbableCheckAccess() { + getMethod().getName() = ["contains", "equals"] and + getMethod().getDeclaringType().getQualifiedName() = + ["java.util.List", "java.util.ArrayList", "java.lang.String"] + } +} + private Expr getAccessControlAllowOriginHeaderName() { result.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin" } @@ -49,6 +57,21 @@ class CorsOriginConfig extends TaintTracking::Configuration { sink.asExpr() = corsheader.getArgument(1) ) } + + /* + * This should ideally check, the origin being validated against a list/array-list. + * or function being used to validate the origin, which has a flow from its parameter to any of the CorsProbableCheckAccess functions + */ + + override predicate isSanitizer(DataFlow::Node node) { + node.asExpr() = any(CorsProbableCheckAccess ma).getAnArgument() + or + exists(MethodAccess ma, CorsProbableCheckAccess ca | + ma.getMethod().calls(ca.getMethod()) and + DataFlow::localExprFlow(ma.getMethod().getAParameter().getAnAccess(), ca.getAnArgument()) and + (node.asExpr() = ma.getAnArgument() or node.asExpr() = ma.getAnArgument().getAChildExpr()) + ) + } } from DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf From 79855157b3ce7713fda492577243301349c71639 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 11 Feb 2021 15:34:13 +0100 Subject: [PATCH 066/757] Python: Move django response test to django v2/v3 That's really the django version I care about :P --- .../frameworks/{django-v1 => django-v2-v3}/response_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python/ql/test/experimental/library-tests/frameworks/{django-v1 => django-v2-v3}/response_test.py (100%) diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py similarity index 100% rename from python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py rename to python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py From 6934d5e642fec37ae62bf6eb235b51ba3274b5cb Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 11 Feb 2021 15:40:06 +0100 Subject: [PATCH 067/757] Python: Add django test of RedirectView subclass --- .../frameworks/django-v2-v3/response_test.py | 8 ++++++++ .../frameworks/django-v2-v3/testapp/urls.py | 3 +++ .../frameworks/django-v2-v3/testapp/views.py | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py index 99dc97624aa..8d73184702d 100644 --- a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py +++ b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py @@ -1,4 +1,5 @@ from django.http.response import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, JsonResponse, HttpResponseNotFound +from django.views.generic import RedirectView import django.shortcuts # Not an XSS sink, since the Content-Type is not "text/html" @@ -54,6 +55,13 @@ def redirect_shortcut(request): return django.shortcuts.redirect(next) # $ HttpResponse HttpRedirectResponse redirectLocation=next +class CustomRedirectView(RedirectView): + + def get_redirect_url(self, foo): # $ MISSING: routedParameter=foo + next = "https://example.com/{}".format(foo) + return next # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next + + # Ensure that simple subclasses are still vuln to XSS def xss__not_found(request): return HttpResponseNotFound(request.GET.get("name")) # $HttpResponse mimetype=text/html responseBody=Attribute() diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/urls.py b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/urls.py index 0f5c360b9d8..2f0d978c97a 100644 --- a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/urls.py +++ b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/urls.py @@ -15,4 +15,7 @@ urlpatterns = [ path("basic-view-handler/", views.MyBasicViewHandler.as_view()), # $routeSetup="basic-view-handler/" path("custom-inheritance-view-handler/", views.MyViewHandlerWithCustomInheritance.as_view()), # $routeSetup="custom-inheritance-view-handler/" + + path("CustomRedirectView/", views.CustomRedirectView.as_view()), # $routeSetup="CustomRedirectView/" + path("CustomRedirectView2/", views.CustomRedirectView2.as_view()), # $routeSetup="CustomRedirectView2/" ] diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py index d2028d0dd03..c51d07cef2a 100644 --- a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py +++ b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py @@ -1,5 +1,5 @@ from django.http import HttpRequest, HttpResponse -from django.views import View +from django.views.generic import View, RedirectView from django.views.decorators.csrf import csrf_exempt @@ -32,3 +32,16 @@ class MyViewHandlerWithCustomInheritance(MyCustomViewBaseClass): def get(self, request: HttpRequest): # $ requestHandler print(self.request.GET) return HttpResponse("MyViewHandlerWithCustomInheritance: GET") # $ HttpResponse + +# RedirectView +# See docs at https://docs.djangoproject.com/en/3.1/ref/class-based-views/base/#redirectview +class CustomRedirectView(RedirectView): + + def get_redirect_url(self, foo): # $ MISSING: routedParameter=foo + next = "https://example.com/{}".format(foo) + return next # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next + + +class CustomRedirectView2(RedirectView): + + url = "https://example.com/%(foo)s" From 745148474a6b90d2468acfd5a1c832f41fc90c54 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 12 Feb 2021 10:25:33 +0100 Subject: [PATCH 068/757] Python: Model get_redirect_url in django --- .../2021-02-12-django-get_redirect_url.md | 2 + .../src/semmle/python/frameworks/Django.qll | 73 +++++++++++++++++-- .../django-v2-v3/TestTaint.expected | 1 + .../frameworks/django-v2-v3/response_test.py | 5 +- .../frameworks/django-v2-v3/testapp/views.py | 4 +- 5 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 python/change-notes/2021-02-12-django-get_redirect_url.md diff --git a/python/change-notes/2021-02-12-django-get_redirect_url.md b/python/change-notes/2021-02-12-django-get_redirect_url.md new file mode 100644 index 00000000000..e2aef502c67 --- /dev/null +++ b/python/change-notes/2021-02-12-django-get_redirect_url.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Improved modeling of `django` to recognize request redirects from `get_redirect_url` on a `RedirectView` subclass. diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll index 6e90d5adee0..b68f7b5e43e 100644 --- a/python/ql/src/semmle/python/frameworks/Django.qll +++ b/python/ql/src/semmle/python/frameworks/Django.qll @@ -2074,7 +2074,11 @@ private module Django { // TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with // points-to and `.lookup`, which would handle `post = my_post_handler` inside class def result = this.getAMethod() and - result.getName() = HTTP::httpVerbLower() + ( + result.getName() = HTTP::httpVerbLower() + or + result.getName() = "get_redirect_url" + ) } /** @@ -2124,6 +2128,8 @@ private module Django { /** * A function that is a django route handler, meaning it handles incoming requests * with the django framework. + * + * Most functions take a django HttpRequest as a parameter (but not all). */ private class DjangoRouteHandler extends Function { DjangoRouteHandler() { @@ -2132,6 +2138,12 @@ private module Django { any(DjangoViewClass vc).getARequestHandler() = this } + /** + * Gets the index of the parameter where the first routed parameter can be passed -- + * that is, the one just after any possible `self` or HttpRequest parameters. + */ + int getFirstPossibleRoutedParamIndex() { result = 1 + this.getRequestParamIndex() } + /** Gets the index of the request parameter. */ int getRequestParamIndex() { not this.isMethod() and @@ -2145,6 +2157,26 @@ private module Django { Parameter getRequestParam() { result = this.getArg(this.getRequestParamIndex()) } } + /** + * A method named `get_redirect_url` on a django view class. + * + * See https://docs.djangoproject.com/en/3.1/ref/class-based-views/base/#django.views.generic.base.RedirectView.get_redirect_url + * + * Note: this function only does something on a subclass of `RedirectView`, but since + * classes can be considered django view classes without us knowing their super-classes, + * we need to consider _any_ django view class. I don't expect any problems to come from this. + */ + private class GetRedirectUrlFunction extends DjangoRouteHandler { + GetRedirectUrlFunction() { + this.getName() = "get_redirect_url" and + any(DjangoViewClass vc).getARequestHandler() = this + } + + override int getFirstPossibleRoutedParamIndex() { result = 1 } + + override int getRequestParamIndex() { none() } + } + /** A data-flow node that sets up a route on a server, using the django framework. */ abstract private class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode { /** Gets the data-flow node that is used as the argument for the view handler. */ @@ -2173,7 +2205,7 @@ private module Django { // parameter. This should give us more RemoteFlowSources but could also lead to // more FPs. If this turns out to be the wrong tradeoff, we can always change our mind. result in [this.getArg(_), this.getArgByName(_)] and - not result = any(int i | i <= this.getRequestParamIndex() | this.getArg(i)) + not result = any(int i | i < this.getFirstPossibleRoutedParamIndex() | this.getArg(i)) } } @@ -2211,7 +2243,8 @@ private module Django { exists(DjangoRouteHandler routeHandler | routeHandler = this.getARequestHandler() | not exists(this.getUrlPattern()) and result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and - not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i)) + not result = + any(int i | i < routeHandler.getFirstPossibleRoutedParamIndex() | routeHandler.getArg(i)) ) or exists(string name | @@ -2233,7 +2266,8 @@ private module Django { exists(DjangoRouteHandler routeHandler | routeHandler = this.getARequestHandler() | not exists(this.getUrlPattern()) and result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and - not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i)) + not result = + any(int i | i < routeHandler.getFirstPossibleRoutedParamIndex() | routeHandler.getArg(i)) ) or exists(DjangoRouteHandler routeHandler, DjangoRouteRegex regex | @@ -2245,7 +2279,9 @@ private module Django { not exists(regex.getGroupName(_, _)) and // first group will have group number 1 result = - routeHandler.getArg(routeHandler.getRequestParamIndex() + regex.getGroupNumber(_, _)) + routeHandler + .getArg(routeHandler.getFirstPossibleRoutedParamIndex() - 1 + + regex.getGroupNumber(_, _)) or result = routeHandler.getArgByName(regex.getGroupName(_, _)) ) @@ -2441,4 +2477,31 @@ private module Django { override string getMimetypeDefault() { none() } } + + // --------------------------------------------------------------------------- + // RedirectView handling + // --------------------------------------------------------------------------- + /** + * A return from a method named `get_redirect_url` on a django view class. + * + * Note that in reality, this only does something on a subclass of `RedirectView` -- + * but until API graphs makes this easy to model, I took a shortcut in modeling + * preciseness. + * + * See https://docs.djangoproject.com/en/3.1/ref/class-based-views/base/#redirectview + */ + private class DjangoRedirectViewGetRedirectUrlReturn extends HTTP::Server::HttpRedirectResponse::Range, + DataFlow::CfgNode { + DjangoRedirectViewGetRedirectUrlReturn() { + node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode() + } + + override DataFlow::Node getRedirectLocation() { result = this } + + override DataFlow::Node getBody() { none() } + + override DataFlow::Node getMimetypeOrContentTypeArg() { none() } + + override string getMimetypeDefault() { none() } + } } diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/TestTaint.expected b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/TestTaint.expected index c76685c6739..0602985a392 100644 --- a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/TestTaint.expected +++ b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/TestTaint.expected @@ -1,3 +1,4 @@ +| response_test.py:61 | ok | get_redirect_url | foo | | taint_test.py:8 | ok | test_taint | bar | | taint_test.py:8 | ok | test_taint | foo | | taint_test.py:9 | ok | test_taint | baz | diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py index 8d73184702d..91252378677 100644 --- a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py +++ b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/response_test.py @@ -57,9 +57,10 @@ def redirect_shortcut(request): class CustomRedirectView(RedirectView): - def get_redirect_url(self, foo): # $ MISSING: routedParameter=foo + def get_redirect_url(self, foo): # $ requestHandler routedParameter=foo + ensure_tainted(foo) next = "https://example.com/{}".format(foo) - return next # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next + return next # $ HttpResponse HttpRedirectResponse redirectLocation=next # Ensure that simple subclasses are still vuln to XSS diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py index c51d07cef2a..2a3ed803507 100644 --- a/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py +++ b/python/ql/test/experimental/library-tests/frameworks/django-v2-v3/testapp/views.py @@ -37,9 +37,9 @@ class MyViewHandlerWithCustomInheritance(MyCustomViewBaseClass): # See docs at https://docs.djangoproject.com/en/3.1/ref/class-based-views/base/#redirectview class CustomRedirectView(RedirectView): - def get_redirect_url(self, foo): # $ MISSING: routedParameter=foo + def get_redirect_url(self, foo): # $ requestHandler routedParameter=foo next = "https://example.com/{}".format(foo) - return next # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next + return next # $ HttpResponse HttpRedirectResponse redirectLocation=next class CustomRedirectView2(RedirectView): From f1e44bce4abaa982976decbc669c2a327621eee3 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 16 Feb 2021 00:07:44 +0800 Subject: [PATCH 069/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index 69617cad629..dfa900f81ad 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -16,7 +16,7 @@ import XQueryInjectionLib import DataFlow::PathGraph /** - * Taint-tracking configuration tracing flow from remote sources, through an XQuery parser, to its eventual execution. + * A taint-tracking configuration tracing flow from remote sources, through an XQuery parser, to its eventual execution. */ class XQueryInjectionConfig extends TaintTracking::Configuration { XQueryInjectionConfig() { this = "XQueryInjectionConfig" } From 92c00cb741ccdab82e7d0d8999e62a7243a989dd Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 16 Feb 2021 00:09:21 +0800 Subject: [PATCH 070/757] Update java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql Co-authored-by: Chris Smowton --- java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql index dfa900f81ad..0bb85272f08 100644 --- a/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql +++ b/java/ql/src/Security/CWE/CWE-652/XQueryInjection.ql @@ -30,7 +30,7 @@ class XQueryInjectionConfig extends TaintTracking::Configuration { } /** - * Conveys taint from the input to a `prepareExpression` call to the returned prepared expression. + * Holds if taint from the input `pred` to a `prepareExpression` call flows to the returned prepared expression `succ`. */ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { exists(XQueryParserCall parser | pred.asExpr() = parser.getInput() and succ.asExpr() = parser) From f32c77c266c3099ee4e6cfc05edcfe332f313c3a Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Mon, 15 Feb 2021 22:35:58 +0530 Subject: [PATCH 071/757] Qldoc and formatting changes --- .../semmle/code/java/dataflow/FlowSources.qll | 2 +- .../semmle/code/java/frameworks/play/Play.qll | 80 ++++++++----------- .../dataflow/taintsources/PlayResource.java | 48 +++++------ .../dataflow/taintsources/remote.expected | 17 ++-- .../play/PlayAddCSRFTokenAnnotation.ql | 2 +- .../play/PlayAddCsrfTokenAnnotation.expected | 1 + .../play/PlayAddCsrfTokenAnnotation.ql | 4 + .../play/PlayMVCHTTPRequestHeader.ql | 2 +- .../frameworks/play/PlayMVCResultClass.ql | 2 +- .../frameworks/play/PlayMVCResultsClass.ql | 2 +- .../frameworks/play/PlayMVCResultsMethods.ql | 2 +- 11 files changed, 76 insertions(+), 86 deletions(-) create mode 100644 java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.expected create mode 100644 java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.ql diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll index d2ddceec05f..ca48d583d18 100644 --- a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -286,7 +286,7 @@ private class RemoteTaintedMethod extends Method { private class PlayRequestGetMethod extends Method { PlayRequestGetMethod() { - this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader and + this.getDeclaringType() instanceof PlayMvcHttpRequestHeader and this.hasName(["queryString", "getQueryString", "header", "getHeader"]) } } diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll index 573e66f1fb9..701766a4e75 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/Play.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -1,52 +1,48 @@ +/** + * Provides classes and predicates for working with the `play` package. + */ + import java /** - * Play MVC Framework Result Class + * A `play.mvc.Result` class. */ -class PlayMVCResultClass extends Class { - PlayMVCResultClass() { this.hasQualifiedName("play.mvc", "Result") } +class PlayMvcResultClass extends Class { + PlayMvcResultClass() { this.hasQualifiedName("play.mvc", "Result") } } /** - * Play MVC Framework Results Class - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions + * A `play.mvc.Results` class. */ -class PlayMVCResultsClass extends Class { - PlayMVCResultsClass() { this.hasQualifiedName("play.mvc", "Results") } +class PlayMvcResultsClass extends Class { + PlayMvcResultsClass() { this.hasQualifiedName("play.mvc", "Results") } } /** - * Play MVC Framework HTTP Request Header Class + * A `play.mvc.Http$RequestHeader` class. */ -class PlayMVCHTTPRequestHeader extends RefType { - PlayMVCHTTPRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } +class PlayMvcHttpRequestHeader extends RefType { + PlayMvcHttpRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } } /** - * Play Framework Explicit Body Parser Annotation - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaBodyParsers#Choosing-an-explicit-body-parser + * A `play.mvc.BodyParser<>$Of"` annotation. */ class PlayBodyParserAnnotation extends Annotation { PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } } /** - * Play Framework AddCSRFToken Annotation - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaCsrf + * A `play.filters.csrf.AddCSRFToken` annotation. */ -class PlayAddCSRFTokenAnnotation extends Annotation { - PlayAddCSRFTokenAnnotation() { +class PlayAddCsrfTokenAnnotation extends Annotation { + PlayAddCsrfTokenAnnotation() { this.getType().hasQualifiedName("play.filters.csrf", "AddCSRFToken") } } /** - * Play Framework Async Promise - Gets the Promise Generic Member/Type of (play.libs.F) - * - * Documentation: https://www.playframework.com/documentation/2.5.1/api/java/play/libs/F.Promise.html + * A member with qualified name `F.Promise` of package `play.libs.F`. */ class PlayAsyncResultPromise extends Member { PlayAsyncResultPromise() { @@ -59,9 +55,7 @@ class PlayAsyncResultPromise extends Member { } /** - * Play Framework Async Generic Result - Gets the CompletionStage Generic Type of (java.util.concurrent) - * - * Documentation: https://www.playframework.com/documentation/2.6.x/JavaAsync + * A type with qualified name `CompletionStage` of package `java.util.concurrent`. */ class PlayAsyncResultCompletionStage extends Type { PlayAsyncResultCompletionStage() { @@ -71,7 +65,7 @@ class PlayAsyncResultCompletionStage extends Type { } /** - * Play Framework Controllers which extends PlayMVCController recursively - Used to find all Controllers + * A class which extends PlayMvcController recursively to find all controllers. */ class PlayController extends Class { PlayController() { @@ -80,9 +74,9 @@ class PlayController extends Class { } /** - * Play Framework Controller Action Methods - Mappings to route files + * A method to find PlayFramework controller action methods, these are mapping's to route files. * - * Sample Route - `POST /login @com.company.Application.login()` + * Sample Route - `POST /login @com.company.Application.login()`. * * Example - class get's `index` & `login` as valid action methods. * ``` @@ -96,24 +90,22 @@ class PlayController extends Class { * } * } * ``` - * - * Documentation: https://www.playframework.com/documentation/2.8.x/JavaActions */ class PlayControllerActionMethod extends Method { PlayControllerActionMethod() { this = any(PlayController c).getAMethod() and ( this.getReturnType() instanceof PlayAsyncResultPromise or - this.getReturnType() instanceof PlayMVCResultClass or + this.getReturnType() instanceof PlayMvcResultClass or this.getReturnType() instanceof PlayAsyncResultCompletionStage ) } } /** - * Play Action-Method parameters. These are a source of user input + * The PlayFramework action method parameters, these are a source of user input. * - * Example - Class get's `username` & `password` as valid parameters + * Example - `username` & `password` are marked as valid parameters. * ``` * public class Application extends Controller { * public Result index(String username, String password) { @@ -132,15 +124,13 @@ class PlayActionMethodQueryParameter extends Parameter { } /** - * Play Framework HTTPRequestHeader Methods - `headers`, `getQueryString`, `getHeader` - * - * Documentation: https://www.playframework.com/documentation/2.6.0/api/java/play/mvc/Http.RequestHeader.html + * A PlayFramework HttpRequestHeader method, some of these are `headers`, `getQueryString`, `getHeader`. */ -class PlayMVCHTTPRequestHeaderMethods extends Method { - PlayMVCHTTPRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMVCHTTPRequestHeader } +class PlayMvcHttpRequestHeaderMethods extends Method { + PlayMvcHttpRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMvcHttpRequestHeader } /** - * Gets all references to play.mvc.HTTP.RequestHeader `getQueryString` method + * A reference to the `getQueryString` method. */ MethodAccess getAQueryStringAccess() { this.hasName("getQueryString") and result = this.getAReference() @@ -148,20 +138,18 @@ class PlayMVCHTTPRequestHeaderMethods extends Method { } /** - * Play Framework mvc.Results Methods - `ok`, `status`, `redirect` - * - * Documentation: https://www.playframework.com/documentation/2.5.8/api/java/play/mvc/Results.html + * A PlayFramework results method, some of these are `ok`, `status`, `redirect`. */ -class PlayMVCResultsMethods extends Method { - PlayMVCResultsMethods() { this.getDeclaringType() instanceof PlayMVCResultsClass } +class PlayMvcResultsMethods extends Method { + PlayMvcResultsMethods() { this.getDeclaringType() instanceof PlayMvcResultsClass } /** - * Gets all references to play.mvc.Results `ok` method + * A reference to the play.mvc.Results `ok` method. */ MethodAccess getAnOkAccess() { this.hasName("ok") and result = this.getAReference() } /** - * Gets all references to play.mvc.Results `redirect` method + * A reference to the play.mvc.Results `redirect` method. */ MethodAccess getARedirectAccess() { this.hasName("redirect") and result = this.getAReference() } } diff --git a/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java b/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java index 547aad11edf..fac12f534ce 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java +++ b/java/ql/test/library-tests/dataflow/taintsources/PlayResource.java @@ -1,34 +1,36 @@ +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import play.filters.csrf.AddCSRFToken; +import play.libs.F; +import play.mvc.BodyParser; import play.mvc.Controller; import play.mvc.Http.*; import play.mvc.Result; -import play.filters.csrf.AddCSRFToken; -import play.libs.F; -import java.util.concurrent.CompletionStage; - public class PlayResource extends Controller { - public Result index(String username, String password) { - String append_token = "password" + password; - return ok("Working"); - } + @AddCSRFToken + public Result index() { + response().setHeader("X-Play-QL", "1"); + return ok("It works!"); + } - public Result session_redirect_me() { - String url = request().getQueryString("url"); - redirect(url); - } + @BodyParser.Of() + public Result session_redirect_me(String uri) { + String url = request().getQueryString("url"); + return redirect(url); + } - public F.Promise async_promise(String token) { - ok(token); - } + public F.Promise async_promise(String token) { + return F.Promise.pure(ok(token)); + } - public CompletionStage async_completionstage(String complete) { - String return_code = "complete" + complete; - ok("Async completion Stage"); - } + public CompletionStage async_completionstage(String uri) { + return CompletableFuture.completedFuture(ok("Async completion Stage")); + } - public String not_playactionmethod(String no_action) { - String return_code = no_action; - return return_code; - } + public String not_playactionmethod(String no_action) { + String return_code = no_action; + return return_code; + } } diff --git a/java/ql/test/library-tests/dataflow/taintsources/remote.expected b/java/ql/test/library-tests/dataflow/taintsources/remote.expected index f14dac0917b..3b8bdf4e5a0 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/remote.expected +++ b/java/ql/test/library-tests/dataflow/taintsources/remote.expected @@ -23,17 +23,12 @@ | IntentSources.java:33:20:33:33 | getIntent(...) | IntentSources.java:33:20:33:33 | getIntent(...) | | IntentSources.java:33:20:33:33 | getIntent(...) | IntentSources.java:33:20:33:55 | getStringExtra(...) | | IntentSources.java:33:20:33:33 | getIntent(...) | IntentSources.java:34:29:34:35 | trouble | -| PlayResource.java:11:25:11:39 | username | PlayResource.java:11:25:11:39 | username | -| PlayResource.java:11:42:11:56 | password | PlayResource.java:11:42:11:56 | password | -| PlayResource.java:11:42:11:56 | password | PlayResource.java:12:31:12:51 | ... + ... | -| PlayResource.java:11:42:11:56 | password | PlayResource.java:12:44:12:51 | password | -| PlayResource.java:17:22:17:52 | getQueryString(...) | PlayResource.java:17:22:17:52 | getQueryString(...) | -| PlayResource.java:21:44:21:55 | token | ../../../stubs/playframework-2.6.x/play/mvc/Results.java:261:27:261:40 | content | -| PlayResource.java:21:44:21:55 | token | PlayResource.java:21:44:21:55 | token | -| PlayResource.java:21:44:21:55 | token | PlayResource.java:22:12:22:16 | token | -| PlayResource.java:25:58:25:72 | complete | PlayResource.java:25:58:25:72 | complete | -| PlayResource.java:25:58:25:72 | complete | PlayResource.java:26:30:26:50 | ... + ... | -| PlayResource.java:25:58:25:72 | complete | PlayResource.java:26:43:26:50 | complete | +| PlayResource.java:19:37:19:46 | uri | PlayResource.java:19:37:19:46 | uri | +| PlayResource.java:20:18:20:48 | getQueryString(...) | PlayResource.java:20:18:20:48 | getQueryString(...) | +| PlayResource.java:24:42:24:53 | token | ../../../stubs/playframework-2.6.x/play/mvc/Results.java:261:27:261:40 | content | +| PlayResource.java:24:42:24:53 | token | PlayResource.java:24:42:24:53 | token | +| PlayResource.java:24:42:24:53 | token | PlayResource.java:25:30:25:34 | token | +| PlayResource.java:28:56:28:65 | uri | PlayResource.java:28:56:28:65 | uri | | RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:4:30:4:40 | path | | RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:5:20:5:31 | ... + ... | | RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:5:28:5:31 | path | diff --git a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql b/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql index 38fca8f6ab0..9448e4cce4e 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql +++ b/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql @@ -1,4 +1,4 @@ import semmle.code.java.frameworks.play.Play -from PlayAddCSRFTokenAnnotation token +from PlayAddCsrfTokenAnnotation token select token diff --git a/java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.expected b/java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.expected new file mode 100644 index 00000000000..06f021740be --- /dev/null +++ b/java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.expected @@ -0,0 +1 @@ +| resources/Resource.java:12:3:12:15 | AddCSRFToken | diff --git a/java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.ql b/java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.ql new file mode 100644 index 00000000000..9448e4cce4e --- /dev/null +++ b/java/ql/test/library-tests/frameworks/play/PlayAddCsrfTokenAnnotation.ql @@ -0,0 +1,4 @@ +import semmle.code.java.frameworks.play.Play + +from PlayAddCsrfTokenAnnotation token +select token diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.ql b/java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.ql index 0bfb7afe537..40af3a9e43d 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.ql +++ b/java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.ql @@ -1,4 +1,4 @@ import semmle.code.java.frameworks.play.Play -from PlayMVCHTTPRequestHeader c +from PlayMvcHttpRequestHeader c select c.getQualifiedName(), c.getAMethod().getQualifiedName() diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.ql b/java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.ql index a924a5bf730..49bc7b9c1f9 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.ql +++ b/java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.ql @@ -1,4 +1,4 @@ import semmle.code.java.frameworks.play.Play -from PlayMVCResultClass m +from PlayMvcResultClass m select m.getQualifiedName() diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.ql b/java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.ql index 0ca501da260..4d111c08e87 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.ql +++ b/java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.ql @@ -1,4 +1,4 @@ import semmle.code.java.frameworks.play.Play -from PlayMVCResultsClass m +from PlayMvcResultsClass m select m.getQualifiedName(), m.getAMethod().getQualifiedName() diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.ql b/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.ql index fc9f465253f..d4f274e96a6 100644 --- a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.ql +++ b/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.ql @@ -1,4 +1,4 @@ import semmle.code.java.frameworks.play.Play -from PlayMVCResultsMethods m +from PlayMvcResultsMethods m select m.getAnOkAccess() From 0004efc2ac57aac2fece1472826d7333950db962 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Mon, 15 Feb 2021 22:39:11 +0530 Subject: [PATCH 072/757] filename changes --- .../frameworks/play/PlayAddCSRFTokenAnnotation.expected | 1 - .../frameworks/play/PlayAddCSRFTokenAnnotation.ql | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected delete mode 100644 java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql diff --git a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected b/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected deleted file mode 100644 index 06f021740be..00000000000 --- a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.expected +++ /dev/null @@ -1 +0,0 @@ -| resources/Resource.java:12:3:12:15 | AddCSRFToken | diff --git a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql b/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql deleted file mode 100644 index 9448e4cce4e..00000000000 --- a/java/ql/test/library-tests/frameworks/play/PlayAddCSRFTokenAnnotation.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.code.java.frameworks.play.Play - -from PlayAddCsrfTokenAnnotation token -select token From c45be91d6fbdcebac436c0e31ca4f399cb746680 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Mon, 15 Feb 2021 23:09:11 +0530 Subject: [PATCH 073/757] more filename changes --- .../{PlayMVCResultClass.expected => PlayMvcResultClass.expected} | 0 .../play/{PlayMVCResultClass.ql => PlayMvcResultClass.ql} | 0 ...{PlayMVCResultsClass.expected => PlayMvcResultsClass.expected} | 0 .../play/{PlayMVCResultsClass.ql => PlayMvcResultsClass.ql} | 0 ...yMVCResultsMethods.expected => PlayMvcResultsMethods.expected} | 0 .../play/{PlayMVCResultsMethods.ql => PlayMvcResultsMethods.ql} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename java/ql/test/library-tests/frameworks/play/{PlayMVCResultClass.expected => PlayMvcResultClass.expected} (100%) rename java/ql/test/library-tests/frameworks/play/{PlayMVCResultClass.ql => PlayMvcResultClass.ql} (100%) rename java/ql/test/library-tests/frameworks/play/{PlayMVCResultsClass.expected => PlayMvcResultsClass.expected} (100%) rename java/ql/test/library-tests/frameworks/play/{PlayMVCResultsClass.ql => PlayMvcResultsClass.ql} (100%) rename java/ql/test/library-tests/frameworks/play/{PlayMVCResultsMethods.expected => PlayMvcResultsMethods.expected} (100%) rename java/ql/test/library-tests/frameworks/play/{PlayMVCResultsMethods.ql => PlayMvcResultsMethods.ql} (100%) diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.expected b/java/ql/test/library-tests/frameworks/play/PlayMvcResultClass.expected similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.expected rename to java/ql/test/library-tests/frameworks/play/PlayMvcResultClass.expected diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.ql b/java/ql/test/library-tests/frameworks/play/PlayMvcResultClass.ql similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCResultClass.ql rename to java/ql/test/library-tests/frameworks/play/PlayMvcResultClass.ql diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.expected b/java/ql/test/library-tests/frameworks/play/PlayMvcResultsClass.expected similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.expected rename to java/ql/test/library-tests/frameworks/play/PlayMvcResultsClass.expected diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.ql b/java/ql/test/library-tests/frameworks/play/PlayMvcResultsClass.ql similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCResultsClass.ql rename to java/ql/test/library-tests/frameworks/play/PlayMvcResultsClass.ql diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.expected b/java/ql/test/library-tests/frameworks/play/PlayMvcResultsMethods.expected similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.expected rename to java/ql/test/library-tests/frameworks/play/PlayMvcResultsMethods.expected diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.ql b/java/ql/test/library-tests/frameworks/play/PlayMvcResultsMethods.ql similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCResultsMethods.ql rename to java/ql/test/library-tests/frameworks/play/PlayMvcResultsMethods.ql From dae6771a19a31c1ed17f090bd4ec161604b5d9ab Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Mon, 15 Feb 2021 23:17:08 +0530 Subject: [PATCH 074/757] test file name changes --- ...TPRequestHeader.expected => PlayMvcHttpRequestHeader.expected} | 0 .../{PlayMVCHTTPRequestHeader.ql => PlayMvcHttpRequestHeader.ql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename java/ql/test/library-tests/frameworks/play/{PlayMVCHTTPRequestHeader.expected => PlayMvcHttpRequestHeader.expected} (100%) rename java/ql/test/library-tests/frameworks/play/{PlayMVCHTTPRequestHeader.ql => PlayMvcHttpRequestHeader.ql} (100%) diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.expected b/java/ql/test/library-tests/frameworks/play/PlayMvcHttpRequestHeader.expected similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.expected rename to java/ql/test/library-tests/frameworks/play/PlayMvcHttpRequestHeader.expected diff --git a/java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.ql b/java/ql/test/library-tests/frameworks/play/PlayMvcHttpRequestHeader.ql similarity index 100% rename from java/ql/test/library-tests/frameworks/play/PlayMVCHTTPRequestHeader.ql rename to java/ql/test/library-tests/frameworks/play/PlayMvcHttpRequestHeader.ql From 5ce3af05910fefdbc8e07c793ded5593f1a0e706 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Mon, 15 Feb 2021 21:38:54 +0000 Subject: [PATCH 075/757] Enhance the query and update qldoc --- .../CWE/CWE-297/InsecureLdapEndpoint.java | 13 +-- .../CWE/CWE-297/InsecureLdapEndpoint.qhelp | 9 +- .../CWE/CWE-297/InsecureLdapEndpoint.ql | 86 +++++++++++++------ .../CWE/CWE-297/InsecureLdapEndpoint2.java | 17 ++++ .../CWE-297/InsecureLdapEndpoint.expected | 7 +- .../CWE-297/InsecureLdapEndpoint.java | 60 ++++++++++++- 6 files changed, 149 insertions(+), 43 deletions(-) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint2.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java index 0af5200a150..6f7494f8811 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.java @@ -8,16 +8,11 @@ public class InsecureLdapEndpoint { env.put(Context.SECURITY_PRINCIPAL, "username"); env.put(Context.SECURITY_CREDENTIALS, "secpassword"); - // BAD - Test configuration with disabled SSL endpoint check. - { - System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); - } - - // GOOD - No configuration to disable SSL endpoint check since it is enabled by default. - { - } + // BAD - Test configuration with disabled SSL endpoint check. + { + System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + } return env; } - } diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp index 3aee17f9777..671f713ca0d 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp @@ -5,8 +5,8 @@

    Java versions 8u181 or greater have enabled LDAPS endpoint identification by default. Nowadays infrastructure services like LDAP are commonly deployed behind load balancers therefore the LDAP server name can be different from the FQDN of the LDAPS endpoint. If a service certificate does not properly contain a matching DNS name as part of the certificate, Java will reject it by default.

    -

    Instead of addressing the issue properly by having a compliant certificate deployed, frequently developers simply disable SSL endpoint check.

    -

    This query checks whether LDAPS endpoint check is disabled in system properties.

    +

    Instead of addressing the issue properly by having a compliant certificate deployed, frequently developers simply disable LDAPS endpoint check.

    +

    Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack. This query checks whether LDAPS endpoint check is disabled in system properties.

    @@ -14,9 +14,10 @@ -

    The following two examples show two ways of configuring SSL endpoint. In the 'BAD' case, +

    The following two examples show two ways of configuring LDAPS endpoint. In the 'BAD' case, endpoint check is disabled. In the 'GOOD' case, endpoint check is left enabled through the default Java configuration.

    - + +>
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql index 266b2b999a8..144f92875e7 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql @@ -1,17 +1,15 @@ /** - * @name Insecure LDAP Endpoint Configuration - * @description Java application configured to disable LDAP endpoint identification does not validate the SSL certificate to properly ensure that it is actually associated with that host. + * @name Insecure LDAPS Endpoint Configuration + * @description Java application configured to disable LDAPS endpoint identification does not validate the SSL certificate to properly ensure that it is actually associated with that host. * @kind problem - * @id java/insecure-ldap-endpoint + * @id java/insecure-ldaps-endpoint * @tags security * external/cwe-297 */ import java -/** - * The method to set a system property. - */ +/** The method to set a system property. */ class SetSystemPropertyMethod extends Method { SetSystemPropertyMethod() { this.hasName("setProperty") and @@ -19,19 +17,22 @@ class SetSystemPropertyMethod extends Method { } } -/** - * The method to set Java properties. - */ -class SetPropertyMethod extends Method { - SetPropertyMethod() { - this.hasName("setProperty") and - this.getDeclaringType().hasQualifiedName("java.util", "Properties") - } +/** The class `java.util.Hashtable`. */ +class TypeHashtable extends Class { + TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") } } /** - * The method to set system properties. + * The method to set Java properties either through `setProperty` declared in the class `Properties` or `put` declared in its parent class `HashTable`. */ +class SetPropertyMethod extends Method { + SetPropertyMethod() { + this.getDeclaringType().getAnAncestor() instanceof TypeHashtable and + this.hasName(["put", "setProperty"]) + } +} + +/** The method to set system properties. */ class SetSystemPropertiesMethod extends Method { SetSystemPropertiesMethod() { this.hasName("setProperties") and @@ -39,27 +40,62 @@ class SetSystemPropertiesMethod extends Method { } } +/** Holds if an expression is evaluated to the string literal `com.sun.jndi.ldap.object.disableEndpointIdentification`. */ +predicate isPropertyDisableLdapEndpointId(Expr expr) { + expr.(CompileTimeConstantExpr).getStringValue() = + "com.sun.jndi.ldap.object.disableEndpointIdentification" + or + exists(Field f | + expr = f.getAnAccess() and + f.getAnAssignedValue().(StringLiteral).getValue() = + "com.sun.jndi.ldap.object.disableEndpointIdentification" + ) +} + +/** Holds if an expression is evaluated to the boolean value true. */ +predicate isBooleanTrue(Expr expr) { + expr.(CompileTimeConstantExpr).getStringValue() = "true" // "true" + or + expr.(BooleanLiteral).getBooleanValue() = true // true + or + exists(MethodAccess ma | + expr = ma and + ma.getMethod().hasName("toString") and + ma.getQualifier().(FieldAccess).getField().hasName("TRUE") and + ma.getQualifier() + .(FieldAccess) + .getField() + .getDeclaringType() + .hasQualifiedName("java.lang", "Boolean") // Boolean.TRUE.toString() + ) +} + +/** Holds if `ma` is in a test class or method. */ +predicate isTestMethod(MethodAccess ma) { + ma.getMethod() instanceof TestMethod or + ma.getEnclosingCallable().getDeclaringType().getPackage().getName().matches("%test%") or + ma.getEnclosingCallable().getDeclaringType().getName().toLowerCase().matches("%test%") +} + /** Holds if `MethodAccess` ma disables SSL endpoint check. */ predicate isInsecureSSLEndpoint(MethodAccess ma) { ( ma.getMethod() instanceof SetSystemPropertyMethod and - ( - ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = - "com.sun.jndi.ldap.object.disableEndpointIdentification" and - ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" //com.sun.jndi.ldap.object.disableEndpointIdentification=true - ) + isPropertyDisableLdapEndpointId(ma.getArgument(0)) and + isBooleanTrue(ma.getArgument(1)) //com.sun.jndi.ldap.object.disableEndpointIdentification=true or ma.getMethod() instanceof SetSystemPropertiesMethod and exists(MethodAccess ma2 | ma2.getMethod() instanceof SetPropertyMethod and - ma2.getArgument(0).(CompileTimeConstantExpr).getStringValue() = - "com.sun.jndi.ldap.object.disableEndpointIdentification" and - ma2.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" and //com.sun.jndi.ldap.object.disableEndpointIdentification=true + isPropertyDisableLdapEndpointId(ma2.getArgument(0)) and + isBooleanTrue(ma2.getArgument(1)) and //com.sun.jndi.ldap.object.disableEndpointIdentification=true ma2.getQualifier().(VarAccess).getVariable().getAnAccess() = ma.getArgument(0) // systemProps.setProperties(properties) ) ) } from MethodAccess ma -where isInsecureSSLEndpoint(ma) -select ma, "SSL configuration allows insecure endpoint configuration" +where + isInsecureSSLEndpoint(ma) and + not isTestMethod(ma) +select ma, "LDAPS configuration allows insecure endpoint identification" diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint2.java b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint2.java new file mode 100644 index 00000000000..2a5c3c87bc7 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint2.java @@ -0,0 +1,17 @@ +public class InsecureLdapEndpoint2 { + public Hashtable createConnectionEnv() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // GOOD - No configuration to disable SSL endpoint check since it is enabled by default. + { + } + + return env; + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected index 29eb44937ee..f37e49e30e0 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.expected @@ -1,2 +1,5 @@ -| InsecureLdapEndpoint.java:17:9:17:92 | setProperty(...) | SSL configuration allows insecure endpoint configuration | -| InsecureLdapEndpoint.java:48:3:48:34 | setProperties(...) | SSL configuration allows insecure endpoint configuration | +| InsecureLdapEndpoint.java:19:9:19:92 | setProperty(...) | LDAPS configuration allows insecure endpoint identification | +| InsecureLdapEndpoint.java:50:9:50:40 | setProperties(...) | LDAPS configuration allows insecure endpoint identification | +| InsecureLdapEndpoint.java:68:9:68:40 | setProperties(...) | LDAPS configuration allows insecure endpoint identification | +| InsecureLdapEndpoint.java:84:9:84:94 | setProperty(...) | LDAPS configuration allows insecure endpoint identification | +| InsecureLdapEndpoint.java:102:9:102:40 | setProperties(...) | LDAPS configuration allows insecure endpoint identification | diff --git a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java index 44c37864af0..72f6bee118a 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java +++ b/java/ql/test/experimental/query-tests/security/CWE-297/InsecureLdapEndpoint.java @@ -3,7 +3,9 @@ import java.util.Properties; import javax.naming.Context; public class InsecureLdapEndpoint { - // BAD - Test configuration with disabled SSL endpoint check. + private static String PROP_DISABLE_LDAP_ENDPOINT_IDENTIFICATION = "com.sun.jndi.ldap.object.disableEndpointIdentification"; + + // BAD - Test configuration with disabled LDAPS endpoint check using `System.setProperty()`. public Hashtable createConnectionEnv() { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); @@ -19,7 +21,7 @@ public class InsecureLdapEndpoint { return env; } - // GOOD - Test configuration without disabling SSL endpoint check. + // GOOD - Test configuration without disabling LDAPS endpoint check. public Hashtable createConnectionEnv2() { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); @@ -32,7 +34,7 @@ public class InsecureLdapEndpoint { return env; } - // BAD - Test configuration with disabled SSL endpoint check. + // BAD - Test configuration with disabled LDAPS endpoint check using `System.setProperties()`. public Hashtable createConnectionEnv3() { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); @@ -49,4 +51,56 @@ public class InsecureLdapEndpoint { return env; } + + // BAD - Test configuration with disabled LDAPS endpoint check using `HashTable.put()`. + public Hashtable createConnectionEnv4() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // Disable SSL endpoint check + Properties properties = new Properties(); + properties.put("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + System.setProperties(properties); + + return env; + } + + // BAD - Test configuration with disabled LDAPS endpoint check using the `TRUE` boolean field. + public Hashtable createConnectionEnv5() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // Disable SSL endpoint check + System.setProperty(PROP_DISABLE_LDAP_ENDPOINT_IDENTIFICATION, Boolean.TRUE.toString()); + + return env; + } + + // BAD - Test configuration with disabled LDAPS endpoint check using a boolean value. + public Hashtable createConnectionEnv6() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636"); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "username"); + env.put(Context.SECURITY_CREDENTIALS, "secpassword"); + + // Disable SSL endpoint check + Properties properties = new Properties(); + properties.put("com.sun.jndi.ldap.object.disableEndpointIdentification", true); + System.setProperties(properties); + + return env; + } } From 0f7f465675bfeaf8d5d0677bdfb9efa30924020d Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Tue, 16 Feb 2021 15:48:00 +0530 Subject: [PATCH 076/757] Apply suggestions from code review Co-authored-by: Anders Schack-Mulligen --- .../semmle/code/java/frameworks/play/Play.qll | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll index 701766a4e75..fccf6546424 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/Play.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -1,32 +1,32 @@ /** - * Provides classes and predicates for working with the `play` package. + * Provides classes and predicates for working with the Play framework. */ import java /** - * A `play.mvc.Result` class. + * The `play.mvc.Result` class. */ class PlayMvcResultClass extends Class { PlayMvcResultClass() { this.hasQualifiedName("play.mvc", "Result") } } /** - * A `play.mvc.Results` class. + * The `play.mvc.Results` class. */ class PlayMvcResultsClass extends Class { PlayMvcResultsClass() { this.hasQualifiedName("play.mvc", "Results") } } /** - * A `play.mvc.Http$RequestHeader` class. + * The `play.mvc.Http$RequestHeader` class. */ class PlayMvcHttpRequestHeader extends RefType { PlayMvcHttpRequestHeader() { this.hasQualifiedName("play.mvc", "Http$RequestHeader") } } /** - * A `play.mvc.BodyParser<>$Of"` annotation. + * A `play.mvc.BodyParser<>$Of` annotation. */ class PlayBodyParserAnnotation extends Annotation { PlayBodyParserAnnotation() { this.getType().hasQualifiedName("play.mvc", "BodyParser<>$Of") } @@ -42,7 +42,7 @@ class PlayAddCsrfTokenAnnotation extends Annotation { } /** - * A member with qualified name `F.Promise` of package `play.libs.F`. + * The type `play.libs.F.Promise`. */ class PlayAsyncResultPromise extends Member { PlayAsyncResultPromise() { @@ -55,7 +55,7 @@ class PlayAsyncResultPromise extends Member { } /** - * A type with qualified name `CompletionStage` of package `java.util.concurrent`. + * The type `java.util.concurrent.CompletionStage`. */ class PlayAsyncResultCompletionStage extends Type { PlayAsyncResultCompletionStage() { @@ -65,7 +65,7 @@ class PlayAsyncResultCompletionStage extends Type { } /** - * A class which extends PlayMvcController recursively to find all controllers. + * The class `play.mvc.Controller` or a class that transitively extends it. */ class PlayController extends Class { PlayController() { @@ -74,21 +74,21 @@ class PlayController extends Class { } /** - * A method to find PlayFramework controller action methods, these are mapping's to route files. + * A Play framework controller action method. These are mappings to route files. * * Sample Route - `POST /login @com.company.Application.login()`. * - * Example - class get's `index` & `login` as valid action methods. - * ``` + * Example - class gets `index` and `login` as valid action methods. + * ```java * public class Application extends Controller { - * public Result index(String username, String password) { - * return ok("It works!"); - * } + * public Result index(String username, String password) { + * return ok("It works!"); + * } * - * public Result login() { - * return ok("Log me In!"); - * } - * } + * public Result login() { + * return ok("Log me In!"); + * } + * } * ``` */ class PlayControllerActionMethod extends Method { From 8e83de1c0556356282942265de4a88880f3e4d83 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Tue, 16 Feb 2021 16:13:21 +0530 Subject: [PATCH 077/757] formatting and grammar corrections from code review Co-authored-by: Anders Schack-Mulligen --- .../semmle/code/java/frameworks/play/Play.qll | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll index fccf6546424..0498ba9317c 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/Play.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -81,7 +81,7 @@ class PlayController extends Class { * Example - class gets `index` and `login` as valid action methods. * ```java * public class Application extends Controller { - * public Result index(String username, String password) { + * public Result index(String username, String password) { * return ok("It works!"); * } * @@ -103,15 +103,15 @@ class PlayControllerActionMethod extends Method { } /** - * The PlayFramework action method parameters, these are a source of user input. + * A Play framework action method parameter. These are a source of user input. * - * Example - `username` & `password` are marked as valid parameters. - * ``` - * public class Application extends Controller { - * public Result index(String username, String password) { - * return ok("It works!"); - * } - * } + * Example - `username` and `password` are action method parameters. + * ```java + * public class Application extends Controller { + * public Result index(String username, String password) { + * return ok("It works!"); + * } + * } * ``` */ class PlayActionMethodQueryParameter extends Parameter { @@ -130,7 +130,7 @@ class PlayMvcHttpRequestHeaderMethods extends Method { PlayMvcHttpRequestHeaderMethods() { this.getDeclaringType() instanceof PlayMvcHttpRequestHeader } /** - * A reference to the `getQueryString` method. + * Gets a reference to the `getQueryString` method. */ MethodAccess getAQueryStringAccess() { this.hasName("getQueryString") and result = this.getAReference() @@ -144,12 +144,12 @@ class PlayMvcResultsMethods extends Method { PlayMvcResultsMethods() { this.getDeclaringType() instanceof PlayMvcResultsClass } /** - * A reference to the play.mvc.Results `ok` method. + * Gets a reference to the `play.mvc.Results.ok` method. */ MethodAccess getAnOkAccess() { this.hasName("ok") and result = this.getAReference() } /** - * A reference to the play.mvc.Results `redirect` method. + * Gets a reference to the `play.mvc.Results.redirect` method. */ MethodAccess getARedirectAccess() { this.hasName("redirect") and result = this.getAReference() } } From e698ee77f7aa1ceb2034100429003ba0211681c6 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Tue, 16 Feb 2021 14:11:39 +0000 Subject: [PATCH 078/757] Update qldoc and test method --- .../Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp | 4 ++-- .../Security/CWE/CWE-297/InsecureLdapEndpoint.ql | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp index 671f713ca0d..cb43f52515b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.qhelp @@ -5,8 +5,8 @@

    Java versions 8u181 or greater have enabled LDAPS endpoint identification by default. Nowadays infrastructure services like LDAP are commonly deployed behind load balancers therefore the LDAP server name can be different from the FQDN of the LDAPS endpoint. If a service certificate does not properly contain a matching DNS name as part of the certificate, Java will reject it by default.

    -

    Instead of addressing the issue properly by having a compliant certificate deployed, frequently developers simply disable LDAPS endpoint check.

    -

    Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack. This query checks whether LDAPS endpoint check is disabled in system properties.

    +

    Instead of addressing the issue properly by having a compliant certificate deployed, frequently developers simply disable the LDAPS endpoint check.

    +

    Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack. This query checks whether the LDAPS endpoint check is disabled in system properties.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql index 144f92875e7..c83aeb4a6a5 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql @@ -32,7 +32,7 @@ class SetPropertyMethod extends Method { } } -/** The method to set system properties. */ +/** The `setProperties` method declared in `java.lang.System`. */ class SetSystemPropertiesMethod extends Method { SetSystemPropertiesMethod() { this.hasName("setProperties") and @@ -40,7 +40,7 @@ class SetSystemPropertiesMethod extends Method { } } -/** Holds if an expression is evaluated to the string literal `com.sun.jndi.ldap.object.disableEndpointIdentification`. */ +/** Holds if `expr` is evaluated to the string literal `com.sun.jndi.ldap.object.disableEndpointIdentification`. */ predicate isPropertyDisableLdapEndpointId(Expr expr) { expr.(CompileTimeConstantExpr).getStringValue() = "com.sun.jndi.ldap.object.disableEndpointIdentification" @@ -72,7 +72,8 @@ predicate isBooleanTrue(Expr expr) { /** Holds if `ma` is in a test class or method. */ predicate isTestMethod(MethodAccess ma) { - ma.getMethod() instanceof TestMethod or + ma.getEnclosingCallable() instanceof TestMethod or + ma.getEnclosingCallable().getDeclaringType() instanceof TestClass or ma.getEnclosingCallable().getDeclaringType().getPackage().getName().matches("%test%") or ma.getEnclosingCallable().getDeclaringType().getName().toLowerCase().matches("%test%") } From 520ba472934962033fd1b11f222ca0c0cfc7e145 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Wed, 17 Feb 2021 08:35:50 +0530 Subject: [PATCH 079/757] Sanitizer improvements from code review --- .../Security/CWE/CWE-346/UnvalidatedCors.ql | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 17cf94d2c1e..31de9821a96 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -30,9 +30,14 @@ private predicate setsAllowCredentials(MethodAccess header) { class CorsProbableCheckAccess extends MethodAccess { CorsProbableCheckAccess() { - getMethod().getName() = ["contains", "equals"] and - getMethod().getDeclaringType().getQualifiedName() = - ["java.util.List", "java.util.ArrayList", "java.lang.String"] + getMethod().hasName("contains") and + getMethod() + .getDeclaringType() + .getASourceSupertype*() + .hasQualifiedName("java.util", "Collection") + or + getMethod().hasName("equals") and + getQualifier().getType() instanceof TypeString } } @@ -58,18 +63,23 @@ class CorsOriginConfig extends TaintTracking::Configuration { ) } - /* - * This should ideally check, the origin being validated against a list/array-list. - * or function being used to validate the origin, which has a flow from its parameter to any of the CorsProbableCheckAccess functions + /** + * this sanitizer is oversimplistic: + * - it only considers local dataflows + * - it will consider any method calling `Collection.contains` or `String.equals` as a sanitizer + * no matter if that check is taken into account and its result reaches the + * return statement of the wrapper. */ - override predicate isSanitizer(DataFlow::Node node) { - node.asExpr() = any(CorsProbableCheckAccess ma).getAnArgument() - or - exists(MethodAccess ma, CorsProbableCheckAccess ca | - ma.getMethod().calls(ca.getMethod()) and - DataFlow::localExprFlow(ma.getMethod().getAParameter().getAnAccess(), ca.getAnArgument()) and - (node.asExpr() = ma.getAnArgument() or node.asExpr() = ma.getAnArgument().getAChildExpr()) + exists(CorsProbableCheckAccess check | + TaintTracking::localTaint(node, DataFlow::exprNode(check.getAnArgument())) + or + exists(MethodAccess wrapperAccess, Method wrapper, int i | + TaintTracking::localTaint(node, DataFlow::exprNode(wrapperAccess.getArgument(i))) and + wrapperAccess.getMethod() = wrapper and + TaintTracking::localTaint(DataFlow::parameterNode(wrapper.getParameter(i)), + DataFlow::exprNode(check.getAnArgument())) + ) ) } } From 58971f9f4e78789f546ce60fa7b5837d198cd288 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Wed, 17 Feb 2021 16:01:27 +0530 Subject: [PATCH 080/757] Switch qualified name to available CollectionType --- java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 31de9821a96..2a7152b1cad 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -31,10 +31,7 @@ private predicate setsAllowCredentials(MethodAccess header) { class CorsProbableCheckAccess extends MethodAccess { CorsProbableCheckAccess() { getMethod().hasName("contains") and - getMethod() - .getDeclaringType() - .getASourceSupertype*() - .hasQualifiedName("java.util", "Collection") + getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType or getMethod().hasName("equals") and getQualifier().getType() instanceof TypeString From 2baf2aa5c194e356dc563054a176c24d3ccb7311 Mon Sep 17 00:00:00 2001 From: Francis Alexander Date: Wed, 17 Feb 2021 18:58:32 +0530 Subject: [PATCH 081/757] Apply suggestions from code review - improved sanitizer checks. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alvaro Muñoz --- java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql index 2a7152b1cad..9f9c12b2295 100644 --- a/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql +++ b/java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.ql @@ -33,6 +33,9 @@ class CorsProbableCheckAccess extends MethodAccess { getMethod().hasName("contains") and getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType or + getMethod().hasName("containsKey") and + getMethod().getDeclaringType().getASourceSupertype*() instanceof MapType + or getMethod().hasName("equals") and getQualifier().getType() instanceof TypeString } From 5e36eedcb6ecd79f6b0f961be6215770a379cee7 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Wed, 17 Feb 2021 18:04:55 +0000 Subject: [PATCH 082/757] Add check for test packages --- java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql index d8e0f0abb8a..30cd05a044c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql @@ -41,7 +41,8 @@ class ServletMainMethod extends Method { this.isPublic() and this.getNumberOfParameters() = 1 and this.getParameter(0).getType() instanceof Array and - not this.getDeclaringType().getName().matches("%Test%") // Simple check to exclude test classes to reduce FPs + not this.getDeclaringType().getName().toLowerCase().matches("%test%") and // Simple check to exclude test classes to reduce FPs + not this.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") // Simple check to exclude classes in test packages to reduce FPs } } From e916ce8b9bcc0f16b7fc2f3d6ec1a4ffea5a243e Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Thu, 18 Feb 2021 00:50:38 +0000 Subject: [PATCH 083/757] Exclude test directories of typical build tools --- java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql index 30cd05a044c..b365a344020 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-489/ServletMain.ql @@ -42,7 +42,8 @@ class ServletMainMethod extends Method { this.getNumberOfParameters() = 1 and this.getParameter(0).getType() instanceof Array and not this.getDeclaringType().getName().toLowerCase().matches("%test%") and // Simple check to exclude test classes to reduce FPs - not this.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") // Simple check to exclude classes in test packages to reduce FPs + not this.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") and // Simple check to exclude classes in test packages to reduce FPs + not exists(this.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) // Match test directory structure of build tools like maven } } From 8f8b8be1e9db226b79625969ba7f149b10e3c087 Mon Sep 17 00:00:00 2001 From: Bas van Schaik <5082246+sj@users.noreply.github.com> Date: Thu, 18 Feb 2021 18:07:09 +0000 Subject: [PATCH 084/757] Include @xcorail in code reviews for `experimental` queries --- CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 64bda94db77..6e183c5da35 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -13,8 +13,8 @@ /docs/language/ @shati-patel @jf205 # Exclude help for experimental queries from docs review -/cpp/**/experimental/**/*.qhelp @github/codeql-c-analysis -/csharp/**/experimental/**/*.qhelp @github/codeql-csharp -/java/**/experimental/**/*.qhelp @github/codeql-java -/javascript/**/experimental/**/*.qhelp @github/codeql-javascript -/python/**/experimental/**/*.qhelp @github/codeql-python +/cpp/**/experimental/**/*.qhelp @github/codeql-c-analysis @xcorail +/csharp/**/experimental/**/*.qhelp @github/codeql-csharp @xcorail +/java/**/experimental/**/*.qhelp @github/codeql-java @xcorail +/javascript/**/experimental/**/*.qhelp @github/codeql-javascript @xcorail +/python/**/experimental/**/*.qhelp @github/codeql-python @xcorail From 4ab61bb0882461fe5e8d22587fe4abcdfcbb00dd Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 13:43:21 +0100 Subject: [PATCH 085/757] Python: Add a few tests for crypto frameworks Tests working can be verified by running ``` ls ql/python/ql/test/experimental/library-tests/frameworks/crypto*/*.py | xargs -L1 sh -c 'python $0 || exit 255' ``` --- .../frameworks/crypto/ConceptsTest.expected | 0 .../frameworks/crypto/ConceptsTest.ql | 2 + .../frameworks/crypto/test_dsa.py | 41 ++++++++++ .../frameworks/crypto/test_ec.py | 38 +++++++++ .../frameworks/crypto/test_rsa.py | 70 ++++++++++++++++ .../cryptodome/ConceptsTest.expected | 0 .../frameworks/cryptodome/ConceptsTest.ql | 2 + .../frameworks/cryptodome/test_dsa.py | 41 ++++++++++ .../frameworks/cryptodome/test_ec.py | 38 +++++++++ .../frameworks/cryptodome/test_rsa.py | 70 ++++++++++++++++ .../cryptography/ConceptsTest.expected | 0 .../frameworks/cryptography/ConceptsTest.ql | 2 + .../frameworks/cryptography/test_dsa.py | 37 +++++++++ .../frameworks/cryptography/test_ec.py | 43 ++++++++++ .../frameworks/cryptography/test_rsa.py | 80 +++++++++++++++++++ 15 files changed, 464 insertions(+) create mode 100644 python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.expected create mode 100644 python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.ql create mode 100644 python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.expected create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.ql create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.expected create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.ql create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py create mode 100644 python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.expected b/python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.ql b/python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py new file mode 100644 index 00000000000..dd8a9f68d72 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py @@ -0,0 +1,41 @@ +# DSA is a public-key algorithm for signing messages. +# Following example at https://pycryptodome.readthedocs.io/en/latest/src/signature/dsa.html + +from Crypto.PublicKey import DSA +from Crypto.Signature import DSS +from Crypto.Hash import SHA256 + + +private_key = DSA.generate(2048) +public_key = private_key.publickey() + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + + +message = b"message" + +signer = DSS.new(private_key, mode='fips-186-3') + +hasher = SHA256.new(message) +signature = signer.sign(hasher) + +print("signature={}".format(signature)) + +print() + +verifier = DSS.new(public_key, mode='fips-186-3') + +hasher = SHA256.new(message) +verifier.verify(hasher, signature) +print("Signature verified (as expected)") + +try: + hasher = SHA256.new(b"other message") + verifier.verify(hasher, signature) + raise Exception("Signature verified (unexpected)") +except ValueError: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py new file mode 100644 index 00000000000..0c7d17e8b81 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py @@ -0,0 +1,38 @@ +from Crypto.PublicKey import ECC +from Crypto.Signature import DSS +from Crypto.Hash import SHA256 + + +private_key = ECC.generate(curve="P-256") +public_key = private_key.public_key() + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + + +message = b"message" + +signer = DSS.new(private_key, mode='fips-186-3') + +hasher = SHA256.new(message) +signature = signer.sign(hasher) + +print("signature={}".format(signature)) + +print() + +verifier = DSS.new(public_key, mode='fips-186-3') + +hasher = SHA256.new(message) +verifier.verify(hasher, signature) +print("Signature verified (as expected)") + +try: + hasher = SHA256.new(b"other message") + verifier.verify(hasher, signature) + raise Exception("Signature verified (unexpected)") +except ValueError: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py new file mode 100644 index 00000000000..cc384263c96 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py @@ -0,0 +1,70 @@ +# RSA is a public-key algorithm for encrypting and signing messages. + +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP +from Crypto.Signature import pss +from Crypto.Hash import SHA256 + +private_key = RSA.generate(2048) +public_key = private_key.publickey() + +# ------------------------------------------------------------------------------ +# encrypt/decrypt +# ------------------------------------------------------------------------------ + +print("encrypt/decrypt") + +secret_message = b"secret message" + +# Following example at https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-rsa + +encrypt_cipher = PKCS1_OAEP.new(public_key) + +encrypted = encrypt_cipher.encrypt(secret_message) + +print("encrypted={}".format(encrypted)) + +print() + +decrypt_cipher = PKCS1_OAEP.new(private_key) + +decrypted = decrypt_cipher.decrypt( + encrypted, +) + +print("decrypted={}".format(decrypted)) +assert decrypted == secret_message + +print("\n---\n") + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + + +message = b"message" + +signer = pss.new(private_key) + +hasher = SHA256.new(message) +signature = signer.sign(hasher) + +print("signature={}".format(signature)) + +print() + + +verifier = pss.new(public_key) +hasher = SHA256.new(message) +verifier.verify(hasher, signature) +print("Signature verified (as expected)") + +try: + verifier = pss.new(public_key) + hasher = SHA256.new(b"other message") + verifier.verify(hasher, signature) + raise Exception("Signature verified (unexpected)") +except ValueError: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.expected b/python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.ql b/python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py new file mode 100644 index 00000000000..8c5b4e2e519 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py @@ -0,0 +1,41 @@ +# DSA is a public-key algorithm for signing messages. +# Following example at https://pycryptodome.readthedocs.io/en/latest/src/signature/dsa.html + +from Cryptodome.PublicKey import DSA +from Cryptodome.Signature import DSS +from Cryptodome.Hash import SHA256 + + +private_key = DSA.generate(2048) +public_key = private_key.publickey() + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + + +message = b"message" + +signer = DSS.new(private_key, mode='fips-186-3') + +hasher = SHA256.new(message) +signature = signer.sign(hasher) + +print("signature={}".format(signature)) + +print() + +verifier = DSS.new(public_key, mode='fips-186-3') + +hasher = SHA256.new(message) +verifier.verify(hasher, signature) +print("Signature verified (as expected)") + +try: + hasher = SHA256.new(b"other message") + verifier.verify(hasher, signature) + raise Exception("Signature verified (unexpected)") +except ValueError: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py new file mode 100644 index 00000000000..781d1dc68ac --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py @@ -0,0 +1,38 @@ +from Cryptodome.PublicKey import ECC +from Cryptodome.Signature import DSS +from Cryptodome.Hash import SHA256 + + +private_key = ECC.generate(curve="P-256") +public_key = private_key.public_key() + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + + +message = b"message" + +signer = DSS.new(private_key, mode='fips-186-3') + +hasher = SHA256.new(message) +signature = signer.sign(hasher) + +print("signature={}".format(signature)) + +print() + +verifier = DSS.new(public_key, mode='fips-186-3') + +hasher = SHA256.new(message) +verifier.verify(hasher, signature) +print("Signature verified (as expected)") + +try: + hasher = SHA256.new(b"other message") + verifier.verify(hasher, signature) + raise Exception("Signature verified (unexpected)") +except ValueError: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py new file mode 100644 index 00000000000..807c1dded51 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py @@ -0,0 +1,70 @@ +# RSA is a public-key algorithm for encrypting and signing messages. + +from Cryptodome.PublicKey import RSA +from Cryptodome.Cipher import PKCS1_OAEP +from Cryptodome.Signature import pss +from Cryptodome.Hash import SHA256 + +private_key = RSA.generate(2048) +public_key = private_key.publickey() + +# ------------------------------------------------------------------------------ +# encrypt/decrypt +# ------------------------------------------------------------------------------ + +print("encrypt/decrypt") + +secret_message = b"secret message" + +# Following example at https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-rsa + +encrypt_cipher = PKCS1_OAEP.new(public_key) + +encrypted = encrypt_cipher.encrypt(secret_message) + +print("encrypted={}".format(encrypted)) + +print() + +decrypt_cipher = PKCS1_OAEP.new(private_key) + +decrypted = decrypt_cipher.decrypt( + encrypted, +) + +print("decrypted={}".format(decrypted)) +assert decrypted == secret_message + +print("\n---\n") + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + + +message = b"message" + +signer = pss.new(private_key) + +hasher = SHA256.new(message) +signature = signer.sign(hasher) + +print("signature={}".format(signature)) + +print() + +verifier = pss.new(public_key) + +hasher = SHA256.new(message) +verifier.verify(hasher, signature) +print("Signature verified (as expected)") + +try: + verifier = pss.new(public_key) + hasher = SHA256.new(b"other message") + verifier.verify(hasher, signature) + raise Exception("Signature verified (unexpected)") +except ValueError: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.expected b/python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.ql b/python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py new file mode 100644 index 00000000000..31ca224b52f --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py @@ -0,0 +1,37 @@ +# DSA is a public-key algorithm for signing messages. +# see https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html + +from cryptography.hazmat.primitives.asymmetric import dsa +from cryptography.hazmat.primitives import hashes +from cryptography.exceptions import InvalidSignature + +HASH_ALGORITHM = hashes.SHA256() + +private_key = dsa.generate_private_key(key_size=2048) +public_key = private_key.public_key() + +message = b"message" + +# Following example at https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html#signing + +signature = private_key.sign( + message, + algorithm=HASH_ALGORITHM, +) + +print("signature={}".format(signature)) + +print() + +public_key.verify( + signature, message, algorithm=HASH_ALGORITHM +) +print("Signature verified (as expected)") + +try: + public_key.verify( + signature, b"other message", algorithm=HASH_ALGORITHM + ) + raise Exception("Signature verified (unexpected)") +except InvalidSignature: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py new file mode 100644 index 00000000000..9f810b261cb --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py @@ -0,0 +1,43 @@ +# see https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html + +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import hashes +from cryptography.exceptions import InvalidSignature + + +private_key = ec.generate_private_key(curve=ec.SECP384R1()) +public_key = private_key.public_key() + +HASH_ALGORITHM = hashes.SHA256() + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + +SIGNATURE_ALGORITHM = ec.ECDSA(HASH_ALGORITHM) + +message = b"message" + +signature = private_key.sign( + message, + signature_algorithm=SIGNATURE_ALGORITHM, +) + +print("signature={}".format(signature)) + +print() + +public_key.verify( + signature, message, signature_algorithm=SIGNATURE_ALGORITHM +) +print("Signature verified (as expected)") + +try: + public_key.verify( + signature, b"other message", signature_algorithm=SIGNATURE_ALGORITHM + ) + raise Exception("Signature verified (unexpected)") +except InvalidSignature: + print("Signature mismatch (as expected)") diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py new file mode 100644 index 00000000000..107b60214a9 --- /dev/null +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py @@ -0,0 +1,80 @@ +# RSA is a public-key algorithm for encrypting and signing messages. +# see https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html + +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import hashes +from cryptography.exceptions import InvalidSignature + + +private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) +public_key = private_key.public_key() + +HASH_ALGORITHM = hashes.SHA256() + +# ------------------------------------------------------------------------------ +# encrypt/decrypt +# ------------------------------------------------------------------------------ + +print("encrypt/decrypt") + +ENCRYPT_PADDING = padding.OAEP( + mgf=padding.MGF1(algorithm=HASH_ALGORITHM), + algorithm=HASH_ALGORITHM, + label=None, +) + + +secret_message = b"secret message" + +# Following example at https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html#encryption +encrypted = public_key.encrypt(secret_message, padding=ENCRYPT_PADDING) + +print("encrypted={}".format(encrypted)) + +print() + +decrypted = private_key.decrypt( + encrypted, + padding=ENCRYPT_PADDING +) + +print("decrypted={}".format(decrypted)) +assert decrypted == secret_message + +print("\n---\n") + +# ------------------------------------------------------------------------------ +# sign/verify +# ------------------------------------------------------------------------------ + +print("sign/verify") + +SIGN_PADDING = padding.PSS( + mgf=padding.MGF1(HASH_ALGORITHM), + salt_length=padding.PSS.MAX_LENGTH +) + +message = b"message" + +signature = private_key.sign( + message, + padding=SIGN_PADDING, + algorithm=HASH_ALGORITHM, +) + +print("signature={}".format(signature)) + +print() + +public_key.verify( + signature, message, padding=SIGN_PADDING, algorithm=HASH_ALGORITHM +) +print("Signature verified (as expected)") + +try: + public_key.verify( + signature, b"other message", padding=SIGN_PADDING, algorithm=HASH_ALGORITHM + ) + raise Exception("Signature verified (unexpected)") +except InvalidSignature: + print("Signature mismatch (as expected)") From 11cd0dbbc08a5d4ee31b7a8e5a4947cc947def21 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 16:20:04 +0100 Subject: [PATCH 086/757] Python: Add concepts for public-key generation I did spend some time to figure out how to best write `minimumSecureKeySize` predicate. I wanted to write once and for all the recommended sizes for each cryptosystem. I considered making the predicate such as ```codeql int minimumSecureKeySize() { this.getName() = "RSA" and result = 2048 or this.getName() = "DSA" and result = 2048 or this.getName() = "ECC" and result = 244 } ``` but then it would be impossible to add a new model without also being able to modify the body of this predicate -- which seems like a bad way to start off a brand new way of modeling things. So I considered if we could add it to the non-range class, such as ```codeql class RSAKeyGeneration extends KeyGeneration { RSAKeyGeneration() { this.getName() = "RSA" } override int minimumSecureKeySize() { result = 2048 } } ``` This has the major problem that when you're writing the models for a new API (and therefore extending KeyGeneration::Range), there is no way for you to see that you need to take this extra step :| (also problem about how we should define `minimumSecureKeySize` on `KeyGeneration` class then, since if we make it abstract, we effectively disable the ability to refine `KeyGeneration` since any subclass must provide an implementation.) So, therefore I ended up with this solution ;) --- python/ql/src/semmle/python/Concepts.qll | 87 +++++++++++++++++++ .../test/experimental/meta/ConceptsTest.qll | 22 +++++ 2 files changed, 109 insertions(+) diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll index c6d1ce367e9..1f04b077834 100644 --- a/python/ql/src/semmle/python/Concepts.qll +++ b/python/ql/src/semmle/python/Concepts.qll @@ -526,3 +526,90 @@ module HTTP { } } } + +/** Provides models for cryptographic things. */ +module Cryptography { + /** Provides models for public-key cryptography, also called asymmetric cryptography. */ + module PublicKey { + /** + * A data-flow node that generates a new key-pair for use with public-key cryptography. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `KeyGeneration::Range` instead. + */ + class KeyGeneration extends DataFlow::Node { + KeyGeneration::Range range; + + KeyGeneration() { this = range } + + /** Gets the name of the cryptographic algorithm (for example `"RSA"` or `"AES"`). */ + string getName() { result = range.getName() } + + /** Gets the argument that specifies size of the key in bits, if available. */ + DataFlow::Node getKeySizeArg() { result = range.getKeySizeArg() } + + /** + * Gets the size of the key generated (in bits), as well as the `origin` that + * explains how we obtained this specific key size. + */ + int getKeySizeWithOrigin(DataFlow::Node origin) { + result = range.getKeySizeWithOrigin(origin) + } + + /** Gets the minimum key size (in bits) for this algorithm to be considered secure. */ + int minimumSecureKeySize() { result = range.minimumSecureKeySize() } + } + + /** Provides classes for modeling new key-pair generation APIs. */ + module KeyGeneration { + /** + * A data-flow node that generates a new key-pair for use with public-key cryptography. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `KeyGeneration` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the name of the cryptographic algorithm (for example `"RSA"`). */ + abstract string getName(); + + /** Gets the argument that specifies size of the key in bits, if available. */ + abstract DataFlow::Node getKeySizeArg(); + + /** + * Gets the size of the key generated (in bits), as well as the `origin` that + * explains how we obtained this specific key size. + */ + int getKeySizeWithOrigin(DataFlow::Node origin) { + exists(IntegerLiteral size | origin = DataFlow::exprNode(size) | + origin.(DataFlow::LocalSourceNode).flowsTo(this.getKeySizeArg()) and + result = size.getValue() + ) + } + + /** Gets the minimum key size (in bits) for this algorithm to be considered secure. */ + abstract int minimumSecureKeySize(); + } + + /** A data-flow node that generates a new RSA key-pair. */ + abstract class RSARange extends Range { + override string getName() { result = "RSA" } + + override int minimumSecureKeySize() { result = 2048 } + } + + /** A data-flow node that generates a new DSA key-pair. */ + abstract class DSARange extends Range { + override string getName() { result = "DSA" } + + override int minimumSecureKeySize() { result = 2048 } + } + + /** A data-flow node that generates a new ECC key-pair. */ + abstract class ECCRange extends Range { + override string getName() { result = "ECC" } + + override int minimumSecureKeySize() { result = 224 } + } + } + } +} diff --git a/python/ql/test/experimental/meta/ConceptsTest.qll b/python/ql/test/experimental/meta/ConceptsTest.qll index eafcb8b0ef9..3800a4cd273 100644 --- a/python/ql/test/experimental/meta/ConceptsTest.qll +++ b/python/ql/test/experimental/meta/ConceptsTest.qll @@ -319,3 +319,25 @@ class SafeAccessCheckTest extends InlineExpectationsTest { ) } } + +class PublicKeyGenerationTest extends InlineExpectationsTest { + PublicKeyGenerationTest() { this = "PublicKeyGenerationTest" } + + override string getARelevantTag() { result in ["PublicKeyGeneration", "keySize"] } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(location.getFile().getRelativePath()) and + exists(Cryptography::PublicKey::KeyGeneration keyGen | + location = keyGen.getLocation() and + ( + element = keyGen.toString() and + value = "" and + tag = "PublicKeyGeneration" + or + element = keyGen.toString() and + value = keyGen.getKeySizeWithOrigin(_).toString() and + tag = "keySize" + ) + ) + } +} From 1bf9f7d1353a30a8355c9e67e0bacdf8bec0706a Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 16:30:18 +0100 Subject: [PATCH 087/757] Python: Add missing annotations to new crypto tests --- .../experimental/library-tests/frameworks/crypto/test_dsa.py | 2 +- .../experimental/library-tests/frameworks/crypto/test_ec.py | 2 +- .../experimental/library-tests/frameworks/crypto/test_rsa.py | 2 +- .../library-tests/frameworks/cryptodome/test_dsa.py | 2 +- .../experimental/library-tests/frameworks/cryptodome/test_ec.py | 2 +- .../library-tests/frameworks/cryptodome/test_rsa.py | 2 +- .../library-tests/frameworks/cryptography/test_dsa.py | 2 +- .../library-tests/frameworks/cryptography/test_ec.py | 2 +- .../library-tests/frameworks/cryptography/test_rsa.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py index dd8a9f68d72..9d58b375187 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py @@ -6,7 +6,7 @@ from Crypto.Signature import DSS from Crypto.Hash import SHA256 -private_key = DSA.generate(2048) +private_key = DSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py index 0c7d17e8b81..fdf17571232 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py @@ -3,7 +3,7 @@ from Crypto.Signature import DSS from Crypto.Hash import SHA256 -private_key = ECC.generate(curve="P-256") +private_key = ECC.generate(curve="P-256") # $ MISSING: PublicKeyGeneration keySize=256 public_key = private_key.public_key() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py index cc384263c96..68cac4d7ad6 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py @@ -5,7 +5,7 @@ from Crypto.Cipher import PKCS1_OAEP from Crypto.Signature import pss from Crypto.Hash import SHA256 -private_key = RSA.generate(2048) +private_key = RSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py index 8c5b4e2e519..044f7c28df9 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py @@ -6,7 +6,7 @@ from Cryptodome.Signature import DSS from Cryptodome.Hash import SHA256 -private_key = DSA.generate(2048) +private_key = DSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py index 781d1dc68ac..b1728dd9629 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py @@ -3,7 +3,7 @@ from Cryptodome.Signature import DSS from Cryptodome.Hash import SHA256 -private_key = ECC.generate(curve="P-256") +private_key = ECC.generate(curve="P-256") # $ MISSING: PublicKeyGeneration keySize=256 public_key = private_key.public_key() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py index 807c1dded51..19cea87d600 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py @@ -5,7 +5,7 @@ from Cryptodome.Cipher import PKCS1_OAEP from Cryptodome.Signature import pss from Cryptodome.Hash import SHA256 -private_key = RSA.generate(2048) +private_key = RSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py index 31ca224b52f..af244a044e6 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py @@ -7,7 +7,7 @@ from cryptography.exceptions import InvalidSignature HASH_ALGORITHM = hashes.SHA256() -private_key = dsa.generate_private_key(key_size=2048) +private_key = dsa.generate_private_key(key_size=2048) # $ MISSING: PublicKeyGeneration keySize=2048 public_key = private_key.public_key() message = b"message" diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py index 9f810b261cb..e62d5f2242b 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py @@ -5,7 +5,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.exceptions import InvalidSignature -private_key = ec.generate_private_key(curve=ec.SECP384R1()) +private_key = ec.generate_private_key(curve=ec.SECP384R1()) # $ MISSING: PublicKeyGeneration keySize=384 public_key = private_key.public_key() HASH_ALGORITHM = hashes.SHA256() diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py index 107b60214a9..8e7553dfa4d 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py @@ -6,7 +6,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.exceptions import InvalidSignature -private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) +private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) # $ MISSING: PublicKeyGeneration keySize=2048 public_key = private_key.public_key() HASH_ALGORITHM = hashes.SHA256() From bd40965afe3dd1b976b6ec2527871fe93d4905f4 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 16:31:20 +0100 Subject: [PATCH 088/757] Python: Add modeling for `cryptography` PyPI package --- python/ql/src/semmle/python/Frameworks.qll | 1 + .../semmle/python/frameworks/Cryptography.qll | 516 ++++++++++++++++++ .../frameworks/cryptography/test_dsa.py | 2 +- .../frameworks/cryptography/test_ec.py | 2 +- .../frameworks/cryptography/test_rsa.py | 2 +- 5 files changed, 520 insertions(+), 3 deletions(-) create mode 100644 python/ql/src/semmle/python/frameworks/Cryptography.qll diff --git a/python/ql/src/semmle/python/Frameworks.qll b/python/ql/src/semmle/python/Frameworks.qll index 23ffb88641e..7f252d0218e 100644 --- a/python/ql/src/semmle/python/Frameworks.qll +++ b/python/ql/src/semmle/python/Frameworks.qll @@ -2,6 +2,7 @@ * Helper file that imports all framework modeling. */ +private import semmle.python.frameworks.Cryptography private import semmle.python.frameworks.Dill private import semmle.python.frameworks.Django private import semmle.python.frameworks.Fabric diff --git a/python/ql/src/semmle/python/frameworks/Cryptography.qll b/python/ql/src/semmle/python/frameworks/Cryptography.qll new file mode 100644 index 00000000000..3f7ae145af1 --- /dev/null +++ b/python/ql/src/semmle/python/frameworks/Cryptography.qll @@ -0,0 +1,516 @@ +/** + * Provides classes modeling security-relevant aspects of the `cryptography` PyPI package. + * See https://cryptography.io/en/latest/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.Concepts + +/** + * Provides models for the `cryptography` PyPI package. + * See https://cryptography.io/en/latest/. + */ +private module CryptographyModel { + // --------------------------------------------------------------------------- + // cryptography + // --------------------------------------------------------------------------- + /** Gets a reference to the `cryptography` module. */ + private DataFlow::Node cryptography(DataFlow::TypeTracker t) { + t.start() and + result = DataFlow::importNode("cryptography") + or + exists(DataFlow::TypeTracker t2 | result = cryptography(t2).track(t2, t)) + } + + /** Gets a reference to the `cryptography` module. */ + DataFlow::Node cryptography() { result = cryptography(DataFlow::TypeTracker::end()) } + + /** Provides models for the `cryptography` module. */ + module cryptography { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node cryptography_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["hazmat"] and + ( + t.start() and + result = DataFlow::importNode("cryptography" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = cryptography() + ) + or + // Due to bad performance when using normal setup with `cryptography_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + cryptography_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate cryptography_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(cryptography_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node cryptography_attr(string attr_name) { + result = cryptography_attr(DataFlow::TypeTracker::end(), attr_name) + } + + // ------------------------------------------------------------------------- + // cryptography.hazmat + // ------------------------------------------------------------------------- + /** Gets a reference to the `cryptography.hazmat` module. */ + DataFlow::Node hazmat() { result = cryptography_attr("hazmat") } + + /** Provides models for the `cryptography.hazmat` module */ + module hazmat { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node hazmat_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["primitives"] and + ( + t.start() and + result = DataFlow::importNode("cryptography.hazmat" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = hazmat() + ) + or + // Due to bad performance when using normal setup with `hazmat_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + hazmat_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate hazmat_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(hazmat_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node hazmat_attr(string attr_name) { + result = hazmat_attr(DataFlow::TypeTracker::end(), attr_name) + } + + // ------------------------------------------------------------------------- + // cryptography.hazmat.primitives + // ------------------------------------------------------------------------- + /** Gets a reference to the `cryptography.hazmat.primitives` module. */ + DataFlow::Node primitives() { result = hazmat_attr("primitives") } + + /** Provides models for the `cryptography.hazmat.primitives` module */ + module primitives { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node primitives_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["asymmetric"] and + ( + t.start() and + result = DataFlow::importNode("cryptography.hazmat.primitives" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = primitives() + ) + or + // Due to bad performance when using normal setup with `primitives_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + primitives_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate primitives_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(primitives_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node primitives_attr(string attr_name) { + result = primitives_attr(DataFlow::TypeTracker::end(), attr_name) + } + + // ------------------------------------------------------------------------- + // cryptography.hazmat.primitives.asymmetric + // ------------------------------------------------------------------------- + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric` module. */ + DataFlow::Node asymmetric() { result = primitives_attr("asymmetric") } + + /** Provides models for the `cryptography.hazmat.primitives.asymmetric` module */ + module asymmetric { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node asymmetric_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["rsa", "dsa", "ec"] and + ( + t.start() and + result = + DataFlow::importNode("cryptography.hazmat.primitives.asymmetric" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = asymmetric() + ) + or + // Due to bad performance when using normal setup with `asymmetric_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + asymmetric_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate asymmetric_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(asymmetric_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node asymmetric_attr(string attr_name) { + result = asymmetric_attr(DataFlow::TypeTracker::end(), attr_name) + } + + // ------------------------------------------------------------------------- + // cryptography.hazmat.primitives.asymmetric.rsa + // ------------------------------------------------------------------------- + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.rsa` module. */ + DataFlow::Node rsa() { result = asymmetric_attr("rsa") } + + /** Provides models for the `cryptography.hazmat.primitives.asymmetric.rsa` module */ + module rsa { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.rsa` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node rsa_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["generate_private_key"] and + ( + t.start() and + result = + DataFlow::importNode("cryptography.hazmat.primitives.asymmetric.rsa" + "." + + attr_name) + or + t.startInAttr(attr_name) and + result = rsa() + ) + or + // Due to bad performance when using normal setup with `rsa_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + rsa_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate rsa_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(rsa_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.rsa` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node rsa_attr(string attr_name) { + result = rsa_attr(DataFlow::TypeTracker::end(), attr_name) + } + + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key` function. */ + DataFlow::Node generate_private_key() { result = rsa_attr("generate_private_key") } + } + + // ------------------------------------------------------------------------- + // cryptography.hazmat.primitives.asymmetric.dsa + // ------------------------------------------------------------------------- + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.dsa` module. */ + DataFlow::Node dsa() { result = asymmetric_attr("dsa") } + + /** Provides models for the `cryptography.hazmat.primitives.asymmetric.dsa` module */ + module dsa { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.dsa` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node dsa_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["generate_private_key"] and + ( + t.start() and + result = + DataFlow::importNode("cryptography.hazmat.primitives.asymmetric.dsa" + "." + + attr_name) + or + t.startInAttr(attr_name) and + result = dsa() + ) + or + // Due to bad performance when using normal setup with `dsa_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + dsa_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate dsa_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(dsa_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.dsa` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node dsa_attr(string attr_name) { + result = dsa_attr(DataFlow::TypeTracker::end(), attr_name) + } + + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key` function. */ + DataFlow::Node generate_private_key() { result = dsa_attr("generate_private_key") } + } + + // ------------------------------------------------------------------------- + // cryptography.hazmat.primitives.asymmetric.ec + // ------------------------------------------------------------------------- + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.ec` module. */ + DataFlow::Node ec() { result = asymmetric_attr("ec") } + + /** Provides models for the `cryptography.hazmat.primitives.asymmetric.ec` module */ + module ec { + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.ec` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node ec_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in [ + "generate_private_key", + // curves + "SECT571R1", "SECT409R1", "SECT283R1", "SECT233R1", "SECT163R2", "SECT571K1", + "SECT409K1", "SECT283K1", "SECT233K1", "SECT163K1", "SECP521R1", "SECP384R1", + "SECP256R1", "SECP256K1", "SECP224R1", "SECP192R1", "BrainpoolP256R1", + "BrainpoolP384R1", "BrainpoolP512R1" + ] and + ( + t.start() and + result = + DataFlow::importNode("cryptography.hazmat.primitives.asymmetric.ec" + "." + + attr_name) + or + t.startInAttr(attr_name) and + result = ec() + ) + or + // Due to bad performance when using normal setup with `ec_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + ec_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate ec_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(ec_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.ec` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node ec_attr(string attr_name) { + result = ec_attr(DataFlow::TypeTracker::end(), attr_name) + } + + /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.ec.generate_private_key` function. */ + DataFlow::Node generate_private_key() { result = ec_attr("generate_private_key") } + + /** Gets a predefined curve class with a specific key size (in bits). */ + DataFlow::Node curveClassWithKeySize(int keySize) { + // obtained by manually looking at source code in + // https://github.com/pyca/cryptography/blob/cba69f1922803f4f29a3fde01741890d88b8e217/src/cryptography/hazmat/primitives/asymmetric/ec.py#L208-L300 + result = ec_attr("SECT571R1") and keySize = 570 + or + result = ec_attr("SECT409R1") and keySize = 409 + or + result = ec_attr("SECT283R1") and keySize = 283 + or + result = ec_attr("SECT233R1") and keySize = 233 + or + result = ec_attr("SECT163R2") and keySize = 163 + or + result = ec_attr("SECT571K1") and keySize = 571 + or + result = ec_attr("SECT409K1") and keySize = 409 + or + result = ec_attr("SECT283K1") and keySize = 283 + or + result = ec_attr("SECT233K1") and keySize = 233 + or + result = ec_attr("SECT163K1") and keySize = 163 + or + result = ec_attr("SECP521R1") and keySize = 521 + or + result = ec_attr("SECP384R1") and keySize = 384 + or + result = ec_attr("SECP256R1") and keySize = 256 + or + result = ec_attr("SECP256K1") and keySize = 256 + or + result = ec_attr("SECP224R1") and keySize = 224 + or + result = ec_attr("SECP192R1") and keySize = 192 + or + result = ec_attr("BrainpoolP256R1") and keySize = 256 + or + result = ec_attr("BrainpoolP384R1") and keySize = 384 + or + result = ec_attr("BrainpoolP512R1") and keySize = 512 + } + + /** Gets a predefined curve class instance with a specific key size (in bits). */ + private DataFlow::Node curveClassInstanceWithKeySize( + DataFlow::TypeTracker t, int keySize + ) { + t.start() and + result.asCfgNode().(CallNode).getFunction() = + curveClassWithKeySize(keySize).asCfgNode() + or + exists(DataFlow::TypeTracker t2 | + result = curveClassInstanceWithKeySize(t2, keySize).track(t2, t) + ) + } + + /** Gets a predefined curve class instance with a specific key size (in bits). */ + DataFlow::Node curveClassInstanceWithKeySize(int keySize) { + result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize) + } + } + } + } + } + } + + // --------------------------------------------------------------------------- + /** + * A call to `cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key` + * + * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html#cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key + */ + class CryptographyRSAGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::RSARange, + DataFlow::CfgNode { + override CallNode node; + + CryptographyRSAGeneratePrivateKeyCall() { + node.getFunction() = + cryptography::hazmat::primitives::asymmetric::rsa::generate_private_key().asCfgNode() + } + + override DataFlow::Node getKeySizeArg() { + result.asCfgNode() in [node.getArg(1), node.getArgByName("key_size")] + } + } + + /** + * A call to `cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key` + * + * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html#cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key + */ + class CryptographyDSAGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::DSARange, + DataFlow::CfgNode { + override CallNode node; + + CryptographyDSAGeneratePrivateKeyCall() { + node.getFunction() = + cryptography::hazmat::primitives::asymmetric::dsa::generate_private_key().asCfgNode() + } + + override DataFlow::Node getKeySizeArg() { + result.asCfgNode() in [node.getArg(0), node.getArgByName("key_size")] + } + } + + /** + * A call to `cryptography.hazmat.primitives.asymmetric.ec.generate_private_key` + * + * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#cryptography.hazmat.primitives.asymmetric.ec.generate_private_key + */ + class CryptographyECGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::ECCRange, + DataFlow::CfgNode { + override CallNode node; + + CryptographyECGeneratePrivateKeyCall() { + node.getFunction() = + cryptography::hazmat::primitives::asymmetric::ec::generate_private_key().asCfgNode() + } + + /** Gets the argument that specifies the curve to use. */ + DataFlow::Node getCurveArg() { + result.asCfgNode() in [node.getArg(0), node.getArgByName("curve")] + } + + override int getKeySizeWithOrigin(DataFlow::Node origin) { + origin = this.getCurveArg() and + origin = + cryptography::hazmat::primitives::asymmetric::ec::curveClassInstanceWithKeySize(result) + } + + // Note: There is not really a key-size argument, since it's always specified by the curve. + override DataFlow::Node getKeySizeArg() { none() } + } +} diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py index af244a044e6..73aa38a246b 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_dsa.py @@ -7,7 +7,7 @@ from cryptography.exceptions import InvalidSignature HASH_ALGORITHM = hashes.SHA256() -private_key = dsa.generate_private_key(key_size=2048) # $ MISSING: PublicKeyGeneration keySize=2048 +private_key = dsa.generate_private_key(key_size=2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.public_key() message = b"message" diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py index e62d5f2242b..0372d7e9dbf 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_ec.py @@ -5,7 +5,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.exceptions import InvalidSignature -private_key = ec.generate_private_key(curve=ec.SECP384R1()) # $ MISSING: PublicKeyGeneration keySize=384 +private_key = ec.generate_private_key(curve=ec.SECP384R1()) # $ PublicKeyGeneration keySize=384 public_key = private_key.public_key() HASH_ALGORITHM = hashes.SHA256() diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py index 8e7553dfa4d..ee1d98646a9 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptography/test_rsa.py @@ -6,7 +6,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.exceptions import InvalidSignature -private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) # $ MISSING: PublicKeyGeneration keySize=2048 +private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.public_key() HASH_ALGORITHM = hashes.SHA256() From 6e4c627209803f79af79a3259520e319bf984bf3 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 16:34:28 +0100 Subject: [PATCH 089/757] Python: Add modeling for `pycryptodomex` PyPI package --- python/ql/src/semmle/python/Frameworks.qll | 1 + .../semmle/python/frameworks/Cryptodome.qll | 354 ++++++++++++++++++ .../frameworks/cryptodome/test_dsa.py | 2 +- .../frameworks/cryptodome/test_ec.py | 2 +- .../frameworks/cryptodome/test_rsa.py | 2 +- 5 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 python/ql/src/semmle/python/frameworks/Cryptodome.qll diff --git a/python/ql/src/semmle/python/Frameworks.qll b/python/ql/src/semmle/python/Frameworks.qll index 7f252d0218e..a00511ca545 100644 --- a/python/ql/src/semmle/python/Frameworks.qll +++ b/python/ql/src/semmle/python/Frameworks.qll @@ -2,6 +2,7 @@ * Helper file that imports all framework modeling. */ +private import semmle.python.frameworks.Cryptodome private import semmle.python.frameworks.Cryptography private import semmle.python.frameworks.Dill private import semmle.python.frameworks.Django diff --git a/python/ql/src/semmle/python/frameworks/Cryptodome.qll b/python/ql/src/semmle/python/frameworks/Cryptodome.qll new file mode 100644 index 00000000000..e960e1baff6 --- /dev/null +++ b/python/ql/src/semmle/python/frameworks/Cryptodome.qll @@ -0,0 +1,354 @@ +/** + * Provides classes modeling security-relevant aspects of + * - the `pycryptodome` PyPI package (imported as `Crypto`) + * - the `pycryptodomex` PyPI package (imported as `Cryptodome`) + * See https://pycryptodome.readthedocs.io/en/latest/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.Concepts + +/** + * Provides models for + * - the `pycryptodome` PyPI package (imported as `Crypto`) + * - the `pycryptodomex` PyPI package (imported as `Cryptodome`) + * See https://pycryptodome.readthedocs.io/en/latest/ + */ +private module CryptodomeModel { + // --------------------------------------------------------------------------- + // Cryptodome + // --------------------------------------------------------------------------- + /** Gets a reference to the `Cryptodome` module. */ + private DataFlow::Node cryptodome(DataFlow::TypeTracker t) { + t.start() and + result = DataFlow::importNode("Cryptodome") + or + exists(DataFlow::TypeTracker t2 | result = cryptodome(t2).track(t2, t)) + } + + /** Gets a reference to the `Cryptodome` module. */ + DataFlow::Node cryptodome() { result = cryptodome(DataFlow::TypeTracker::end()) } + + /** Provides models for the `Cryptodome` module. */ + module Cryptodome { + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node cryptodome_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["PublicKey"] and + ( + t.start() and + result = DataFlow::importNode("Cryptodome" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = cryptodome() + ) + or + // Due to bad performance when using normal setup with `cryptodome_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + cryptodome_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate cryptodome_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(cryptodome_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node cryptodome_attr(string attr_name) { + result = cryptodome_attr(DataFlow::TypeTracker::end(), attr_name) + } + + // ------------------------------------------------------------------------- + // Cryptodome.PublicKey + // ------------------------------------------------------------------------- + /** Gets a reference to the `Cryptodome.PublicKey` module. */ + DataFlow::Node publicKey() { result = cryptodome_attr("PublicKey") } + + /** Provides models for the `Cryptodome.PublicKey` module */ + module PublicKey { + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node publicKey_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["RSA", "DSA", "ECC"] and + ( + t.start() and + result = DataFlow::importNode("Cryptodome.PublicKey" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = publicKey() + ) + or + // Due to bad performance when using normal setup with `publicKey_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + publicKey_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate publicKey_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(publicKey_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node publicKey_attr(string attr_name) { + result = publicKey_attr(DataFlow::TypeTracker::end(), attr_name) + } + + // ------------------------------------------------------------------------- + // Cryptodome.PublicKey.RSA + // ------------------------------------------------------------------------- + /** Gets a reference to the `Cryptodome.PublicKey.RSA` module. */ + DataFlow::Node rsa() { result = publicKey_attr("RSA") } + + /** Provides models for the `Cryptodome.PublicKey.RSA` module */ + module RSA { + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node rsa_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["generate"] and + ( + t.start() and + result = DataFlow::importNode("Cryptodome.PublicKey.RSA" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = rsa() + ) + or + // Due to bad performance when using normal setup with `rsa_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + rsa_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate rsa_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(rsa_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node rsa_attr(string attr_name) { + result = rsa_attr(DataFlow::TypeTracker::end(), attr_name) + } + + /** Gets a reference to the `Cryptodome.PublicKey.RSA.generate` function. */ + DataFlow::Node generate() { result = rsa_attr("generate") } + } + + // ------------------------------------------------------------------------- + // Cryptodome.PublicKey.DSA + // ------------------------------------------------------------------------- + /** Gets a reference to the `Cryptodome.PublicKey.DSA` module. */ + DataFlow::Node dsa() { result = publicKey_attr("DSA") } + + /** Provides models for the `Cryptodome.PublicKey.DSA` module */ + module DSA { + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node dsa_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["generate"] and + ( + t.start() and + result = DataFlow::importNode("Cryptodome.PublicKey.DSA" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = dsa() + ) + or + // Due to bad performance when using normal setup with `dsa_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + dsa_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate dsa_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(dsa_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node dsa_attr(string attr_name) { + result = dsa_attr(DataFlow::TypeTracker::end(), attr_name) + } + + /** Gets a reference to the `Cryptodome.PublicKey.DSA.generate` function. */ + DataFlow::Node generate() { result = dsa_attr("generate") } + } + + // ------------------------------------------------------------------------- + // Cryptodome.PublicKey.ECC + // ------------------------------------------------------------------------- + /** Gets a reference to the `Cryptodome.PublicKey.ECC` module. */ + DataFlow::Node ecc() { result = publicKey_attr("ECC") } + + /** Provides models for the `Cryptodome.PublicKey.ECC` module */ + module ECC { + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node ecc_attr(DataFlow::TypeTracker t, string attr_name) { + attr_name in ["generate"] and + ( + t.start() and + result = DataFlow::importNode("Cryptodome.PublicKey.ECC" + "." + attr_name) + or + t.startInAttr(attr_name) and + result = ecc() + ) + or + // Due to bad performance when using normal setup with `ecc_attr(t2, attr_name).track(t2, t)` + // we have inlined that code and forced a join + exists(DataFlow::TypeTracker t2 | + exists(DataFlow::StepSummary summary | + ecc_attr_first_join(t2, attr_name, result, summary) and + t = t2.append(summary) + ) + ) + } + + pragma[nomagic] + private predicate ecc_attr_first_join( + DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(ecc_attr(t2, attr_name), res, summary) + } + + /** + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC` module. + * WARNING: Only holds for a few predefined attributes. + */ + private DataFlow::Node ecc_attr(string attr_name) { + result = ecc_attr(DataFlow::TypeTracker::end(), attr_name) + } + + /** Gets a reference to the `Cryptodome.PublicKey.ECC.generate` function. */ + DataFlow::Node generate() { result = ecc_attr("generate") } + } + } + } + + // --------------------------------------------------------------------------- + /** + * A call to `Cryptodome.PublicKey.RSA.generate` + * + * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate + */ + class CryptodomePublicKeyRSAGenerateCall extends Cryptography::PublicKey::KeyGeneration::RSARange, + DataFlow::CfgNode { + override CallNode node; + + CryptodomePublicKeyRSAGenerateCall() { + node.getFunction() = Cryptodome::PublicKey::RSA::generate().asCfgNode() + } + + override DataFlow::Node getKeySizeArg() { + result.asCfgNode() in [node.getArg(0), node.getArgByName("bits")] + } + } + + /** + * A call to `Cryptodome.PublicKey.DSA.generate` + * + * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate + */ + class CryptodomePublicKeyDSAGenerateCall extends Cryptography::PublicKey::KeyGeneration::DSARange, + DataFlow::CfgNode { + override CallNode node; + + CryptodomePublicKeyDSAGenerateCall() { + node.getFunction() = Cryptodome::PublicKey::DSA::generate().asCfgNode() + } + + override DataFlow::Node getKeySizeArg() { + result.asCfgNode() in [node.getArg(0), node.getArgByName("bits")] + } + } + + /** + * A call to `Cryptodome.PublicKey.ECC.generate` + * + * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate + */ + class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::ECCRange, + DataFlow::CfgNode { + override CallNode node; + + CryptodomePublicKeyEccGenerateCall() { + node.getFunction() = Cryptodome::PublicKey::ECC::generate().asCfgNode() + } + + /** Gets the argument that specifies the curve to use (a string). */ + DataFlow::Node getCurveArg() { result.asCfgNode() in [node.getArgByName("curve")] } + + string getCurveWithOrigin(DataFlow::Node origin) { + exists(StrConst str | origin = DataFlow::exprNode(str) | + origin.(DataFlow::LocalSourceNode).flowsTo(this.getCurveArg()) and + result = str.getText() + ) + } + + override int getKeySizeWithOrigin(DataFlow::Node origin) { + exists(string curve | curve = getCurveWithOrigin(origin) | + // using list from https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html + curve in ["NIST P-256", "p256", "P-256", "prime256v1", "secp256r1"] and result = 256 + or + curve in ["NIST P-384", "p384", "P-384", "prime384v1", "secp384r1"] and result = 384 + or + curve in ["NIST P-521", "p521", "P-521", "prime521v1", "secp521r1"] and result = 521 + ) + } + + // Note: There is not really a key-size argument, since it's always specified by the curve. + override DataFlow::Node getKeySizeArg() { none() } + } +} diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py index 044f7c28df9..a33cf8c0944 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py @@ -6,7 +6,7 @@ from Cryptodome.Signature import DSS from Cryptodome.Hash import SHA256 -private_key = DSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 +private_key = DSA.generate(2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py index b1728dd9629..d3860bbb3b3 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py @@ -3,7 +3,7 @@ from Cryptodome.Signature import DSS from Cryptodome.Hash import SHA256 -private_key = ECC.generate(curve="P-256") # $ MISSING: PublicKeyGeneration keySize=256 +private_key = ECC.generate(curve="P-256") # $ PublicKeyGeneration keySize=256 public_key = private_key.public_key() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py index 19cea87d600..fd1feccb29b 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py @@ -5,7 +5,7 @@ from Cryptodome.Cipher import PKCS1_OAEP from Cryptodome.Signature import pss from Cryptodome.Hash import SHA256 -private_key = RSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 +private_key = RSA.generate(2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ From d5ff477644a37b289ed95159451d832510ba7cd1 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 16:42:40 +0100 Subject: [PATCH 090/757] Python: Add modeling for `pycryptodome` PyPI package --- .../semmle/python/frameworks/Cryptodome.qll | 69 ++++++++++--------- .../frameworks/crypto/test_dsa.py | 2 +- .../frameworks/crypto/test_ec.py | 2 +- .../frameworks/crypto/test_rsa.py | 2 +- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Cryptodome.qll b/python/ql/src/semmle/python/frameworks/Cryptodome.qll index e960e1baff6..b598cc76677 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptodome.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptodome.qll @@ -19,28 +19,28 @@ private module CryptodomeModel { // --------------------------------------------------------------------------- // Cryptodome // --------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome` module. */ + /** Gets a reference to the `Cryptodome`/`Crypto` module. */ private DataFlow::Node cryptodome(DataFlow::TypeTracker t) { t.start() and - result = DataFlow::importNode("Cryptodome") + result = DataFlow::importNode(["Cryptodome", "Crypto"]) or exists(DataFlow::TypeTracker t2 | result = cryptodome(t2).track(t2, t)) } - /** Gets a reference to the `Cryptodome` module. */ + /** Gets a reference to the `Cryptodome`/`Crypto` module. */ DataFlow::Node cryptodome() { result = cryptodome(DataFlow::TypeTracker::end()) } - /** Provides models for the `Cryptodome` module. */ + /** Provides models for the `Cryptodome`/`Crypto` module. */ module Cryptodome { /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome`/`Crypto` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node cryptodome_attr(DataFlow::TypeTracker t, string attr_name) { attr_name in ["PublicKey"] and ( t.start() and - result = DataFlow::importNode("Cryptodome" + "." + attr_name) + result = DataFlow::importNode(["Cryptodome", "Crypto"] + "." + attr_name) or t.startInAttr(attr_name) and result = cryptodome() @@ -64,7 +64,7 @@ private module CryptodomeModel { } /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome`/`Crypto` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node cryptodome_attr(string attr_name) { @@ -74,20 +74,20 @@ private module CryptodomeModel { // ------------------------------------------------------------------------- // Cryptodome.PublicKey // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey` module. */ + /** Gets a reference to the `Cryptodome.PublicKey`/`Crypto.PublicKey` module. */ DataFlow::Node publicKey() { result = cryptodome_attr("PublicKey") } - /** Provides models for the `Cryptodome.PublicKey` module */ + /** Provides models for the `Cryptodome.PublicKey`/`Crypto.PublicKey` module */ module PublicKey { /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey`/`Crypto.PublicKey` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node publicKey_attr(DataFlow::TypeTracker t, string attr_name) { attr_name in ["RSA", "DSA", "ECC"] and ( t.start() and - result = DataFlow::importNode("Cryptodome.PublicKey" + "." + attr_name) + result = DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey" + "." + attr_name) or t.startInAttr(attr_name) and result = publicKey() @@ -112,7 +112,7 @@ private module CryptodomeModel { } /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey`/`Crypto.PublicKey` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node publicKey_attr(string attr_name) { @@ -122,20 +122,21 @@ private module CryptodomeModel { // ------------------------------------------------------------------------- // Cryptodome.PublicKey.RSA // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey.RSA` module. */ + /** Gets a reference to the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module. */ DataFlow::Node rsa() { result = publicKey_attr("RSA") } - /** Provides models for the `Cryptodome.PublicKey.RSA` module */ + /** Provides models for the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module */ module RSA { /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node rsa_attr(DataFlow::TypeTracker t, string attr_name) { attr_name in ["generate"] and ( t.start() and - result = DataFlow::importNode("Cryptodome.PublicKey.RSA" + "." + attr_name) + result = + DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey.RSA" + "." + attr_name) or t.startInAttr(attr_name) and result = rsa() @@ -160,34 +161,35 @@ private module CryptodomeModel { } /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node rsa_attr(string attr_name) { result = rsa_attr(DataFlow::TypeTracker::end(), attr_name) } - /** Gets a reference to the `Cryptodome.PublicKey.RSA.generate` function. */ + /** Gets a reference to the `Cryptodome.PublicKey.RSA.generate`/`Crypto.PublicKey.RSA.generate` function. */ DataFlow::Node generate() { result = rsa_attr("generate") } } // ------------------------------------------------------------------------- // Cryptodome.PublicKey.DSA // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey.DSA` module. */ + /** Gets a reference to the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module. */ DataFlow::Node dsa() { result = publicKey_attr("DSA") } - /** Provides models for the `Cryptodome.PublicKey.DSA` module */ + /** Provides models for the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module */ module DSA { /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node dsa_attr(DataFlow::TypeTracker t, string attr_name) { attr_name in ["generate"] and ( t.start() and - result = DataFlow::importNode("Cryptodome.PublicKey.DSA" + "." + attr_name) + result = + DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey.DSA" + "." + attr_name) or t.startInAttr(attr_name) and result = dsa() @@ -212,34 +214,35 @@ private module CryptodomeModel { } /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node dsa_attr(string attr_name) { result = dsa_attr(DataFlow::TypeTracker::end(), attr_name) } - /** Gets a reference to the `Cryptodome.PublicKey.DSA.generate` function. */ + /** Gets a reference to the `Cryptodome.PublicKey.DSA.generate`/`Crypto.PublicKey.DSA.generate` function. */ DataFlow::Node generate() { result = dsa_attr("generate") } } // ------------------------------------------------------------------------- // Cryptodome.PublicKey.ECC // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey.ECC` module. */ + /** Gets a reference to the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module. */ DataFlow::Node ecc() { result = publicKey_attr("ECC") } - /** Provides models for the `Cryptodome.PublicKey.ECC` module */ + /** Provides models for the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module */ module ECC { /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node ecc_attr(DataFlow::TypeTracker t, string attr_name) { attr_name in ["generate"] and ( t.start() and - result = DataFlow::importNode("Cryptodome.PublicKey.ECC" + "." + attr_name) + result = + DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey.ECC" + "." + attr_name) or t.startInAttr(attr_name) and result = ecc() @@ -264,14 +267,14 @@ private module CryptodomeModel { } /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC` module. + * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module. * WARNING: Only holds for a few predefined attributes. */ private DataFlow::Node ecc_attr(string attr_name) { result = ecc_attr(DataFlow::TypeTracker::end(), attr_name) } - /** Gets a reference to the `Cryptodome.PublicKey.ECC.generate` function. */ + /** Gets a reference to the `Cryptodome.PublicKey.ECC.generate`/`Crypto.PublicKey.ECC.generate` function. */ DataFlow::Node generate() { result = ecc_attr("generate") } } } @@ -279,7 +282,7 @@ private module CryptodomeModel { // --------------------------------------------------------------------------- /** - * A call to `Cryptodome.PublicKey.RSA.generate` + * A call to `Cryptodome.PublicKey.RSA.generate`/`Crypto.PublicKey.RSA.generate` * * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate */ @@ -297,7 +300,7 @@ private module CryptodomeModel { } /** - * A call to `Cryptodome.PublicKey.DSA.generate` + * A call to `Cryptodome.PublicKey.DSA.generate`/`Crypto.PublicKey.DSA.generate` * * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate */ @@ -315,7 +318,7 @@ private module CryptodomeModel { } /** - * A call to `Cryptodome.PublicKey.ECC.generate` + * A call to `Cryptodome.PublicKey.ECC.generate`/`Crypto.PublicKey.ECC.generate` * * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate */ diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py index 9d58b375187..a6c2c081845 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_dsa.py @@ -6,7 +6,7 @@ from Crypto.Signature import DSS from Crypto.Hash import SHA256 -private_key = DSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 +private_key = DSA.generate(2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py index fdf17571232..2b482b4aa4e 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_ec.py @@ -3,7 +3,7 @@ from Crypto.Signature import DSS from Crypto.Hash import SHA256 -private_key = ECC.generate(curve="P-256") # $ MISSING: PublicKeyGeneration keySize=256 +private_key = ECC.generate(curve="P-256") # $ PublicKeyGeneration keySize=256 public_key = private_key.public_key() # ------------------------------------------------------------------------------ diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py index 68cac4d7ad6..d2532e707e1 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py @@ -5,7 +5,7 @@ from Crypto.Cipher import PKCS1_OAEP from Crypto.Signature import pss from Crypto.Hash import SHA256 -private_key = RSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048 +private_key = RSA.generate(2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ From 2429c6c4508f3f4b1232aaebc301dc578dbb4875 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 16:50:42 +0100 Subject: [PATCH 091/757] Python: Rewrite py/weak-crypto-key tests * Removed backend arugment that is not required * Added DSA constants (they are just accidentially the same as RSA right now) * Removed FakeWeakEllipticCurve and used a real weak elliptic curve instead --- .../Security/CWE-326/WeakCrypto.expected | 16 ++--- .../Security/CWE-326/weak_crypto.py | 71 ++++++++++--------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.expected b/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.expected index e0e656f4bb3..a52d67eaff9 100644 --- a/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.expected +++ b/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.expected @@ -1,8 +1,8 @@ -| weak_crypto.py:67:1:67:30 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | -| weak_crypto.py:68:1:68:28 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:21:11:21:33 | ControlFlowNode for FakeWeakEllipticCurve() | 160 | -| weak_crypto.py:69:1:69:37 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | -| weak_crypto.py:71:1:71:39 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | -| weak_crypto.py:72:1:72:34 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:21:11:21:33 | ControlFlowNode for FakeWeakEllipticCurve() | 160 | -| weak_crypto.py:73:1:73:46 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | -| weak_crypto.py:75:1:75:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | -| weak_crypto.py:76:1:76:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:68:1:68:21 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:69:1:69:19 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:22:11:22:24 | ControlFlowNode for Attribute() | 163 | +| weak_crypto.py:70:1:70:28 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:72:1:72:30 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:73:1:73:25 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:22:11:22:24 | ControlFlowNode for Attribute() | 163 | +| weak_crypto.py:74:1:74:37 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:76:1:76:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:77:1:77:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | diff --git a/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py b/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py index 77a123f1617..a64d39bc866 100644 --- a/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py +++ b/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py @@ -1,7 +1,7 @@ from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import ec, dsa, rsa -#Crypto and Cryptodome have same API +# Crypto and Cryptodome have same API if random(): from Crypto.PublicKey import DSA from Crypto.PublicKey import RSA @@ -12,13 +12,14 @@ else: RSA_WEAK = 1024 RSA_OK = 2048 RSA_STRONG = 3076 + +DSA_WEAK = 1024 +DSA_OK = 2048 +DSA_STRONG = 3076 + BIG = 10000 -class FakeWeakEllipticCurve: - name = "fake" - key_size = 160 - -EC_WEAK = FakeWeakEllipticCurve() +EC_WEAK = ec.SECT163K1() # has key size of 163 EC_OK = ec.SECP224R1() EC_STRONG = ec.SECP384R1() EC_BIG = ec.SECT571R1() @@ -27,50 +28,50 @@ dsa_gen_key = dsa.generate_private_key ec_gen_key = ec.generate_private_key rsa_gen_key = rsa.generate_private_key -default = backends.default_backend() -#Strong and OK keys. -dsa_gen_key(key_size=RSA_OK, backend=default) -dsa_gen_key(key_size=RSA_STRONG, backend=default) -dsa_gen_key(key_size=BIG, backend=default) -ec_gen_key(curve=EC_OK, backend=default) -ec_gen_key(curve=EC_STRONG, backend=default) -ec_gen_key(curve=EC_BIG, backend=default) -rsa_gen_key(public_exponent=65537, key_size=RSA_OK, backend=default) -rsa_gen_key(public_exponent=65537, key_size=RSA_STRONG, backend=default) -rsa_gen_key(public_exponent=65537, key_size=BIG, backend=default) +# Strong and OK keys. + +dsa_gen_key(key_size=DSA_OK) +dsa_gen_key(key_size=DSA_STRONG) +dsa_gen_key(key_size=BIG) +ec_gen_key(curve=EC_OK) +ec_gen_key(curve=EC_STRONG) +ec_gen_key(curve=EC_BIG) +rsa_gen_key(public_exponent=65537, key_size=RSA_OK) +rsa_gen_key(public_exponent=65537, key_size=RSA_STRONG) +rsa_gen_key(public_exponent=65537, key_size=BIG) DSA.generate(bits=RSA_OK) DSA.generate(bits=RSA_STRONG) RSA.generate(bits=RSA_OK) RSA.generate(bits=RSA_STRONG) -dsa_gen_key(RSA_OK, default) -dsa_gen_key(RSA_STRONG, default) -dsa_gen_key(BIG, default) -ec_gen_key(EC_OK, default) -ec_gen_key(EC_STRONG, default) -ec_gen_key(EC_BIG, default) -rsa_gen_key(65537, RSA_OK, default) -rsa_gen_key(65537, RSA_STRONG, default) -rsa_gen_key(65537, BIG, default) +dsa_gen_key(DSA_OK) +dsa_gen_key(DSA_STRONG) +dsa_gen_key(BIG) +ec_gen_key(EC_OK) +ec_gen_key(EC_STRONG) +ec_gen_key(EC_BIG) +rsa_gen_key(65537, RSA_OK) +rsa_gen_key(65537, RSA_STRONG) +rsa_gen_key(65537, BIG) -DSA.generate(RSA_OK) -DSA.generate(RSA_STRONG) +DSA.generate(DSA_OK) +DSA.generate(DSA_STRONG) RSA.generate(RSA_OK) RSA.generate(RSA_STRONG) # Weak keys -dsa_gen_key(RSA_WEAK, default) -ec_gen_key(EC_WEAK, default) -rsa_gen_key(65537, RSA_WEAK, default) +dsa_gen_key(DSA_WEAK) +ec_gen_key(EC_WEAK) +rsa_gen_key(65537, RSA_WEAK) -dsa_gen_key(key_size=RSA_WEAK, default) -ec_gen_key(curve=EC_WEAK, default) -rsa_gen_key(65537, key_size=RSA_WEAK, default) +dsa_gen_key(key_size=DSA_WEAK) +ec_gen_key(curve=EC_WEAK) +rsa_gen_key(65537, key_size=RSA_WEAK) -DSA.generate(RSA_WEAK) +DSA.generate(DSA_WEAK) RSA.generate(RSA_WEAK) From 46ad611d576359a9505c53811ab5d964e4235792 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 17:10:36 +0100 Subject: [PATCH 092/757] Python: Port py/weak-crypto-key to use type-tracking instead of points-to. Looking at query results also made me realize I didn't supply a very good "origin" for ECC in cryptography package, so I improved that :+1: -- maybe that sohuld have been split into multiple commits... too late :( --- .../2021-02-02-port-weak-crypto-key-query.md | 2 + python/ql/src/Security/CWE-326/WeakCrypto.ql | 75 ++--------------- .../CWE-326/WeakCrypto.ql | 81 +++++++++++++++++++ .../semmle/python/frameworks/Cryptography.qll | 21 ++--- .../test/query-tests/Security/CWE-326/options | 1 - 5 files changed, 102 insertions(+), 78 deletions(-) create mode 100644 python/change-notes/2021-02-02-port-weak-crypto-key-query.md create mode 100644 python/ql/src/experimental/Security-old-dataflow/CWE-326/WeakCrypto.ql delete mode 100644 python/ql/test/query-tests/Security/CWE-326/options diff --git a/python/change-notes/2021-02-02-port-weak-crypto-key-query.md b/python/change-notes/2021-02-02-port-weak-crypto-key-query.md new file mode 100644 index 00000000000..20a2e2d1bdf --- /dev/null +++ b/python/change-notes/2021-02-02-port-weak-crypto-key-query.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Ported _Use of weak cryptographic key_ (`py/weak-crypto-key`) query to use new type-tracking approach instead of points-to. This might result in some difference in results being found, but overall this should result in a more robust and accurate analysis. diff --git a/python/ql/src/Security/CWE-326/WeakCrypto.ql b/python/ql/src/Security/CWE-326/WeakCrypto.ql index 27c1fcce429..5fb3494e3e6 100644 --- a/python/ql/src/Security/CWE-326/WeakCrypto.ql +++ b/python/ql/src/Security/CWE-326/WeakCrypto.ql @@ -10,72 +10,13 @@ */ import python +import semmle.python.Concepts +import semmle.python.dataflow.new.DataFlow -int minimumSecureKeySize(string algo) { - algo = "DSA" and result = 2048 - or - algo = "RSA" and result = 2048 - or - algo = "ECC" and result = 224 -} - -predicate dsaRsaKeySizeArg(FunctionValue func, string algorithm, string arg) { - exists(ModuleValue mod | func = mod.attr(_) | - algorithm = "DSA" and - ( - mod = Module::named("cryptography.hazmat.primitives.asymmetric.dsa") and arg = "key_size" - or - mod = Module::named("Crypto.PublicKey.DSA") and arg = "bits" - or - mod = Module::named("Cryptodome.PublicKey.DSA") and arg = "bits" - ) - or - algorithm = "RSA" and - ( - mod = Module::named("cryptography.hazmat.primitives.asymmetric.rsa") and arg = "key_size" - or - mod = Module::named("Crypto.PublicKey.RSA") and arg = "bits" - or - mod = Module::named("Cryptodome.PublicKey.RSA") and arg = "bits" - ) - ) -} - -predicate ecKeySizeArg(FunctionValue func, string arg) { - exists(ModuleValue mod | func = mod.attr(_) | - mod = Module::named("cryptography.hazmat.primitives.asymmetric.ec") and arg = "curve" - ) -} - -int keySizeFromCurve(ClassValue curveClass) { - result = curveClass.declaredAttribute("key_size").(NumericValue).getIntValue() -} - -predicate algorithmAndKeysizeForCall( - CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin -) { - exists(FunctionValue func, string argname, ControlFlowNode arg | - arg = func.getNamedArgumentForCall(call, argname) - | - exists(NumericValue key | - arg.pointsTo(key, keyOrigin) and - dsaRsaKeySizeArg(func, algorithm, argname) and - keySize = key.getIntValue() - ) - or - exists(Value curveClassInstance | - algorithm = "ECC" and - ecKeySizeArg(func, argname) and - arg.pointsTo(_, curveClassInstance, keyOrigin) and - keySize = keySizeFromCurve(curveClassInstance.getClass()) - ) - ) -} - -from CallNode call, string algo, int keySize, ControlFlowNode origin +from Cryptography::PublicKey::KeyGeneration keyGen, int keySize, DataFlow::Node origin where - algorithmAndKeysizeForCall(call, algo, keySize, origin) and - keySize < minimumSecureKeySize(algo) -select call, - "Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) + - " and considered breakable.", origin, keySize.toString() + keySize = keyGen.getKeySizeWithOrigin(origin) and + keySize < keyGen.minimumSecureKeySize() +select keyGen, + "Creation of an " + keyGen.getName() + " key uses $@ bits, which is below " + + keyGen.minimumSecureKeySize() + " and considered breakable.", origin, keySize.toString() diff --git a/python/ql/src/experimental/Security-old-dataflow/CWE-326/WeakCrypto.ql b/python/ql/src/experimental/Security-old-dataflow/CWE-326/WeakCrypto.ql new file mode 100644 index 00000000000..27c1fcce429 --- /dev/null +++ b/python/ql/src/experimental/Security-old-dataflow/CWE-326/WeakCrypto.ql @@ -0,0 +1,81 @@ +/** + * @name Use of weak cryptographic key + * @description Use of a cryptographic key that is too small may allow the encryption to be broken. + * @kind problem + * @problem.severity error + * @precision high + * @id py/weak-crypto-key + * @tags security + * external/cwe/cwe-326 + */ + +import python + +int minimumSecureKeySize(string algo) { + algo = "DSA" and result = 2048 + or + algo = "RSA" and result = 2048 + or + algo = "ECC" and result = 224 +} + +predicate dsaRsaKeySizeArg(FunctionValue func, string algorithm, string arg) { + exists(ModuleValue mod | func = mod.attr(_) | + algorithm = "DSA" and + ( + mod = Module::named("cryptography.hazmat.primitives.asymmetric.dsa") and arg = "key_size" + or + mod = Module::named("Crypto.PublicKey.DSA") and arg = "bits" + or + mod = Module::named("Cryptodome.PublicKey.DSA") and arg = "bits" + ) + or + algorithm = "RSA" and + ( + mod = Module::named("cryptography.hazmat.primitives.asymmetric.rsa") and arg = "key_size" + or + mod = Module::named("Crypto.PublicKey.RSA") and arg = "bits" + or + mod = Module::named("Cryptodome.PublicKey.RSA") and arg = "bits" + ) + ) +} + +predicate ecKeySizeArg(FunctionValue func, string arg) { + exists(ModuleValue mod | func = mod.attr(_) | + mod = Module::named("cryptography.hazmat.primitives.asymmetric.ec") and arg = "curve" + ) +} + +int keySizeFromCurve(ClassValue curveClass) { + result = curveClass.declaredAttribute("key_size").(NumericValue).getIntValue() +} + +predicate algorithmAndKeysizeForCall( + CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin +) { + exists(FunctionValue func, string argname, ControlFlowNode arg | + arg = func.getNamedArgumentForCall(call, argname) + | + exists(NumericValue key | + arg.pointsTo(key, keyOrigin) and + dsaRsaKeySizeArg(func, algorithm, argname) and + keySize = key.getIntValue() + ) + or + exists(Value curveClassInstance | + algorithm = "ECC" and + ecKeySizeArg(func, argname) and + arg.pointsTo(_, curveClassInstance, keyOrigin) and + keySize = keySizeFromCurve(curveClassInstance.getClass()) + ) + ) +} + +from CallNode call, string algo, int keySize, ControlFlowNode origin +where + algorithmAndKeysizeForCall(call, algo, keySize, origin) and + keySize < minimumSecureKeySize(algo) +select call, + "Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) + + " and considered breakable.", origin, keySize.toString() diff --git a/python/ql/src/semmle/python/frameworks/Cryptography.qll b/python/ql/src/semmle/python/frameworks/Cryptography.qll index 3f7ae145af1..cd37c8662b4 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptography.qll @@ -423,22 +423,23 @@ private module CryptographyModel { result = ec_attr("BrainpoolP512R1") and keySize = 512 } - /** Gets a predefined curve class instance with a specific key size (in bits). */ + /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ private DataFlow::Node curveClassInstanceWithKeySize( - DataFlow::TypeTracker t, int keySize + DataFlow::TypeTracker t, int keySize, DataFlow::Node origin ) { t.start() and result.asCfgNode().(CallNode).getFunction() = - curveClassWithKeySize(keySize).asCfgNode() + curveClassWithKeySize(keySize).asCfgNode() and + origin = result or exists(DataFlow::TypeTracker t2 | - result = curveClassInstanceWithKeySize(t2, keySize).track(t2, t) + result = curveClassInstanceWithKeySize(t2, keySize, origin).track(t2, t) ) } - /** Gets a predefined curve class instance with a specific key size (in bits). */ - DataFlow::Node curveClassInstanceWithKeySize(int keySize) { - result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize) + /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ + DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) { + result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin) } } } @@ -505,9 +506,9 @@ private module CryptographyModel { } override int getKeySizeWithOrigin(DataFlow::Node origin) { - origin = this.getCurveArg() and - origin = - cryptography::hazmat::primitives::asymmetric::ec::curveClassInstanceWithKeySize(result) + this.getCurveArg() = + cryptography::hazmat::primitives::asymmetric::ec::curveClassInstanceWithKeySize(result, + origin) } // Note: There is not really a key-size argument, since it's always specified by the curve. diff --git a/python/ql/test/query-tests/Security/CWE-326/options b/python/ql/test/query-tests/Security/CWE-326/options deleted file mode 100644 index 492768b3481..00000000000 --- a/python/ql/test/query-tests/Security/CWE-326/options +++ /dev/null @@ -1 +0,0 @@ -semmle-extractor-options: -p ../lib/ --max-import-depth=3 From 0e9a54e9a98fef3a209b5490bff10f35c10b4ffd Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 2 Feb 2021 17:14:56 +0100 Subject: [PATCH 093/757] Python: Rename WeakCrypto to WeakCryptoKey Since WeakCrypto always makes me think that it's about all weak crypto (like using MD5, or completely broken ciphers such as ARC4 ro DES) and not just about weak key generation. --- python/change-notes/2021-02-02-port-weak-crypto-key-query.md | 1 + .../Security/CWE-326/{WeakCrypto.qhelp => WeakCryptoKey.qhelp} | 0 .../ql/src/Security/CWE-326/{WeakCrypto.ql => WeakCryptoKey.ql} | 0 python/ql/test/query-tests/Security/CWE-326/WeakCrypto.qlref | 1 - .../CWE-326/{WeakCrypto.expected => WeakCryptoKey.expected} | 0 python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.qlref | 1 + 6 files changed, 2 insertions(+), 1 deletion(-) rename python/ql/src/Security/CWE-326/{WeakCrypto.qhelp => WeakCryptoKey.qhelp} (100%) rename python/ql/src/Security/CWE-326/{WeakCrypto.ql => WeakCryptoKey.ql} (100%) delete mode 100644 python/ql/test/query-tests/Security/CWE-326/WeakCrypto.qlref rename python/ql/test/query-tests/Security/CWE-326/{WeakCrypto.expected => WeakCryptoKey.expected} (100%) create mode 100644 python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.qlref diff --git a/python/change-notes/2021-02-02-port-weak-crypto-key-query.md b/python/change-notes/2021-02-02-port-weak-crypto-key-query.md index 20a2e2d1bdf..93897c586e8 100644 --- a/python/change-notes/2021-02-02-port-weak-crypto-key-query.md +++ b/python/change-notes/2021-02-02-port-weak-crypto-key-query.md @@ -1,2 +1,3 @@ lgtm,codescanning * Ported _Use of weak cryptographic key_ (`py/weak-crypto-key`) query to use new type-tracking approach instead of points-to. This might result in some difference in results being found, but overall this should result in a more robust and accurate analysis. +* Renamed the query file for _Use of weak cryptographic key_ (`py/weak-crypto-key`) from `WeakCrypto.ql` to `WeakCryptoKey.ql` (in the `python/ql/src/Security/CWE-326/` folder), which could impact custom query suites that include/exclude this query by using it's path. diff --git a/python/ql/src/Security/CWE-326/WeakCrypto.qhelp b/python/ql/src/Security/CWE-326/WeakCryptoKey.qhelp similarity index 100% rename from python/ql/src/Security/CWE-326/WeakCrypto.qhelp rename to python/ql/src/Security/CWE-326/WeakCryptoKey.qhelp diff --git a/python/ql/src/Security/CWE-326/WeakCrypto.ql b/python/ql/src/Security/CWE-326/WeakCryptoKey.ql similarity index 100% rename from python/ql/src/Security/CWE-326/WeakCrypto.ql rename to python/ql/src/Security/CWE-326/WeakCryptoKey.ql diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.qlref b/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.qlref deleted file mode 100644 index 75676139ac3..00000000000 --- a/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.qlref +++ /dev/null @@ -1 +0,0 @@ -Security/CWE-326/WeakCrypto.ql diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCrypto.expected b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected similarity index 100% rename from python/ql/test/query-tests/Security/CWE-326/WeakCrypto.expected rename to python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.qlref b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.qlref new file mode 100644 index 00000000000..70a66eef06e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.qlref @@ -0,0 +1 @@ +Security/CWE-326/WeakCryptoKey.ql From 32d0790500f234e52a1a8eceae3813052cf076c4 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 3 Feb 2021 13:16:56 +0100 Subject: [PATCH 094/757] Python: Use camelCase for RSA/DSA/ECC after asking around, this seems to be the right approach --- python/ql/src/semmle/python/Concepts.qll | 6 +++--- .../ql/src/semmle/python/frameworks/Cryptodome.qll | 10 +++++----- .../ql/src/semmle/python/frameworks/Cryptography.qll | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll index 1f04b077834..989003a1e8a 100644 --- a/python/ql/src/semmle/python/Concepts.qll +++ b/python/ql/src/semmle/python/Concepts.qll @@ -591,21 +591,21 @@ module Cryptography { } /** A data-flow node that generates a new RSA key-pair. */ - abstract class RSARange extends Range { + abstract class RsaRange extends Range { override string getName() { result = "RSA" } override int minimumSecureKeySize() { result = 2048 } } /** A data-flow node that generates a new DSA key-pair. */ - abstract class DSARange extends Range { + abstract class DsaRange extends Range { override string getName() { result = "DSA" } override int minimumSecureKeySize() { result = 2048 } } /** A data-flow node that generates a new ECC key-pair. */ - abstract class ECCRange extends Range { + abstract class EccRange extends Range { override string getName() { result = "ECC" } override int minimumSecureKeySize() { result = 224 } diff --git a/python/ql/src/semmle/python/frameworks/Cryptodome.qll b/python/ql/src/semmle/python/frameworks/Cryptodome.qll index b598cc76677..cd8f589bcd6 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptodome.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptodome.qll @@ -286,11 +286,11 @@ private module CryptodomeModel { * * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate */ - class CryptodomePublicKeyRSAGenerateCall extends Cryptography::PublicKey::KeyGeneration::RSARange, + class CryptodomePublicKeyRsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::RsaRange, DataFlow::CfgNode { override CallNode node; - CryptodomePublicKeyRSAGenerateCall() { + CryptodomePublicKeyRsaGenerateCall() { node.getFunction() = Cryptodome::PublicKey::RSA::generate().asCfgNode() } @@ -304,11 +304,11 @@ private module CryptodomeModel { * * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate */ - class CryptodomePublicKeyDSAGenerateCall extends Cryptography::PublicKey::KeyGeneration::DSARange, + class CryptodomePublicKeyDsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::DsaRange, DataFlow::CfgNode { override CallNode node; - CryptodomePublicKeyDSAGenerateCall() { + CryptodomePublicKeyDsaGenerateCall() { node.getFunction() = Cryptodome::PublicKey::DSA::generate().asCfgNode() } @@ -322,7 +322,7 @@ private module CryptodomeModel { * * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate */ - class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::ECCRange, + class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::EccRange, DataFlow::CfgNode { override CallNode node; diff --git a/python/ql/src/semmle/python/frameworks/Cryptography.qll b/python/ql/src/semmle/python/frameworks/Cryptography.qll index cd37c8662b4..94f3d5c230a 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptography.qll @@ -453,11 +453,11 @@ private module CryptographyModel { * * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html#cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key */ - class CryptographyRSAGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::RSARange, + class CryptographyRsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::RsaRange, DataFlow::CfgNode { override CallNode node; - CryptographyRSAGeneratePrivateKeyCall() { + CryptographyRsaGeneratePrivateKeyCall() { node.getFunction() = cryptography::hazmat::primitives::asymmetric::rsa::generate_private_key().asCfgNode() } @@ -472,11 +472,11 @@ private module CryptographyModel { * * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html#cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key */ - class CryptographyDSAGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::DSARange, + class CryptographyDsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::DsaRange, DataFlow::CfgNode { override CallNode node; - CryptographyDSAGeneratePrivateKeyCall() { + CryptographyDsaGeneratePrivateKeyCall() { node.getFunction() = cryptography::hazmat::primitives::asymmetric::dsa::generate_private_key().asCfgNode() } @@ -491,11 +491,11 @@ private module CryptographyModel { * * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#cryptography.hazmat.primitives.asymmetric.ec.generate_private_key */ - class CryptographyECGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::ECCRange, + class CryptographyEcGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::EccRange, DataFlow::CfgNode { override CallNode node; - CryptographyECGeneratePrivateKeyCall() { + CryptographyEcGeneratePrivateKeyCall() { node.getFunction() = cryptography::hazmat::primitives::asymmetric::ec::generate_private_key().asCfgNode() } From 8d3170bcb465133ee272c559256e7a2fbe3e60c9 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 16 Feb 2021 11:07:58 +0100 Subject: [PATCH 095/757] Python: Fix bad join in crypto models --- .../src/semmle/python/frameworks/Cryptography.qll | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/python/ql/src/semmle/python/frameworks/Cryptography.qll b/python/ql/src/semmle/python/frameworks/Cryptography.qll index 94f3d5c230a..02da624c31f 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptography.qll @@ -432,11 +432,24 @@ private module CryptographyModel { curveClassWithKeySize(keySize).asCfgNode() and origin = result or + // Due to bad performance when using normal setup with we have inlined that code and forced a join exists(DataFlow::TypeTracker t2 | - result = curveClassInstanceWithKeySize(t2, keySize, origin).track(t2, t) + exists(DataFlow::StepSummary summary | + curveClassInstanceWithKeySize_first_join(t2, keySize, origin, result, summary) and + t = t2.append(summary) + ) ) } + pragma[nomagic] + private predicate curveClassInstanceWithKeySize_first_join( + DataFlow::TypeTracker t2, int keySize, DataFlow::Node origin, DataFlow::Node res, + DataFlow::StepSummary summary + ) { + DataFlow::StepSummary::step(curveClassInstanceWithKeySize(t2, keySize, origin), res, + summary) + } + /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) { result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin) From bfbaa8527230229887f7200c82dfe530e0b6ef23 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 16 Feb 2021 15:47:39 +0100 Subject: [PATCH 096/757] Python: Add test of public_key method with cryptodome Added in 3.10 release https://github.com/Legrandin/pycryptodome/blob/master/Changelog.rst#3100-6-february-2021 --- .../experimental/library-tests/frameworks/crypto/test_rsa.py | 3 +++ .../library-tests/frameworks/cryptodome/test_rsa.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py index d2532e707e1..7d463e4f384 100644 --- a/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/crypto/test_rsa.py @@ -6,7 +6,10 @@ from Crypto.Signature import pss from Crypto.Hash import SHA256 private_key = RSA.generate(2048) # $ PublicKeyGeneration keySize=2048 + +# These 2 methods do the same public_key = private_key.publickey() +public_key = private_key.public_key() # ------------------------------------------------------------------------------ # encrypt/decrypt diff --git a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py index fd1feccb29b..cee261e5ebe 100644 --- a/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py +++ b/python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py @@ -6,7 +6,10 @@ from Cryptodome.Signature import pss from Cryptodome.Hash import SHA256 private_key = RSA.generate(2048) # $ PublicKeyGeneration keySize=2048 + +# These 2 methods do the same public_key = private_key.publickey() +public_key = private_key.public_key() # ------------------------------------------------------------------------------ # encrypt/decrypt From 1eabfbd0e4a776c479445969b6420aa1b467a464 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 17 Feb 2021 14:12:04 +0100 Subject: [PATCH 097/757] Python: Port cryptography models to use API graphs (mostly) --- .../semmle/python/frameworks/Cryptography.qll | 558 ++++-------------- 1 file changed, 106 insertions(+), 452 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Cryptography.qll b/python/ql/src/semmle/python/frameworks/Cryptography.qll index 02da624c31f..1c1e309fcce 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptography.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptography.qll @@ -6,457 +6,103 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts +private import semmle.python.ApiGraphs /** * Provides models for the `cryptography` PyPI package. * See https://cryptography.io/en/latest/. */ private module CryptographyModel { - // --------------------------------------------------------------------------- - // cryptography - // --------------------------------------------------------------------------- - /** Gets a reference to the `cryptography` module. */ - private DataFlow::Node cryptography(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("cryptography") - or - exists(DataFlow::TypeTracker t2 | result = cryptography(t2).track(t2, t)) - } - - /** Gets a reference to the `cryptography` module. */ - DataFlow::Node cryptography() { result = cryptography(DataFlow::TypeTracker::end()) } - - /** Provides models for the `cryptography` module. */ - module cryptography { + /** + * Provides helper predicates for the eliptic curve cryptography parts in + * `cryptography.hazmat.primitives.asymmetric.ec`. + */ + module Ecc { /** - * Gets a reference to the attribute `attr_name` of the `cryptography` module. - * WARNING: Only holds for a few predefined attributes. + * Gets a predefined curve class from + * `cryptography.hazmat.primitives.asymmetric.ec` with a specific key size (in bits). */ - private DataFlow::Node cryptography_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["hazmat"] and - ( - t.start() and - result = DataFlow::importNode("cryptography" + "." + attr_name) + private DataFlow::Node curveClassWithKeySize(int keySize) { + exists(string curveName | + result = + API::moduleImport("cryptography") + .getMember("hazmat") + .getMember("primitives") + .getMember("asymmetric") + .getMember("ec") + .getMember(curveName) + .getAUse() + | + // obtained by manually looking at source code in + // https://github.com/pyca/cryptography/blob/cba69f1922803f4f29a3fde01741890d88b8e217/src/cryptography/hazmat/primitives/asymmetric/ec.py#L208-L300 + curveName = "SECT571R1" and keySize = 570 or - t.startInAttr(attr_name) and - result = cryptography() + curveName = "SECT409R1" and keySize = 409 + or + curveName = "SECT283R1" and keySize = 283 + or + curveName = "SECT233R1" and keySize = 233 + or + curveName = "SECT163R2" and keySize = 163 + or + curveName = "SECT571K1" and keySize = 571 + or + curveName = "SECT409K1" and keySize = 409 + or + curveName = "SECT283K1" and keySize = 283 + or + curveName = "SECT233K1" and keySize = 233 + or + curveName = "SECT163K1" and keySize = 163 + or + curveName = "SECP521R1" and keySize = 521 + or + curveName = "SECP384R1" and keySize = 384 + or + curveName = "SECP256R1" and keySize = 256 + or + curveName = "SECP256K1" and keySize = 256 + or + curveName = "SECP224R1" and keySize = 224 + or + curveName = "SECP192R1" and keySize = 192 + or + curveName = "BrainpoolP256R1" and keySize = 256 + or + curveName = "BrainpoolP384R1" and keySize = 384 + or + curveName = "BrainpoolP512R1" and keySize = 512 ) + } + + /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ + private DataFlow::Node curveClassInstanceWithKeySize( + DataFlow::TypeTracker t, int keySize, DataFlow::Node origin + ) { + t.start() and + result.asCfgNode().(CallNode).getFunction() = curveClassWithKeySize(keySize).asCfgNode() and + origin = result or - // Due to bad performance when using normal setup with `cryptography_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join + // Due to bad performance when using normal setup with we have inlined that code and forced a join exists(DataFlow::TypeTracker t2 | exists(DataFlow::StepSummary summary | - cryptography_attr_first_join(t2, attr_name, result, summary) and + curveClassInstanceWithKeySize_first_join(t2, keySize, origin, result, summary) and t = t2.append(summary) ) ) } pragma[nomagic] - private predicate cryptography_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary + private predicate curveClassInstanceWithKeySize_first_join( + DataFlow::TypeTracker t2, int keySize, DataFlow::Node origin, DataFlow::Node res, + DataFlow::StepSummary summary ) { - DataFlow::StepSummary::step(cryptography_attr(t2, attr_name), res, summary) + DataFlow::StepSummary::step(curveClassInstanceWithKeySize(t2, keySize, origin), res, summary) } - /** - * Gets a reference to the attribute `attr_name` of the `cryptography` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node cryptography_attr(string attr_name) { - result = cryptography_attr(DataFlow::TypeTracker::end(), attr_name) - } - - // ------------------------------------------------------------------------- - // cryptography.hazmat - // ------------------------------------------------------------------------- - /** Gets a reference to the `cryptography.hazmat` module. */ - DataFlow::Node hazmat() { result = cryptography_attr("hazmat") } - - /** Provides models for the `cryptography.hazmat` module */ - module hazmat { - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node hazmat_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["primitives"] and - ( - t.start() and - result = DataFlow::importNode("cryptography.hazmat" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = hazmat() - ) - or - // Due to bad performance when using normal setup with `hazmat_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - hazmat_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate hazmat_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(hazmat_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node hazmat_attr(string attr_name) { - result = hazmat_attr(DataFlow::TypeTracker::end(), attr_name) - } - - // ------------------------------------------------------------------------- - // cryptography.hazmat.primitives - // ------------------------------------------------------------------------- - /** Gets a reference to the `cryptography.hazmat.primitives` module. */ - DataFlow::Node primitives() { result = hazmat_attr("primitives") } - - /** Provides models for the `cryptography.hazmat.primitives` module */ - module primitives { - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node primitives_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["asymmetric"] and - ( - t.start() and - result = DataFlow::importNode("cryptography.hazmat.primitives" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = primitives() - ) - or - // Due to bad performance when using normal setup with `primitives_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - primitives_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate primitives_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(primitives_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node primitives_attr(string attr_name) { - result = primitives_attr(DataFlow::TypeTracker::end(), attr_name) - } - - // ------------------------------------------------------------------------- - // cryptography.hazmat.primitives.asymmetric - // ------------------------------------------------------------------------- - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric` module. */ - DataFlow::Node asymmetric() { result = primitives_attr("asymmetric") } - - /** Provides models for the `cryptography.hazmat.primitives.asymmetric` module */ - module asymmetric { - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node asymmetric_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["rsa", "dsa", "ec"] and - ( - t.start() and - result = - DataFlow::importNode("cryptography.hazmat.primitives.asymmetric" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = asymmetric() - ) - or - // Due to bad performance when using normal setup with `asymmetric_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - asymmetric_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate asymmetric_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(asymmetric_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node asymmetric_attr(string attr_name) { - result = asymmetric_attr(DataFlow::TypeTracker::end(), attr_name) - } - - // ------------------------------------------------------------------------- - // cryptography.hazmat.primitives.asymmetric.rsa - // ------------------------------------------------------------------------- - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.rsa` module. */ - DataFlow::Node rsa() { result = asymmetric_attr("rsa") } - - /** Provides models for the `cryptography.hazmat.primitives.asymmetric.rsa` module */ - module rsa { - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.rsa` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node rsa_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["generate_private_key"] and - ( - t.start() and - result = - DataFlow::importNode("cryptography.hazmat.primitives.asymmetric.rsa" + "." + - attr_name) - or - t.startInAttr(attr_name) and - result = rsa() - ) - or - // Due to bad performance when using normal setup with `rsa_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - rsa_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate rsa_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(rsa_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.rsa` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node rsa_attr(string attr_name) { - result = rsa_attr(DataFlow::TypeTracker::end(), attr_name) - } - - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key` function. */ - DataFlow::Node generate_private_key() { result = rsa_attr("generate_private_key") } - } - - // ------------------------------------------------------------------------- - // cryptography.hazmat.primitives.asymmetric.dsa - // ------------------------------------------------------------------------- - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.dsa` module. */ - DataFlow::Node dsa() { result = asymmetric_attr("dsa") } - - /** Provides models for the `cryptography.hazmat.primitives.asymmetric.dsa` module */ - module dsa { - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.dsa` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node dsa_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["generate_private_key"] and - ( - t.start() and - result = - DataFlow::importNode("cryptography.hazmat.primitives.asymmetric.dsa" + "." + - attr_name) - or - t.startInAttr(attr_name) and - result = dsa() - ) - or - // Due to bad performance when using normal setup with `dsa_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - dsa_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate dsa_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(dsa_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.dsa` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node dsa_attr(string attr_name) { - result = dsa_attr(DataFlow::TypeTracker::end(), attr_name) - } - - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key` function. */ - DataFlow::Node generate_private_key() { result = dsa_attr("generate_private_key") } - } - - // ------------------------------------------------------------------------- - // cryptography.hazmat.primitives.asymmetric.ec - // ------------------------------------------------------------------------- - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.ec` module. */ - DataFlow::Node ec() { result = asymmetric_attr("ec") } - - /** Provides models for the `cryptography.hazmat.primitives.asymmetric.ec` module */ - module ec { - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.ec` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node ec_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in [ - "generate_private_key", - // curves - "SECT571R1", "SECT409R1", "SECT283R1", "SECT233R1", "SECT163R2", "SECT571K1", - "SECT409K1", "SECT283K1", "SECT233K1", "SECT163K1", "SECP521R1", "SECP384R1", - "SECP256R1", "SECP256K1", "SECP224R1", "SECP192R1", "BrainpoolP256R1", - "BrainpoolP384R1", "BrainpoolP512R1" - ] and - ( - t.start() and - result = - DataFlow::importNode("cryptography.hazmat.primitives.asymmetric.ec" + "." + - attr_name) - or - t.startInAttr(attr_name) and - result = ec() - ) - or - // Due to bad performance when using normal setup with `ec_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - ec_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate ec_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(ec_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `cryptography.hazmat.primitives.asymmetric.ec` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node ec_attr(string attr_name) { - result = ec_attr(DataFlow::TypeTracker::end(), attr_name) - } - - /** Gets a reference to the `cryptography.hazmat.primitives.asymmetric.ec.generate_private_key` function. */ - DataFlow::Node generate_private_key() { result = ec_attr("generate_private_key") } - - /** Gets a predefined curve class with a specific key size (in bits). */ - DataFlow::Node curveClassWithKeySize(int keySize) { - // obtained by manually looking at source code in - // https://github.com/pyca/cryptography/blob/cba69f1922803f4f29a3fde01741890d88b8e217/src/cryptography/hazmat/primitives/asymmetric/ec.py#L208-L300 - result = ec_attr("SECT571R1") and keySize = 570 - or - result = ec_attr("SECT409R1") and keySize = 409 - or - result = ec_attr("SECT283R1") and keySize = 283 - or - result = ec_attr("SECT233R1") and keySize = 233 - or - result = ec_attr("SECT163R2") and keySize = 163 - or - result = ec_attr("SECT571K1") and keySize = 571 - or - result = ec_attr("SECT409K1") and keySize = 409 - or - result = ec_attr("SECT283K1") and keySize = 283 - or - result = ec_attr("SECT233K1") and keySize = 233 - or - result = ec_attr("SECT163K1") and keySize = 163 - or - result = ec_attr("SECP521R1") and keySize = 521 - or - result = ec_attr("SECP384R1") and keySize = 384 - or - result = ec_attr("SECP256R1") and keySize = 256 - or - result = ec_attr("SECP256K1") and keySize = 256 - or - result = ec_attr("SECP224R1") and keySize = 224 - or - result = ec_attr("SECP192R1") and keySize = 192 - or - result = ec_attr("BrainpoolP256R1") and keySize = 256 - or - result = ec_attr("BrainpoolP384R1") and keySize = 384 - or - result = ec_attr("BrainpoolP512R1") and keySize = 512 - } - - /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ - private DataFlow::Node curveClassInstanceWithKeySize( - DataFlow::TypeTracker t, int keySize, DataFlow::Node origin - ) { - t.start() and - result.asCfgNode().(CallNode).getFunction() = - curveClassWithKeySize(keySize).asCfgNode() and - origin = result - or - // Due to bad performance when using normal setup with we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - curveClassInstanceWithKeySize_first_join(t2, keySize, origin, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate curveClassInstanceWithKeySize_first_join( - DataFlow::TypeTracker t2, int keySize, DataFlow::Node origin, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(curveClassInstanceWithKeySize(t2, keySize, origin), res, - summary) - } - - /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ - DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) { - result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin) - } - } - } - } + /** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */ + DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) { + result = curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin) } } @@ -467,16 +113,20 @@ private module CryptographyModel { * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html#cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key */ class CryptographyRsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::RsaRange, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { CryptographyRsaGeneratePrivateKeyCall() { - node.getFunction() = - cryptography::hazmat::primitives::asymmetric::rsa::generate_private_key().asCfgNode() + this = + API::moduleImport("cryptography") + .getMember("hazmat") + .getMember("primitives") + .getMember("asymmetric") + .getMember("rsa") + .getMember("generate_private_key") + .getACall() } override DataFlow::Node getKeySizeArg() { - result.asCfgNode() in [node.getArg(1), node.getArgByName("key_size")] + result in [this.getArg(1), this.getArgByName("key_size")] } } @@ -486,16 +136,20 @@ private module CryptographyModel { * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html#cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key */ class CryptographyDsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::DsaRange, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { CryptographyDsaGeneratePrivateKeyCall() { - node.getFunction() = - cryptography::hazmat::primitives::asymmetric::dsa::generate_private_key().asCfgNode() + this = + API::moduleImport("cryptography") + .getMember("hazmat") + .getMember("primitives") + .getMember("asymmetric") + .getMember("dsa") + .getMember("generate_private_key") + .getACall() } override DataFlow::Node getKeySizeArg() { - result.asCfgNode() in [node.getArg(0), node.getArgByName("key_size")] + result in [this.getArg(0), this.getArgByName("key_size")] } } @@ -505,23 +159,23 @@ private module CryptographyModel { * See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#cryptography.hazmat.primitives.asymmetric.ec.generate_private_key */ class CryptographyEcGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::EccRange, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { CryptographyEcGeneratePrivateKeyCall() { - node.getFunction() = - cryptography::hazmat::primitives::asymmetric::ec::generate_private_key().asCfgNode() + this = + API::moduleImport("cryptography") + .getMember("hazmat") + .getMember("primitives") + .getMember("asymmetric") + .getMember("ec") + .getMember("generate_private_key") + .getACall() } /** Gets the argument that specifies the curve to use. */ - DataFlow::Node getCurveArg() { - result.asCfgNode() in [node.getArg(0), node.getArgByName("curve")] - } + DataFlow::Node getCurveArg() { result in [this.getArg(0), this.getArgByName("curve")] } override int getKeySizeWithOrigin(DataFlow::Node origin) { - this.getCurveArg() = - cryptography::hazmat::primitives::asymmetric::ec::curveClassInstanceWithKeySize(result, - origin) + this.getCurveArg() = Ecc::curveClassInstanceWithKeySize(result, origin) } // Note: There is not really a key-size argument, since it's always specified by the curve. From 2a8f720bc6e20250b618b0e5e86f876919996834 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 17 Feb 2021 14:31:33 +0100 Subject: [PATCH 098/757] Python: Port cryptodome models to use API graphs --- .../semmle/python/frameworks/Cryptodome.qll | 307 ++---------------- 1 file changed, 27 insertions(+), 280 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Cryptodome.qll b/python/ql/src/semmle/python/frameworks/Cryptodome.qll index cd8f589bcd6..bd28da1067d 100644 --- a/python/ql/src/semmle/python/frameworks/Cryptodome.qll +++ b/python/ql/src/semmle/python/frameworks/Cryptodome.qll @@ -8,6 +8,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts +private import semmle.python.ApiGraphs /** * Provides models for @@ -16,270 +17,6 @@ private import semmle.python.Concepts * See https://pycryptodome.readthedocs.io/en/latest/ */ private module CryptodomeModel { - // --------------------------------------------------------------------------- - // Cryptodome - // --------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome`/`Crypto` module. */ - private DataFlow::Node cryptodome(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode(["Cryptodome", "Crypto"]) - or - exists(DataFlow::TypeTracker t2 | result = cryptodome(t2).track(t2, t)) - } - - /** Gets a reference to the `Cryptodome`/`Crypto` module. */ - DataFlow::Node cryptodome() { result = cryptodome(DataFlow::TypeTracker::end()) } - - /** Provides models for the `Cryptodome`/`Crypto` module. */ - module Cryptodome { - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome`/`Crypto` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node cryptodome_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["PublicKey"] and - ( - t.start() and - result = DataFlow::importNode(["Cryptodome", "Crypto"] + "." + attr_name) - or - t.startInAttr(attr_name) and - result = cryptodome() - ) - or - // Due to bad performance when using normal setup with `cryptodome_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - cryptodome_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate cryptodome_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(cryptodome_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome`/`Crypto` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node cryptodome_attr(string attr_name) { - result = cryptodome_attr(DataFlow::TypeTracker::end(), attr_name) - } - - // ------------------------------------------------------------------------- - // Cryptodome.PublicKey - // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey`/`Crypto.PublicKey` module. */ - DataFlow::Node publicKey() { result = cryptodome_attr("PublicKey") } - - /** Provides models for the `Cryptodome.PublicKey`/`Crypto.PublicKey` module */ - module PublicKey { - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey`/`Crypto.PublicKey` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node publicKey_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["RSA", "DSA", "ECC"] and - ( - t.start() and - result = DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = publicKey() - ) - or - // Due to bad performance when using normal setup with `publicKey_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - publicKey_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate publicKey_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(publicKey_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey`/`Crypto.PublicKey` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node publicKey_attr(string attr_name) { - result = publicKey_attr(DataFlow::TypeTracker::end(), attr_name) - } - - // ------------------------------------------------------------------------- - // Cryptodome.PublicKey.RSA - // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module. */ - DataFlow::Node rsa() { result = publicKey_attr("RSA") } - - /** Provides models for the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module */ - module RSA { - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node rsa_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["generate"] and - ( - t.start() and - result = - DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey.RSA" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = rsa() - ) - or - // Due to bad performance when using normal setup with `rsa_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - rsa_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate rsa_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(rsa_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA`/`Crypto.PublicKey.RSA` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node rsa_attr(string attr_name) { - result = rsa_attr(DataFlow::TypeTracker::end(), attr_name) - } - - /** Gets a reference to the `Cryptodome.PublicKey.RSA.generate`/`Crypto.PublicKey.RSA.generate` function. */ - DataFlow::Node generate() { result = rsa_attr("generate") } - } - - // ------------------------------------------------------------------------- - // Cryptodome.PublicKey.DSA - // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module. */ - DataFlow::Node dsa() { result = publicKey_attr("DSA") } - - /** Provides models for the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module */ - module DSA { - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node dsa_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["generate"] and - ( - t.start() and - result = - DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey.DSA" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = dsa() - ) - or - // Due to bad performance when using normal setup with `dsa_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - dsa_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate dsa_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(dsa_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA`/`Crypto.PublicKey.DSA` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node dsa_attr(string attr_name) { - result = dsa_attr(DataFlow::TypeTracker::end(), attr_name) - } - - /** Gets a reference to the `Cryptodome.PublicKey.DSA.generate`/`Crypto.PublicKey.DSA.generate` function. */ - DataFlow::Node generate() { result = dsa_attr("generate") } - } - - // ------------------------------------------------------------------------- - // Cryptodome.PublicKey.ECC - // ------------------------------------------------------------------------- - /** Gets a reference to the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module. */ - DataFlow::Node ecc() { result = publicKey_attr("ECC") } - - /** Provides models for the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module */ - module ECC { - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node ecc_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["generate"] and - ( - t.start() and - result = - DataFlow::importNode(["Cryptodome", "Crypto"] + ".PublicKey.ECC" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = ecc() - ) - or - // Due to bad performance when using normal setup with `ecc_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - ecc_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate ecc_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(ecc_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC`/`Crypto.PublicKey.ECC` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node ecc_attr(string attr_name) { - result = ecc_attr(DataFlow::TypeTracker::end(), attr_name) - } - - /** Gets a reference to the `Cryptodome.PublicKey.ECC.generate`/`Crypto.PublicKey.ECC.generate` function. */ - DataFlow::Node generate() { result = ecc_attr("generate") } - } - } - } - // --------------------------------------------------------------------------- /** * A call to `Cryptodome.PublicKey.RSA.generate`/`Crypto.PublicKey.RSA.generate` @@ -287,15 +24,18 @@ private module CryptodomeModel { * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate */ class CryptodomePublicKeyRsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::RsaRange, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { CryptodomePublicKeyRsaGenerateCall() { - node.getFunction() = Cryptodome::PublicKey::RSA::generate().asCfgNode() + this = + API::moduleImport(["Crypto", "Cryptodome"]) + .getMember("PublicKey") + .getMember("RSA") + .getMember("generate") + .getACall() } override DataFlow::Node getKeySizeArg() { - result.asCfgNode() in [node.getArg(0), node.getArgByName("bits")] + result in [this.getArg(0), this.getArgByName("bits")] } } @@ -305,15 +45,18 @@ private module CryptodomeModel { * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate */ class CryptodomePublicKeyDsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::DsaRange, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { CryptodomePublicKeyDsaGenerateCall() { - node.getFunction() = Cryptodome::PublicKey::DSA::generate().asCfgNode() + this = + API::moduleImport(["Crypto", "Cryptodome"]) + .getMember("PublicKey") + .getMember("DSA") + .getMember("generate") + .getACall() } override DataFlow::Node getKeySizeArg() { - result.asCfgNode() in [node.getArg(0), node.getArgByName("bits")] + result in [this.getArg(0), this.getArgByName("bits")] } } @@ -323,16 +66,20 @@ private module CryptodomeModel { * See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate */ class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::EccRange, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { CryptodomePublicKeyEccGenerateCall() { - node.getFunction() = Cryptodome::PublicKey::ECC::generate().asCfgNode() + this = + API::moduleImport(["Crypto", "Cryptodome"]) + .getMember("PublicKey") + .getMember("ECC") + .getMember("generate") + .getACall() } /** Gets the argument that specifies the curve to use (a string). */ - DataFlow::Node getCurveArg() { result.asCfgNode() in [node.getArgByName("curve")] } + DataFlow::Node getCurveArg() { result in [this.getArgByName("curve")] } + /** Gets the name of the curve to use, as well as the origin that explains how we obtained this name. */ string getCurveWithOrigin(DataFlow::Node origin) { exists(StrConst str | origin = DataFlow::exprNode(str) | origin.(DataFlow::LocalSourceNode).flowsTo(this.getCurveArg()) and @@ -341,7 +88,7 @@ private module CryptodomeModel { } override int getKeySizeWithOrigin(DataFlow::Node origin) { - exists(string curve | curve = getCurveWithOrigin(origin) | + exists(string curve | curve = this.getCurveWithOrigin(origin) | // using list from https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html curve in ["NIST P-256", "p256", "P-256", "prime256v1", "secp256r1"] and result = 256 or From 37f0d5a28a21cc8090b4d4b9ed8fd56d09231f1f Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 17 Feb 2021 14:35:02 +0100 Subject: [PATCH 099/757] Python: Make KeyGeneration range member overrides final This was the result of an internal dicussion we had about this some time ago. --- python/ql/src/semmle/python/Concepts.qll | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll index 989003a1e8a..3720c087898 100644 --- a/python/ql/src/semmle/python/Concepts.qll +++ b/python/ql/src/semmle/python/Concepts.qll @@ -592,23 +592,23 @@ module Cryptography { /** A data-flow node that generates a new RSA key-pair. */ abstract class RsaRange extends Range { - override string getName() { result = "RSA" } + final override string getName() { result = "RSA" } - override int minimumSecureKeySize() { result = 2048 } + final override int minimumSecureKeySize() { result = 2048 } } /** A data-flow node that generates a new DSA key-pair. */ abstract class DsaRange extends Range { - override string getName() { result = "DSA" } + final override string getName() { result = "DSA" } - override int minimumSecureKeySize() { result = 2048 } + final override int minimumSecureKeySize() { result = 2048 } } /** A data-flow node that generates a new ECC key-pair. */ abstract class EccRange extends Range { - override string getName() { result = "ECC" } + final override string getName() { result = "ECC" } - override int minimumSecureKeySize() { result = 224 } + final override int minimumSecureKeySize() { result = 224 } } } } From a6583345ba3c2a21b8a919127746aa8cc10a310f Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 19 Feb 2021 13:56:09 +0100 Subject: [PATCH 100/757] Python: Add weak crypto key example through function call We used to handle this, but no more :( Adding this example was inspired by looking at results differences --- .../query-tests/Security/CWE-326/weak_crypto.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py b/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py index a64d39bc866..01016a187ee 100644 --- a/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py +++ b/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py @@ -75,3 +75,16 @@ rsa_gen_key(65537, key_size=RSA_WEAK) DSA.generate(DSA_WEAK) RSA.generate(RSA_WEAK) + +# ------------------------------------------------------------------------------ + +# Through function calls + +def make_new_rsa_key_weak(bits): + return RSA.generate(bits) # NOT OK +make_new_rsa_key_weak(RSA_WEAK) + + +def make_new_rsa_key_strong(bits): + return RSA.generate(bits) # OK +make_new_rsa_key_strong(RSA_STRONG) From dfa223ac6afdba22a167de5aeed8d3178e38fde7 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 19 Feb 2021 14:25:38 +0100 Subject: [PATCH 101/757] Python: Better IntegerLiteral tracking for weak crypto key --- python/ql/src/semmle/python/Concepts.qll | 20 +++++++++++++++++-- .../Security/CWE-326/WeakCryptoKey.expected | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll index 3720c087898..4f0d4ed55d9 100644 --- a/python/ql/src/semmle/python/Concepts.qll +++ b/python/ql/src/semmle/python/Concepts.qll @@ -562,6 +562,21 @@ module Cryptography { /** Provides classes for modeling new key-pair generation APIs. */ module KeyGeneration { + /** + * A data-flow configuration for tracking integer literals. + */ + private class IntegerLiteralTrackerConfiguration extends DataFlow::Configuration { + IntegerLiteralTrackerConfiguration() { this = "IntegerLiteralTrackerConfiguration" } + + override predicate isSource(DataFlow::Node source) { + source = DataFlow::exprNode(any(IntegerLiteral size)) + } + + override predicate isSink(DataFlow::Node sink) { + sink = any(KeyGeneration::Range kg).getKeySizeArg() + } + } + /** * A data-flow node that generates a new key-pair for use with public-key cryptography. * @@ -580,8 +595,9 @@ module Cryptography { * explains how we obtained this specific key size. */ int getKeySizeWithOrigin(DataFlow::Node origin) { - exists(IntegerLiteral size | origin = DataFlow::exprNode(size) | - origin.(DataFlow::LocalSourceNode).flowsTo(this.getKeySizeArg()) and + exists(IntegerLiteral size, IntegerLiteralTrackerConfiguration config | + origin.asExpr() = size and + config.hasFlow(origin, this.getKeySizeArg()) and result = size.getValue() ) } diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected index a52d67eaff9..05d759d6f70 100644 --- a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected +++ b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected @@ -6,3 +6,4 @@ | weak_crypto.py:74:1:74:37 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:76:1:76:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:77:1:77:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:84:12:84:29 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | From bfc8ead6673e8ab9aaf203d50372f71cd346b427 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 19 Feb 2021 14:35:47 +0100 Subject: [PATCH 102/757] Python: Add example of test-code with weak crypto key --- .../query-tests/Security/CWE-326/WeakCryptoKey.expected | 2 ++ .../ql/test/query-tests/Security/CWE-326/test_example.py | 9 +++++++++ .../ql/test/query-tests/Security/CWE-326/weak_crypto.py | 5 +++++ 3 files changed, 16 insertions(+) create mode 100644 python/ql/test/query-tests/Security/CWE-326/test_example.py diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected index 05d759d6f70..6da7c7c1b2f 100644 --- a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected +++ b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected @@ -1,3 +1,4 @@ +| test_example.py:7:5:7:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | test_example.py:7:18:7:21 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:68:1:68:21 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:69:1:69:19 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:22:11:22:24 | ControlFlowNode for Attribute() | 163 | | weak_crypto.py:70:1:70:28 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | @@ -7,3 +8,4 @@ | weak_crypto.py:76:1:76:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:77:1:77:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:84:12:84:29 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | +| weak_crypto.py:95:12:95:29 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | test_example.py:9:23:9:26 | ControlFlowNode for IntegerLiteral | 1024 | diff --git a/python/ql/test/query-tests/Security/CWE-326/test_example.py b/python/ql/test/query-tests/Security/CWE-326/test_example.py new file mode 100644 index 00000000000..e0237deca81 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-326/test_example.py @@ -0,0 +1,9 @@ +from Cryptodome.PublicKey import RSA + +from weak_crypto import only_used_by_test + +def test_example(): + # This is technically not ok, but since it's in a test, we don't want to alert on it + RSA.generate(1024) + + only_used_by_test(1024) diff --git a/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py b/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py index 01016a187ee..de533254cfe 100644 --- a/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py +++ b/python/ql/test/query-tests/Security/CWE-326/weak_crypto.py @@ -88,3 +88,8 @@ make_new_rsa_key_weak(RSA_WEAK) def make_new_rsa_key_strong(bits): return RSA.generate(bits) # OK make_new_rsa_key_strong(RSA_STRONG) + + +def only_used_by_test(bits): + # Although this call will technically not be ok, since it's only used in a test, we don't want to alert on it. + return RSA.generate(bits) From d084261a793339f8aff504cc25d29e0aeea5aa1d Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 19 Feb 2021 14:37:10 +0100 Subject: [PATCH 103/757] Python: Ignore weak key-sizes from test-code in weak-crypto-key From looking at old results on LGTM.com, this was quite common (and those alerts doesn't really provide value). --- python/ql/src/Security/CWE-326/WeakCryptoKey.ql | 4 +++- .../test/query-tests/Security/CWE-326/WeakCryptoKey.expected | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Security/CWE-326/WeakCryptoKey.ql b/python/ql/src/Security/CWE-326/WeakCryptoKey.ql index 5fb3494e3e6..67f94640506 100644 --- a/python/ql/src/Security/CWE-326/WeakCryptoKey.ql +++ b/python/ql/src/Security/CWE-326/WeakCryptoKey.ql @@ -12,11 +12,13 @@ import python import semmle.python.Concepts import semmle.python.dataflow.new.DataFlow +import semmle.python.filters.Tests from Cryptography::PublicKey::KeyGeneration keyGen, int keySize, DataFlow::Node origin where keySize = keyGen.getKeySizeWithOrigin(origin) and - keySize < keyGen.minimumSecureKeySize() + keySize < keyGen.minimumSecureKeySize() and + not origin.getScope().getScope*() instanceof TestScope select keyGen, "Creation of an " + keyGen.getName() + " key uses $@ bits, which is below " + keyGen.minimumSecureKeySize() + " and considered breakable.", origin, keySize.toString() diff --git a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected index 6da7c7c1b2f..05d759d6f70 100644 --- a/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected +++ b/python/ql/test/query-tests/Security/CWE-326/WeakCryptoKey.expected @@ -1,4 +1,3 @@ -| test_example.py:7:5:7:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | test_example.py:7:18:7:21 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:68:1:68:21 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:69:1:69:19 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:22:11:22:24 | ControlFlowNode for Attribute() | 163 | | weak_crypto.py:70:1:70:28 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | @@ -8,4 +7,3 @@ | weak_crypto.py:76:1:76:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:16:12:16:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:77:1:77:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | | weak_crypto.py:84:12:84:29 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 | -| weak_crypto.py:95:12:95:29 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | test_example.py:9:23:9:26 | ControlFlowNode for IntegerLiteral | 1024 | From 4f23c3546fc38b4f9ffd2c4b10d21453337f6385 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 19 Feb 2021 15:15:51 +0100 Subject: [PATCH 104/757] C++: Don't generate WriteSideEffect instructions for const parameter indirections. --- .../raw/internal/TranslatedElement.qll | 8 +- .../test/library-tests/ir/ir/raw_ir.expected | 844 ++++++++---------- 2 files changed, 395 insertions(+), 457 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll index 56c48b100f8..1de9936ae1f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll @@ -658,14 +658,18 @@ newtype TTranslatedElement = t instanceof ReferenceType ) and ( - isWrite = true or + isWrite = true and + not call.getTarget().getParameter(n).getType().isDeeplyConstBelow() + or isWrite = false ) or not call.getTarget() instanceof SideEffectFunction and n = -1 and ( - isWrite = true or + isWrite = true and + not call.getTarget() instanceof ConstMemberFunction + or isWrite = false ) ) and diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected index acdf71d5e15..a7dac56cc96 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -3310,8 +3310,7 @@ ir.cpp: # 585| mu585_8(unknown) = ^CallSideEffect : ~m? # 585| v585_9(void) = ^BufferReadSideEffect[0] : &:r585_3, ~m? # 585| v585_10(void) = ^BufferReadSideEffect[2] : &:r585_6, ~m? -# 585| mu585_11(unknown) = ^BufferMayWriteSideEffect[0] : &:r585_3 -# 585| mu585_12(unknown) = ^BufferMayWriteSideEffect[2] : &:r585_6 +# 585| mu585_11(unknown) = ^BufferMayWriteSideEffect[2] : &:r585_6 # 586| v586_1(void) = NoOp : # 584| v584_4(void) = ReturnVoid : # 584| v584_5(void) = AliasedUse : ~m? @@ -3365,7 +3364,6 @@ ir.cpp: # 617| mu617_7(unknown) = ^CallSideEffect : ~m? # 617| mu617_8(String) = ^IndirectMayWriteSideEffect[-1] : &:r617_1 # 617| v617_9(void) = ^BufferReadSideEffect[0] : &:r617_5, ~m? -# 617| mu617_10(unknown) = ^BufferMayWriteSideEffect[0] : &:r617_5 # 618| r618_1(glval) = VariableAddress[s3] : # 618| r618_2(glval) = FunctionAddress[ReturnObject] : # 618| r618_3(String) = Call[ReturnObject] : func:r618_2 @@ -3380,7 +3378,6 @@ ir.cpp: # 619| mu619_7(unknown) = ^CallSideEffect : ~m? # 619| mu619_8(String) = ^IndirectMayWriteSideEffect[-1] : &:r619_1 # 619| v619_9(void) = ^BufferReadSideEffect[0] : &:r619_5, ~m? -# 619| mu619_10(unknown) = ^BufferMayWriteSideEffect[0] : &:r619_5 # 620| v620_1(void) = NoOp : # 615| v615_4(void) = ReturnVoid : # 615| v615_5(void) = AliasedUse : ~m? @@ -3409,7 +3406,6 @@ ir.cpp: # 623| r623_6(char *) = Call[c_str] : func:r623_5, this:r623_4 # 623| mu623_7(unknown) = ^CallSideEffect : ~m? # 623| v623_8(void) = ^BufferReadSideEffect[-1] : &:r623_4, ~m? -# 623| mu623_9(String) = ^IndirectMayWriteSideEffect[-1] : &:r623_4 # 624| r624_1(glval) = VariableAddress[p] : # 624| r624_2(String *) = Load[p] : &:r624_1, ~m? # 624| r624_3(String *) = Convert : r624_2 @@ -3424,7 +3420,6 @@ ir.cpp: # 625| r625_4(char *) = Call[c_str] : func:r625_3, this:r625_2 # 625| mu625_5(unknown) = ^CallSideEffect : ~m? # 625| v625_6(void) = ^BufferReadSideEffect[-1] : &:r625_2, ~m? -# 625| mu625_7(String) = ^IndirectMayWriteSideEffect[-1] : &:r625_2 # 626| v626_1(void) = NoOp : # 622| v622_14(void) = ReturnIndirection[r] : &:r622_6, ~m? # 622| v622_15(void) = ReturnIndirection[p] : &:r622_10, ~m? @@ -3636,7 +3631,6 @@ ir.cpp: # 662| mu662_6(unknown) = ^CallSideEffect : ~m? # 662| mu662_7(String) = ^IndirectMayWriteSideEffect[-1] : &:r662_1 # 662| v662_8(void) = ^BufferReadSideEffect[0] : &:r662_4, ~m? -# 662| mu662_9(unknown) = ^BufferMayWriteSideEffect[0] : &:r662_4 # 664| v664_1(void) = NoOp : # 658| v658_8(void) = ReturnIndirection[#this] : &:r658_6, ~m? # 658| v658_9(void) = ReturnVoid : @@ -3933,8 +3927,7 @@ ir.cpp: # 731| mu731_17(unknown) = ^CallSideEffect : ~m? # 731| mu731_18(String) = ^IndirectMayWriteSideEffect[-1] : &:r731_11 # 731| v731_19(void) = ^BufferReadSideEffect[0] : &:r731_15, ~m? -# 731| mu731_20(unknown) = ^BufferMayWriteSideEffect[0] : &:r731_15 -# 731| v731_21(void) = ThrowValue : &:r731_11, ~m? +# 731| v731_20(void) = ThrowValue : &:r731_11, ~m? #-----| Exception -> Block 9 # 733| Block 8 @@ -3962,8 +3955,7 @@ ir.cpp: # 736| mu736_7(unknown) = ^CallSideEffect : ~m? # 736| mu736_8(String) = ^IndirectMayWriteSideEffect[-1] : &:r736_1 # 736| v736_9(void) = ^BufferReadSideEffect[0] : &:r736_5, ~m? -# 736| mu736_10(unknown) = ^BufferMayWriteSideEffect[0] : &:r736_5 -# 736| v736_11(void) = ThrowValue : &:r736_1, ~m? +# 736| v736_10(void) = ThrowValue : &:r736_1, ~m? #-----| Exception -> Block 2 # 738| Block 11 @@ -4017,16 +4009,15 @@ ir.cpp: # 745| v745_18(void) = ^BufferReadSideEffect[-1] : &:r745_11, ~m? #-----| v0_7(void) = ^BufferReadSideEffect[0] : &:r0_6, ~m? # 745| mu745_19(String) = ^IndirectMayWriteSideEffect[-1] : &:r745_11 -#-----| mu0_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r0_6 -#-----| r0_9(glval) = CopyValue : r745_16 -#-----| r0_10(glval) = VariableAddress[#return] : -#-----| r0_11(glval) = VariableAddress[#this] : -#-----| r0_12(Base *) = Load[#this] : &:r0_11, ~m? -#-----| r0_13(glval) = CopyValue : r0_12 -#-----| r0_14(Base &) = CopyValue : r0_13 -#-----| mu0_15(Base &) = Store[#return] : &:r0_10, r0_14 +#-----| r0_8(glval) = CopyValue : r745_16 +#-----| r0_9(glval) = VariableAddress[#return] : +#-----| r0_10(glval) = VariableAddress[#this] : +#-----| r0_11(Base *) = Load[#this] : &:r0_10, ~m? +#-----| r0_12(glval) = CopyValue : r0_11 +#-----| r0_13(Base &) = CopyValue : r0_12 +#-----| mu0_14(Base &) = Store[#return] : &:r0_9, r0_13 # 745| v745_20(void) = ReturnIndirection[#this] : &:r745_6, ~m? -#-----| v0_16(void) = ReturnIndirection[(unnamed parameter 0)] : &:r0_3, ~m? +#-----| v0_15(void) = ReturnIndirection[(unnamed parameter 0)] : &:r0_3, ~m? # 745| r745_21(glval) = VariableAddress[#return] : # 745| v745_22(void) = ReturnValue : &:r745_21, ~m? # 745| v745_23(void) = AliasedUse : ~m? @@ -4125,8 +4116,7 @@ ir.cpp: #-----| v0_9(void) = ^BufferReadSideEffect[-1] : &:r0_5, ~m? #-----| v0_10(void) = ^BufferReadSideEffect[0] : &:r0_8, ~m? #-----| mu0_11(Base) = ^IndirectMayWriteSideEffect[-1] : &:r0_5 -#-----| mu0_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r0_8 -#-----| r0_13(glval) = CopyValue : r754_15 +#-----| r0_12(glval) = CopyValue : r754_15 # 754| r754_17(glval) = VariableAddress[#this] : # 754| r754_18(Middle *) = Load[#this] : &:r754_17, ~m? # 754| r754_19(glval) = FieldAddress[middle_s] : r754_18 @@ -4134,24 +4124,23 @@ ir.cpp: # 754| r754_21(glval) = FunctionAddress[operator=] : # 754| r754_22(glval) = VariableAddress[(unnamed parameter 0)] : # 754| r754_23(Middle &) = Load[(unnamed parameter 0)] : &:r754_22, ~m? -#-----| r0_14(glval) = CopyValue : r754_23 -# 754| r754_24(glval) = FieldAddress[middle_s] : r0_14 -#-----| r0_15(String &) = CopyValue : r754_24 -# 754| r754_25(String &) = Call[operator=] : func:r754_21, this:r754_20, 0:r0_15 +#-----| r0_13(glval) = CopyValue : r754_23 +# 754| r754_24(glval) = FieldAddress[middle_s] : r0_13 +#-----| r0_14(String &) = CopyValue : r754_24 +# 754| r754_25(String &) = Call[operator=] : func:r754_21, this:r754_20, 0:r0_14 # 754| mu754_26(unknown) = ^CallSideEffect : ~m? # 754| v754_27(void) = ^BufferReadSideEffect[-1] : &:r754_20, ~m? -#-----| v0_16(void) = ^BufferReadSideEffect[0] : &:r0_15, ~m? +#-----| v0_15(void) = ^BufferReadSideEffect[0] : &:r0_14, ~m? # 754| mu754_28(String) = ^IndirectMayWriteSideEffect[-1] : &:r754_20 -#-----| mu0_17(unknown) = ^BufferMayWriteSideEffect[0] : &:r0_15 -#-----| r0_18(glval) = CopyValue : r754_25 -#-----| r0_19(glval) = VariableAddress[#return] : -#-----| r0_20(glval) = VariableAddress[#this] : -#-----| r0_21(Middle *) = Load[#this] : &:r0_20, ~m? -#-----| r0_22(glval) = CopyValue : r0_21 -#-----| r0_23(Middle &) = CopyValue : r0_22 -#-----| mu0_24(Middle &) = Store[#return] : &:r0_19, r0_23 +#-----| r0_16(glval) = CopyValue : r754_25 +#-----| r0_17(glval) = VariableAddress[#return] : +#-----| r0_18(glval) = VariableAddress[#this] : +#-----| r0_19(Middle *) = Load[#this] : &:r0_18, ~m? +#-----| r0_20(glval) = CopyValue : r0_19 +#-----| r0_21(Middle &) = CopyValue : r0_20 +#-----| mu0_22(Middle &) = Store[#return] : &:r0_17, r0_21 # 754| v754_29(void) = ReturnIndirection[#this] : &:r754_6, ~m? -#-----| v0_25(void) = ReturnIndirection[(unnamed parameter 0)] : &:r0_3, ~m? +#-----| v0_23(void) = ReturnIndirection[(unnamed parameter 0)] : &:r0_3, ~m? # 754| r754_30(glval) = VariableAddress[#return] : # 754| v754_31(void) = ReturnValue : &:r754_30, ~m? # 754| v754_32(void) = AliasedUse : ~m? @@ -4234,8 +4223,7 @@ ir.cpp: #-----| v0_9(void) = ^BufferReadSideEffect[-1] : &:r0_5, ~m? #-----| v0_10(void) = ^BufferReadSideEffect[0] : &:r0_8, ~m? #-----| mu0_11(Middle) = ^IndirectMayWriteSideEffect[-1] : &:r0_5 -#-----| mu0_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r0_8 -#-----| r0_13(glval) = CopyValue : r763_15 +#-----| r0_12(glval) = CopyValue : r763_15 # 763| r763_17(glval) = VariableAddress[#this] : # 763| r763_18(Derived *) = Load[#this] : &:r763_17, ~m? # 763| r763_19(glval) = FieldAddress[derived_s] : r763_18 @@ -4243,24 +4231,23 @@ ir.cpp: # 763| r763_21(glval) = FunctionAddress[operator=] : # 763| r763_22(glval) = VariableAddress[(unnamed parameter 0)] : # 763| r763_23(Derived &) = Load[(unnamed parameter 0)] : &:r763_22, ~m? -#-----| r0_14(glval) = CopyValue : r763_23 -# 763| r763_24(glval) = FieldAddress[derived_s] : r0_14 -#-----| r0_15(String &) = CopyValue : r763_24 -# 763| r763_25(String &) = Call[operator=] : func:r763_21, this:r763_20, 0:r0_15 +#-----| r0_13(glval) = CopyValue : r763_23 +# 763| r763_24(glval) = FieldAddress[derived_s] : r0_13 +#-----| r0_14(String &) = CopyValue : r763_24 +# 763| r763_25(String &) = Call[operator=] : func:r763_21, this:r763_20, 0:r0_14 # 763| mu763_26(unknown) = ^CallSideEffect : ~m? # 763| v763_27(void) = ^BufferReadSideEffect[-1] : &:r763_20, ~m? -#-----| v0_16(void) = ^BufferReadSideEffect[0] : &:r0_15, ~m? +#-----| v0_15(void) = ^BufferReadSideEffect[0] : &:r0_14, ~m? # 763| mu763_28(String) = ^IndirectMayWriteSideEffect[-1] : &:r763_20 -#-----| mu0_17(unknown) = ^BufferMayWriteSideEffect[0] : &:r0_15 -#-----| r0_18(glval) = CopyValue : r763_25 -#-----| r0_19(glval) = VariableAddress[#return] : -#-----| r0_20(glval) = VariableAddress[#this] : -#-----| r0_21(Derived *) = Load[#this] : &:r0_20, ~m? -#-----| r0_22(glval) = CopyValue : r0_21 -#-----| r0_23(Derived &) = CopyValue : r0_22 -#-----| mu0_24(Derived &) = Store[#return] : &:r0_19, r0_23 +#-----| r0_16(glval) = CopyValue : r763_25 +#-----| r0_17(glval) = VariableAddress[#return] : +#-----| r0_18(glval) = VariableAddress[#this] : +#-----| r0_19(Derived *) = Load[#this] : &:r0_18, ~m? +#-----| r0_20(glval) = CopyValue : r0_19 +#-----| r0_21(Derived &) = CopyValue : r0_20 +#-----| mu0_22(Derived &) = Store[#return] : &:r0_17, r0_21 # 763| v763_29(void) = ReturnIndirection[#this] : &:r763_6, ~m? -#-----| v0_25(void) = ReturnIndirection[(unnamed parameter 0)] : &:r0_3, ~m? +#-----| v0_23(void) = ReturnIndirection[(unnamed parameter 0)] : &:r0_3, ~m? # 763| r763_30(glval) = VariableAddress[#return] : # 763| v763_31(void) = ReturnValue : &:r763_30, ~m? # 763| v763_32(void) = AliasedUse : ~m? @@ -4521,8 +4508,7 @@ ir.cpp: # 808| v808_8(void) = ^BufferReadSideEffect[-1] : &:r808_1, ~m? # 808| v808_9(void) = ^BufferReadSideEffect[0] : &:r808_5, ~m? # 808| mu808_10(Base) = ^IndirectMayWriteSideEffect[-1] : &:r808_1 -# 808| mu808_11(unknown) = ^BufferMayWriteSideEffect[0] : &:r808_5 -# 808| r808_12(glval) = CopyValue : r808_6 +# 808| r808_11(glval) = CopyValue : r808_6 # 809| r809_1(glval) = VariableAddress[b] : # 809| r809_2(glval) = FunctionAddress[operator=] : # 809| r809_3(glval) = VariableAddress[#temp809:7] : @@ -4535,16 +4521,14 @@ ir.cpp: # 809| mu809_10(unknown) = ^CallSideEffect : ~m? # 809| mu809_11(Base) = ^IndirectMayWriteSideEffect[-1] : &:r809_3 # 809| v809_12(void) = ^BufferReadSideEffect[0] : &:r809_8, ~m? -# 809| mu809_13(unknown) = ^BufferMayWriteSideEffect[0] : &:r809_8 -# 809| r809_14(glval) = Convert : r809_3 -# 809| r809_15(Base &) = CopyValue : r809_14 -# 809| r809_16(Base &) = Call[operator=] : func:r809_2, this:r809_1, 0:r809_15 -# 809| mu809_17(unknown) = ^CallSideEffect : ~m? -# 809| v809_18(void) = ^BufferReadSideEffect[-1] : &:r809_1, ~m? -# 809| v809_19(void) = ^BufferReadSideEffect[0] : &:r809_15, ~m? -# 809| mu809_20(Base) = ^IndirectMayWriteSideEffect[-1] : &:r809_1 -# 809| mu809_21(unknown) = ^BufferMayWriteSideEffect[0] : &:r809_15 -# 809| r809_22(glval) = CopyValue : r809_16 +# 809| r809_13(glval) = Convert : r809_3 +# 809| r809_14(Base &) = CopyValue : r809_13 +# 809| r809_15(Base &) = Call[operator=] : func:r809_2, this:r809_1, 0:r809_14 +# 809| mu809_16(unknown) = ^CallSideEffect : ~m? +# 809| v809_17(void) = ^BufferReadSideEffect[-1] : &:r809_1, ~m? +# 809| v809_18(void) = ^BufferReadSideEffect[0] : &:r809_14, ~m? +# 809| mu809_19(Base) = ^IndirectMayWriteSideEffect[-1] : &:r809_1 +# 809| r809_20(glval) = CopyValue : r809_15 # 810| r810_1(glval) = VariableAddress[b] : # 810| r810_2(glval) = FunctionAddress[operator=] : # 810| r810_3(glval) = VariableAddress[#temp810:7] : @@ -4557,16 +4541,14 @@ ir.cpp: # 810| mu810_10(unknown) = ^CallSideEffect : ~m? # 810| mu810_11(Base) = ^IndirectMayWriteSideEffect[-1] : &:r810_3 # 810| v810_12(void) = ^BufferReadSideEffect[0] : &:r810_8, ~m? -# 810| mu810_13(unknown) = ^BufferMayWriteSideEffect[0] : &:r810_8 -# 810| r810_14(glval) = Convert : r810_3 -# 810| r810_15(Base &) = CopyValue : r810_14 -# 810| r810_16(Base &) = Call[operator=] : func:r810_2, this:r810_1, 0:r810_15 -# 810| mu810_17(unknown) = ^CallSideEffect : ~m? -# 810| v810_18(void) = ^BufferReadSideEffect[-1] : &:r810_1, ~m? -# 810| v810_19(void) = ^BufferReadSideEffect[0] : &:r810_15, ~m? -# 810| mu810_20(Base) = ^IndirectMayWriteSideEffect[-1] : &:r810_1 -# 810| mu810_21(unknown) = ^BufferMayWriteSideEffect[0] : &:r810_15 -# 810| r810_22(glval) = CopyValue : r810_16 +# 810| r810_13(glval) = Convert : r810_3 +# 810| r810_14(Base &) = CopyValue : r810_13 +# 810| r810_15(Base &) = Call[operator=] : func:r810_2, this:r810_1, 0:r810_14 +# 810| mu810_16(unknown) = ^CallSideEffect : ~m? +# 810| v810_17(void) = ^BufferReadSideEffect[-1] : &:r810_1, ~m? +# 810| v810_18(void) = ^BufferReadSideEffect[0] : &:r810_14, ~m? +# 810| mu810_19(Base) = ^IndirectMayWriteSideEffect[-1] : &:r810_1 +# 810| r810_20(glval) = CopyValue : r810_15 # 811| r811_1(glval) = VariableAddress[pm] : # 811| r811_2(Middle *) = Load[pm] : &:r811_1, ~m? # 811| r811_3(Base *) = ConvertToNonVirtualBase[Middle : Base] : r811_2 @@ -4598,8 +4580,7 @@ ir.cpp: # 816| v816_9(void) = ^BufferReadSideEffect[-1] : &:r816_1, ~m? # 816| v816_10(void) = ^BufferReadSideEffect[0] : &:r816_6, ~m? # 816| mu816_11(Middle) = ^IndirectMayWriteSideEffect[-1] : &:r816_1 -# 816| mu816_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r816_6 -# 816| r816_13(glval) = CopyValue : r816_7 +# 816| r816_12(glval) = CopyValue : r816_7 # 817| r817_1(glval) = VariableAddress[m] : # 817| r817_2(glval) = FunctionAddress[operator=] : # 817| r817_3(glval) = VariableAddress[b] : @@ -4611,8 +4592,7 @@ ir.cpp: # 817| v817_9(void) = ^BufferReadSideEffect[-1] : &:r817_1, ~m? # 817| v817_10(void) = ^BufferReadSideEffect[0] : &:r817_6, ~m? # 817| mu817_11(Middle) = ^IndirectMayWriteSideEffect[-1] : &:r817_1 -# 817| mu817_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r817_6 -# 817| r817_13(glval) = CopyValue : r817_7 +# 817| r817_12(glval) = CopyValue : r817_7 # 818| r818_1(glval) = VariableAddress[pb] : # 818| r818_2(Base *) = Load[pb] : &:r818_1, ~m? # 818| r818_3(Middle *) = ConvertToDerived[Middle : Base] : r818_2 @@ -4639,8 +4619,7 @@ ir.cpp: # 822| v822_9(void) = ^BufferReadSideEffect[-1] : &:r822_1, ~m? # 822| v822_10(void) = ^BufferReadSideEffect[0] : &:r822_6, ~m? # 822| mu822_11(Base) = ^IndirectMayWriteSideEffect[-1] : &:r822_1 -# 822| mu822_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r822_6 -# 822| r822_13(glval) = CopyValue : r822_7 +# 822| r822_12(glval) = CopyValue : r822_7 # 823| r823_1(glval) = VariableAddress[b] : # 823| r823_2(glval) = FunctionAddress[operator=] : # 823| r823_3(glval) = VariableAddress[#temp823:7] : @@ -4654,16 +4633,14 @@ ir.cpp: # 823| mu823_11(unknown) = ^CallSideEffect : ~m? # 823| mu823_12(Base) = ^IndirectMayWriteSideEffect[-1] : &:r823_3 # 823| v823_13(void) = ^BufferReadSideEffect[0] : &:r823_9, ~m? -# 823| mu823_14(unknown) = ^BufferMayWriteSideEffect[0] : &:r823_9 -# 823| r823_15(glval) = Convert : r823_3 -# 823| r823_16(Base &) = CopyValue : r823_15 -# 823| r823_17(Base &) = Call[operator=] : func:r823_2, this:r823_1, 0:r823_16 -# 823| mu823_18(unknown) = ^CallSideEffect : ~m? -# 823| v823_19(void) = ^BufferReadSideEffect[-1] : &:r823_1, ~m? -# 823| v823_20(void) = ^BufferReadSideEffect[0] : &:r823_16, ~m? -# 823| mu823_21(Base) = ^IndirectMayWriteSideEffect[-1] : &:r823_1 -# 823| mu823_22(unknown) = ^BufferMayWriteSideEffect[0] : &:r823_16 -# 823| r823_23(glval) = CopyValue : r823_17 +# 823| r823_14(glval) = Convert : r823_3 +# 823| r823_15(Base &) = CopyValue : r823_14 +# 823| r823_16(Base &) = Call[operator=] : func:r823_2, this:r823_1, 0:r823_15 +# 823| mu823_17(unknown) = ^CallSideEffect : ~m? +# 823| v823_18(void) = ^BufferReadSideEffect[-1] : &:r823_1, ~m? +# 823| v823_19(void) = ^BufferReadSideEffect[0] : &:r823_15, ~m? +# 823| mu823_20(Base) = ^IndirectMayWriteSideEffect[-1] : &:r823_1 +# 823| r823_21(glval) = CopyValue : r823_16 # 824| r824_1(glval) = VariableAddress[b] : # 824| r824_2(glval) = FunctionAddress[operator=] : # 824| r824_3(glval) = VariableAddress[#temp824:7] : @@ -4677,16 +4654,14 @@ ir.cpp: # 824| mu824_11(unknown) = ^CallSideEffect : ~m? # 824| mu824_12(Base) = ^IndirectMayWriteSideEffect[-1] : &:r824_3 # 824| v824_13(void) = ^BufferReadSideEffect[0] : &:r824_9, ~m? -# 824| mu824_14(unknown) = ^BufferMayWriteSideEffect[0] : &:r824_9 -# 824| r824_15(glval) = Convert : r824_3 -# 824| r824_16(Base &) = CopyValue : r824_15 -# 824| r824_17(Base &) = Call[operator=] : func:r824_2, this:r824_1, 0:r824_16 -# 824| mu824_18(unknown) = ^CallSideEffect : ~m? -# 824| v824_19(void) = ^BufferReadSideEffect[-1] : &:r824_1, ~m? -# 824| v824_20(void) = ^BufferReadSideEffect[0] : &:r824_16, ~m? -# 824| mu824_21(Base) = ^IndirectMayWriteSideEffect[-1] : &:r824_1 -# 824| mu824_22(unknown) = ^BufferMayWriteSideEffect[0] : &:r824_16 -# 824| r824_23(glval) = CopyValue : r824_17 +# 824| r824_14(glval) = Convert : r824_3 +# 824| r824_15(Base &) = CopyValue : r824_14 +# 824| r824_16(Base &) = Call[operator=] : func:r824_2, this:r824_1, 0:r824_15 +# 824| mu824_17(unknown) = ^CallSideEffect : ~m? +# 824| v824_18(void) = ^BufferReadSideEffect[-1] : &:r824_1, ~m? +# 824| v824_19(void) = ^BufferReadSideEffect[0] : &:r824_15, ~m? +# 824| mu824_20(Base) = ^IndirectMayWriteSideEffect[-1] : &:r824_1 +# 824| r824_21(glval) = CopyValue : r824_16 # 825| r825_1(glval) = VariableAddress[pd] : # 825| r825_2(Derived *) = Load[pd] : &:r825_1, ~m? # 825| r825_3(Middle *) = ConvertToNonVirtualBase[Derived : Middle] : r825_2 @@ -4722,8 +4697,7 @@ ir.cpp: # 830| v830_10(void) = ^BufferReadSideEffect[-1] : &:r830_1, ~m? # 830| v830_11(void) = ^BufferReadSideEffect[0] : &:r830_7, ~m? # 830| mu830_12(Derived) = ^IndirectMayWriteSideEffect[-1] : &:r830_1 -# 830| mu830_13(unknown) = ^BufferMayWriteSideEffect[0] : &:r830_7 -# 830| r830_14(glval) = CopyValue : r830_8 +# 830| r830_13(glval) = CopyValue : r830_8 # 831| r831_1(glval) = VariableAddress[d] : # 831| r831_2(glval) = FunctionAddress[operator=] : # 831| r831_3(glval) = VariableAddress[b] : @@ -4736,8 +4710,7 @@ ir.cpp: # 831| v831_10(void) = ^BufferReadSideEffect[-1] : &:r831_1, ~m? # 831| v831_11(void) = ^BufferReadSideEffect[0] : &:r831_7, ~m? # 831| mu831_12(Derived) = ^IndirectMayWriteSideEffect[-1] : &:r831_1 -# 831| mu831_13(unknown) = ^BufferMayWriteSideEffect[0] : &:r831_7 -# 831| r831_14(glval) = CopyValue : r831_8 +# 831| r831_13(glval) = CopyValue : r831_8 # 832| r832_1(glval) = VariableAddress[pb] : # 832| r832_2(Base *) = Load[pb] : &:r832_1, ~m? # 832| r832_3(Middle *) = ConvertToDerived[Middle : Base] : r832_2 @@ -4906,7 +4879,6 @@ ir.cpp: # 868| mu868_5(unknown) = ^CallSideEffect : ~m? # 868| mu868_6(String) = ^IndirectMayWriteSideEffect[-1] : &:mu867_5 # 868| v868_7(void) = ^BufferReadSideEffect[0] : &:r868_3, ~m? -# 868| mu868_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r868_3 # 869| v869_1(void) = NoOp : # 867| v867_8(void) = ReturnIndirection[#this] : &:r867_6, ~m? # 867| v867_9(void) = ReturnVoid : @@ -5208,7 +5180,6 @@ ir.cpp: # 954| mu954_12(unknown) = ^CallSideEffect : ~m? # 954| mu954_13(String) = ^IndirectMayWriteSideEffect[-1] : &:r954_7 # 954| v954_14(void) = ^BufferReadSideEffect[0] : &:r954_10, ~m? -# 954| mu954_15(unknown) = ^BufferMayWriteSideEffect[0] : &:r954_10 # 955| r955_1(glval) = FunctionAddress[operator new] : # 955| r955_2(unsigned long) = Constant[256] : # 955| r955_3(align_val_t) = Constant[128] : @@ -5718,7 +5689,6 @@ ir.cpp: # 1044| r1044_5(char) = Call[operator()] : func:r1044_3, this:r1044_2, 0:r1044_4 # 1044| mu1044_6(unknown) = ^CallSideEffect : ~m? # 1044| v1044_7(void) = ^BufferReadSideEffect[-1] : &:r1044_2, ~m? -# 1044| mu1044_8(decltype([...](...){...})) = ^IndirectMayWriteSideEffect[-1] : &:r1044_2 # 1045| r1045_1(glval) = VariableAddress[lambda_val] : # 1045| r1045_2(glval) = VariableAddress[#temp1045:20] : # 1045| mu1045_3(decltype([...](...){...})) = Uninitialized[#temp1045:20] : &:r1045_2 @@ -5740,7 +5710,6 @@ ir.cpp: # 1046| r1046_5(char) = Call[operator()] : func:r1046_3, this:r1046_2, 0:r1046_4 # 1046| mu1046_6(unknown) = ^CallSideEffect : ~m? # 1046| v1046_7(void) = ^BufferReadSideEffect[-1] : &:r1046_2, ~m? -# 1046| mu1046_8(decltype([...](...){...})) = ^IndirectMayWriteSideEffect[-1] : &:r1046_2 # 1047| r1047_1(glval) = VariableAddress[lambda_ref_explicit] : # 1047| r1047_2(glval) = VariableAddress[#temp1047:29] : # 1047| mu1047_3(decltype([...](...){...})) = Uninitialized[#temp1047:29] : &:r1047_2 @@ -5759,7 +5728,6 @@ ir.cpp: # 1048| r1048_5(char) = Call[operator()] : func:r1048_3, this:r1048_2, 0:r1048_4 # 1048| mu1048_6(unknown) = ^CallSideEffect : ~m? # 1048| v1048_7(void) = ^BufferReadSideEffect[-1] : &:r1048_2, ~m? -# 1048| mu1048_8(decltype([...](...){...})) = ^IndirectMayWriteSideEffect[-1] : &:r1048_2 # 1049| r1049_1(glval) = VariableAddress[lambda_val_explicit] : # 1049| r1049_2(glval) = VariableAddress[#temp1049:29] : # 1049| mu1049_3(decltype([...](...){...})) = Uninitialized[#temp1049:29] : &:r1049_2 @@ -5777,7 +5745,6 @@ ir.cpp: # 1050| r1050_5(char) = Call[operator()] : func:r1050_3, this:r1050_2, 0:r1050_4 # 1050| mu1050_6(unknown) = ^CallSideEffect : ~m? # 1050| v1050_7(void) = ^BufferReadSideEffect[-1] : &:r1050_2, ~m? -# 1050| mu1050_8(decltype([...](...){...})) = ^IndirectMayWriteSideEffect[-1] : &:r1050_2 # 1051| r1051_1(glval) = VariableAddress[lambda_mixed_explicit] : # 1051| r1051_2(glval) = VariableAddress[#temp1051:31] : # 1051| mu1051_3(decltype([...](...){...})) = Uninitialized[#temp1051:31] : &:r1051_2 @@ -5800,7 +5767,6 @@ ir.cpp: # 1052| r1052_5(char) = Call[operator()] : func:r1052_3, this:r1052_2, 0:r1052_4 # 1052| mu1052_6(unknown) = ^CallSideEffect : ~m? # 1052| v1052_7(void) = ^BufferReadSideEffect[-1] : &:r1052_2, ~m? -# 1052| mu1052_8(decltype([...](...){...})) = ^IndirectMayWriteSideEffect[-1] : &:r1052_2 # 1053| r1053_1(glval) = VariableAddress[r] : # 1053| r1053_2(glval) = VariableAddress[x] : # 1053| r1053_3(int) = Load[x] : &:r1053_2, ~m? @@ -5839,7 +5805,6 @@ ir.cpp: # 1055| r1055_5(char) = Call[operator()] : func:r1055_3, this:r1055_2, 0:r1055_4 # 1055| mu1055_6(unknown) = ^CallSideEffect : ~m? # 1055| v1055_7(void) = ^BufferReadSideEffect[-1] : &:r1055_2, ~m? -# 1055| mu1055_8(decltype([...](...){...})) = ^IndirectMayWriteSideEffect[-1] : &:r1055_2 # 1056| v1056_1(void) = NoOp : # 1040| v1040_10(void) = ReturnIndirection[s] : &:r1040_8, ~m? # 1040| v1040_11(void) = ReturnVoid : @@ -5886,39 +5851,38 @@ ir.cpp: # 1043| char (void Lambda(int, String const&))::(lambda [] type at line 1043, col. 21)::operator()(float) const # 1043| Block 0 -# 1043| v1043_1(void) = EnterFunction : -# 1043| mu1043_2(unknown) = AliasedDefinition : -# 1043| mu1043_3(unknown) = InitializeNonLocal : -# 1043| r1043_4(glval) = VariableAddress[#this] : -# 1043| mu1043_5(glval) = InitializeParameter[#this] : &:r1043_4 -# 1043| r1043_6(glval) = Load[#this] : &:r1043_4, ~m? -# 1043| mu1043_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1043_6 -# 1043| r1043_8(glval) = VariableAddress[f] : -# 1043| mu1043_9(float) = InitializeParameter[f] : &:r1043_8 -# 1043| r1043_10(glval) = VariableAddress[#return] : -# 1043| r1043_11(glval) = VariableAddress[#this] : -# 1043| r1043_12(lambda [] type at line 1043, col. 21 *) = Load[#this] : &:r1043_11, ~m? -# 1043| r1043_13(glval) = FieldAddress[s] : r1043_12 -# 1043| r1043_14(String &) = Load[?] : &:r1043_13, ~m? -# 1043| r1043_15(glval) = CopyValue : r1043_14 -# 1043| r1043_16(glval) = FunctionAddress[c_str] : -# 1043| r1043_17(char *) = Call[c_str] : func:r1043_16, this:r1043_15 -# 1043| mu1043_18(unknown) = ^CallSideEffect : ~m? -# 1043| v1043_19(void) = ^BufferReadSideEffect[-1] : &:r1043_15, ~m? -# 1043| mu1043_20(String) = ^IndirectMayWriteSideEffect[-1] : &:r1043_15 -# 1043| r1043_21(glval) = VariableAddress[#this] : -# 1043| r1043_22(lambda [] type at line 1043, col. 21 *) = Load[#this] : &:r1043_21, ~m? -# 1043| r1043_23(glval) = FieldAddress[x] : r1043_22 -# 1043| r1043_24(int &) = Load[?] : &:r1043_23, ~m? -# 1043| r1043_25(int) = Load[?] : &:r1043_24, ~m? -# 1043| r1043_26(glval) = PointerAdd[1] : r1043_17, r1043_25 -# 1043| r1043_27(char) = Load[?] : &:r1043_26, ~m? -# 1043| mu1043_28(char) = Store[#return] : &:r1043_10, r1043_27 -# 1043| v1043_29(void) = ReturnIndirection[#this] : &:r1043_6, ~m? -# 1043| r1043_30(glval) = VariableAddress[#return] : -# 1043| v1043_31(void) = ReturnValue : &:r1043_30, ~m? -# 1043| v1043_32(void) = AliasedUse : ~m? -# 1043| v1043_33(void) = ExitFunction : +# 1043| v1043_1(void) = EnterFunction : +# 1043| mu1043_2(unknown) = AliasedDefinition : +# 1043| mu1043_3(unknown) = InitializeNonLocal : +# 1043| r1043_4(glval) = VariableAddress[#this] : +# 1043| mu1043_5(glval) = InitializeParameter[#this] : &:r1043_4 +# 1043| r1043_6(glval) = Load[#this] : &:r1043_4, ~m? +# 1043| mu1043_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1043_6 +# 1043| r1043_8(glval) = VariableAddress[f] : +# 1043| mu1043_9(float) = InitializeParameter[f] : &:r1043_8 +# 1043| r1043_10(glval) = VariableAddress[#return] : +# 1043| r1043_11(glval) = VariableAddress[#this] : +# 1043| r1043_12(lambda [] type at line 1043, col. 21 *) = Load[#this] : &:r1043_11, ~m? +# 1043| r1043_13(glval) = FieldAddress[s] : r1043_12 +# 1043| r1043_14(String &) = Load[?] : &:r1043_13, ~m? +# 1043| r1043_15(glval) = CopyValue : r1043_14 +# 1043| r1043_16(glval) = FunctionAddress[c_str] : +# 1043| r1043_17(char *) = Call[c_str] : func:r1043_16, this:r1043_15 +# 1043| mu1043_18(unknown) = ^CallSideEffect : ~m? +# 1043| v1043_19(void) = ^BufferReadSideEffect[-1] : &:r1043_15, ~m? +# 1043| r1043_20(glval) = VariableAddress[#this] : +# 1043| r1043_21(lambda [] type at line 1043, col. 21 *) = Load[#this] : &:r1043_20, ~m? +# 1043| r1043_22(glval) = FieldAddress[x] : r1043_21 +# 1043| r1043_23(int &) = Load[?] : &:r1043_22, ~m? +# 1043| r1043_24(int) = Load[?] : &:r1043_23, ~m? +# 1043| r1043_25(glval) = PointerAdd[1] : r1043_17, r1043_24 +# 1043| r1043_26(char) = Load[?] : &:r1043_25, ~m? +# 1043| mu1043_27(char) = Store[#return] : &:r1043_10, r1043_26 +# 1043| v1043_28(void) = ReturnIndirection[#this] : &:r1043_6, ~m? +# 1043| r1043_29(glval) = VariableAddress[#return] : +# 1043| v1043_30(void) = ReturnValue : &:r1043_29, ~m? +# 1043| v1043_31(void) = AliasedUse : ~m? +# 1043| v1043_32(void) = ExitFunction : # 1045| void (void Lambda(int, String const&))::(lambda [] type at line 1045, col. 21)::~() # 1045| Block 0 @@ -5941,68 +5905,66 @@ ir.cpp: # 1045| char (void Lambda(int, String const&))::(lambda [] type at line 1045, col. 21)::operator()(float) const # 1045| Block 0 -# 1045| v1045_1(void) = EnterFunction : -# 1045| mu1045_2(unknown) = AliasedDefinition : -# 1045| mu1045_3(unknown) = InitializeNonLocal : -# 1045| r1045_4(glval) = VariableAddress[#this] : -# 1045| mu1045_5(glval) = InitializeParameter[#this] : &:r1045_4 -# 1045| r1045_6(glval) = Load[#this] : &:r1045_4, ~m? -# 1045| mu1045_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1045_6 -# 1045| r1045_8(glval) = VariableAddress[f] : -# 1045| mu1045_9(float) = InitializeParameter[f] : &:r1045_8 -# 1045| r1045_10(glval) = VariableAddress[#return] : -# 1045| r1045_11(glval) = VariableAddress[#this] : -# 1045| r1045_12(lambda [] type at line 1045, col. 21 *) = Load[#this] : &:r1045_11, ~m? -# 1045| r1045_13(glval) = FieldAddress[s] : r1045_12 -# 1045| r1045_14(glval) = FunctionAddress[c_str] : -# 1045| r1045_15(char *) = Call[c_str] : func:r1045_14, this:r1045_13 -# 1045| mu1045_16(unknown) = ^CallSideEffect : ~m? -# 1045| v1045_17(void) = ^BufferReadSideEffect[-1] : &:r1045_13, ~m? -# 1045| mu1045_18(String) = ^IndirectMayWriteSideEffect[-1] : &:r1045_13 -# 1045| r1045_19(glval) = VariableAddress[#this] : -# 1045| r1045_20(lambda [] type at line 1045, col. 21 *) = Load[#this] : &:r1045_19, ~m? -# 1045| r1045_21(glval) = FieldAddress[x] : r1045_20 -# 1045| r1045_22(int) = Load[?] : &:r1045_21, ~m? -# 1045| r1045_23(glval) = PointerAdd[1] : r1045_15, r1045_22 -# 1045| r1045_24(char) = Load[?] : &:r1045_23, ~m? -# 1045| mu1045_25(char) = Store[#return] : &:r1045_10, r1045_24 -# 1045| v1045_26(void) = ReturnIndirection[#this] : &:r1045_6, ~m? -# 1045| r1045_27(glval) = VariableAddress[#return] : -# 1045| v1045_28(void) = ReturnValue : &:r1045_27, ~m? -# 1045| v1045_29(void) = AliasedUse : ~m? -# 1045| v1045_30(void) = ExitFunction : +# 1045| v1045_1(void) = EnterFunction : +# 1045| mu1045_2(unknown) = AliasedDefinition : +# 1045| mu1045_3(unknown) = InitializeNonLocal : +# 1045| r1045_4(glval) = VariableAddress[#this] : +# 1045| mu1045_5(glval) = InitializeParameter[#this] : &:r1045_4 +# 1045| r1045_6(glval) = Load[#this] : &:r1045_4, ~m? +# 1045| mu1045_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1045_6 +# 1045| r1045_8(glval) = VariableAddress[f] : +# 1045| mu1045_9(float) = InitializeParameter[f] : &:r1045_8 +# 1045| r1045_10(glval) = VariableAddress[#return] : +# 1045| r1045_11(glval) = VariableAddress[#this] : +# 1045| r1045_12(lambda [] type at line 1045, col. 21 *) = Load[#this] : &:r1045_11, ~m? +# 1045| r1045_13(glval) = FieldAddress[s] : r1045_12 +# 1045| r1045_14(glval) = FunctionAddress[c_str] : +# 1045| r1045_15(char *) = Call[c_str] : func:r1045_14, this:r1045_13 +# 1045| mu1045_16(unknown) = ^CallSideEffect : ~m? +# 1045| v1045_17(void) = ^BufferReadSideEffect[-1] : &:r1045_13, ~m? +# 1045| r1045_18(glval) = VariableAddress[#this] : +# 1045| r1045_19(lambda [] type at line 1045, col. 21 *) = Load[#this] : &:r1045_18, ~m? +# 1045| r1045_20(glval) = FieldAddress[x] : r1045_19 +# 1045| r1045_21(int) = Load[?] : &:r1045_20, ~m? +# 1045| r1045_22(glval) = PointerAdd[1] : r1045_15, r1045_21 +# 1045| r1045_23(char) = Load[?] : &:r1045_22, ~m? +# 1045| mu1045_24(char) = Store[#return] : &:r1045_10, r1045_23 +# 1045| v1045_25(void) = ReturnIndirection[#this] : &:r1045_6, ~m? +# 1045| r1045_26(glval) = VariableAddress[#return] : +# 1045| v1045_27(void) = ReturnValue : &:r1045_26, ~m? +# 1045| v1045_28(void) = AliasedUse : ~m? +# 1045| v1045_29(void) = ExitFunction : # 1047| char (void Lambda(int, String const&))::(lambda [] type at line 1047, col. 30)::operator()(float) const # 1047| Block 0 -# 1047| v1047_1(void) = EnterFunction : -# 1047| mu1047_2(unknown) = AliasedDefinition : -# 1047| mu1047_3(unknown) = InitializeNonLocal : -# 1047| r1047_4(glval) = VariableAddress[#this] : -# 1047| mu1047_5(glval) = InitializeParameter[#this] : &:r1047_4 -# 1047| r1047_6(glval) = Load[#this] : &:r1047_4, ~m? -# 1047| mu1047_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1047_6 -# 1047| r1047_8(glval) = VariableAddress[f] : -# 1047| mu1047_9(float) = InitializeParameter[f] : &:r1047_8 -# 1047| r1047_10(glval) = VariableAddress[#return] : -# 1047| r1047_11(glval) = VariableAddress[#this] : -# 1047| r1047_12(lambda [] type at line 1047, col. 30 *) = Load[#this] : &:r1047_11, ~m? -# 1047| r1047_13(glval) = FieldAddress[s] : r1047_12 -# 1047| r1047_14(String &) = Load[?] : &:r1047_13, ~m? -# 1047| r1047_15(glval) = CopyValue : r1047_14 -# 1047| r1047_16(glval) = FunctionAddress[c_str] : -# 1047| r1047_17(char *) = Call[c_str] : func:r1047_16, this:r1047_15 -# 1047| mu1047_18(unknown) = ^CallSideEffect : ~m? -# 1047| v1047_19(void) = ^BufferReadSideEffect[-1] : &:r1047_15, ~m? -# 1047| mu1047_20(String) = ^IndirectMayWriteSideEffect[-1] : &:r1047_15 -# 1047| r1047_21(int) = Constant[0] : -# 1047| r1047_22(glval) = PointerAdd[1] : r1047_17, r1047_21 -# 1047| r1047_23(char) = Load[?] : &:r1047_22, ~m? -# 1047| mu1047_24(char) = Store[#return] : &:r1047_10, r1047_23 -# 1047| v1047_25(void) = ReturnIndirection[#this] : &:r1047_6, ~m? -# 1047| r1047_26(glval) = VariableAddress[#return] : -# 1047| v1047_27(void) = ReturnValue : &:r1047_26, ~m? -# 1047| v1047_28(void) = AliasedUse : ~m? -# 1047| v1047_29(void) = ExitFunction : +# 1047| v1047_1(void) = EnterFunction : +# 1047| mu1047_2(unknown) = AliasedDefinition : +# 1047| mu1047_3(unknown) = InitializeNonLocal : +# 1047| r1047_4(glval) = VariableAddress[#this] : +# 1047| mu1047_5(glval) = InitializeParameter[#this] : &:r1047_4 +# 1047| r1047_6(glval) = Load[#this] : &:r1047_4, ~m? +# 1047| mu1047_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1047_6 +# 1047| r1047_8(glval) = VariableAddress[f] : +# 1047| mu1047_9(float) = InitializeParameter[f] : &:r1047_8 +# 1047| r1047_10(glval) = VariableAddress[#return] : +# 1047| r1047_11(glval) = VariableAddress[#this] : +# 1047| r1047_12(lambda [] type at line 1047, col. 30 *) = Load[#this] : &:r1047_11, ~m? +# 1047| r1047_13(glval) = FieldAddress[s] : r1047_12 +# 1047| r1047_14(String &) = Load[?] : &:r1047_13, ~m? +# 1047| r1047_15(glval) = CopyValue : r1047_14 +# 1047| r1047_16(glval) = FunctionAddress[c_str] : +# 1047| r1047_17(char *) = Call[c_str] : func:r1047_16, this:r1047_15 +# 1047| mu1047_18(unknown) = ^CallSideEffect : ~m? +# 1047| v1047_19(void) = ^BufferReadSideEffect[-1] : &:r1047_15, ~m? +# 1047| r1047_20(int) = Constant[0] : +# 1047| r1047_21(glval) = PointerAdd[1] : r1047_17, r1047_20 +# 1047| r1047_22(char) = Load[?] : &:r1047_21, ~m? +# 1047| mu1047_23(char) = Store[#return] : &:r1047_10, r1047_22 +# 1047| v1047_24(void) = ReturnIndirection[#this] : &:r1047_6, ~m? +# 1047| r1047_25(glval) = VariableAddress[#return] : +# 1047| v1047_26(void) = ReturnValue : &:r1047_25, ~m? +# 1047| v1047_27(void) = AliasedUse : ~m? +# 1047| v1047_28(void) = ExitFunction : # 1049| void (void Lambda(int, String const&))::(lambda [] type at line 1049, col. 30)::~() # 1049| Block 0 @@ -6025,182 +5987,175 @@ ir.cpp: # 1049| char (void Lambda(int, String const&))::(lambda [] type at line 1049, col. 30)::operator()(float) const # 1049| Block 0 -# 1049| v1049_1(void) = EnterFunction : -# 1049| mu1049_2(unknown) = AliasedDefinition : -# 1049| mu1049_3(unknown) = InitializeNonLocal : -# 1049| r1049_4(glval) = VariableAddress[#this] : -# 1049| mu1049_5(glval) = InitializeParameter[#this] : &:r1049_4 -# 1049| r1049_6(glval) = Load[#this] : &:r1049_4, ~m? -# 1049| mu1049_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1049_6 -# 1049| r1049_8(glval) = VariableAddress[f] : -# 1049| mu1049_9(float) = InitializeParameter[f] : &:r1049_8 -# 1049| r1049_10(glval) = VariableAddress[#return] : -# 1049| r1049_11(glval) = VariableAddress[#this] : -# 1049| r1049_12(lambda [] type at line 1049, col. 30 *) = Load[#this] : &:r1049_11, ~m? -# 1049| r1049_13(glval) = FieldAddress[s] : r1049_12 -# 1049| r1049_14(glval) = FunctionAddress[c_str] : -# 1049| r1049_15(char *) = Call[c_str] : func:r1049_14, this:r1049_13 -# 1049| mu1049_16(unknown) = ^CallSideEffect : ~m? -# 1049| v1049_17(void) = ^BufferReadSideEffect[-1] : &:r1049_13, ~m? -# 1049| mu1049_18(String) = ^IndirectMayWriteSideEffect[-1] : &:r1049_13 -# 1049| r1049_19(int) = Constant[0] : -# 1049| r1049_20(glval) = PointerAdd[1] : r1049_15, r1049_19 -# 1049| r1049_21(char) = Load[?] : &:r1049_20, ~m? -# 1049| mu1049_22(char) = Store[#return] : &:r1049_10, r1049_21 -# 1049| v1049_23(void) = ReturnIndirection[#this] : &:r1049_6, ~m? -# 1049| r1049_24(glval) = VariableAddress[#return] : -# 1049| v1049_25(void) = ReturnValue : &:r1049_24, ~m? -# 1049| v1049_26(void) = AliasedUse : ~m? -# 1049| v1049_27(void) = ExitFunction : +# 1049| v1049_1(void) = EnterFunction : +# 1049| mu1049_2(unknown) = AliasedDefinition : +# 1049| mu1049_3(unknown) = InitializeNonLocal : +# 1049| r1049_4(glval) = VariableAddress[#this] : +# 1049| mu1049_5(glval) = InitializeParameter[#this] : &:r1049_4 +# 1049| r1049_6(glval) = Load[#this] : &:r1049_4, ~m? +# 1049| mu1049_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1049_6 +# 1049| r1049_8(glval) = VariableAddress[f] : +# 1049| mu1049_9(float) = InitializeParameter[f] : &:r1049_8 +# 1049| r1049_10(glval) = VariableAddress[#return] : +# 1049| r1049_11(glval) = VariableAddress[#this] : +# 1049| r1049_12(lambda [] type at line 1049, col. 30 *) = Load[#this] : &:r1049_11, ~m? +# 1049| r1049_13(glval) = FieldAddress[s] : r1049_12 +# 1049| r1049_14(glval) = FunctionAddress[c_str] : +# 1049| r1049_15(char *) = Call[c_str] : func:r1049_14, this:r1049_13 +# 1049| mu1049_16(unknown) = ^CallSideEffect : ~m? +# 1049| v1049_17(void) = ^BufferReadSideEffect[-1] : &:r1049_13, ~m? +# 1049| r1049_18(int) = Constant[0] : +# 1049| r1049_19(glval) = PointerAdd[1] : r1049_15, r1049_18 +# 1049| r1049_20(char) = Load[?] : &:r1049_19, ~m? +# 1049| mu1049_21(char) = Store[#return] : &:r1049_10, r1049_20 +# 1049| v1049_22(void) = ReturnIndirection[#this] : &:r1049_6, ~m? +# 1049| r1049_23(glval) = VariableAddress[#return] : +# 1049| v1049_24(void) = ReturnValue : &:r1049_23, ~m? +# 1049| v1049_25(void) = AliasedUse : ~m? +# 1049| v1049_26(void) = ExitFunction : # 1051| char (void Lambda(int, String const&))::(lambda [] type at line 1051, col. 32)::operator()(float) const # 1051| Block 0 -# 1051| v1051_1(void) = EnterFunction : -# 1051| mu1051_2(unknown) = AliasedDefinition : -# 1051| mu1051_3(unknown) = InitializeNonLocal : -# 1051| r1051_4(glval) = VariableAddress[#this] : -# 1051| mu1051_5(glval) = InitializeParameter[#this] : &:r1051_4 -# 1051| r1051_6(glval) = Load[#this] : &:r1051_4, ~m? -# 1051| mu1051_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1051_6 -# 1051| r1051_8(glval) = VariableAddress[f] : -# 1051| mu1051_9(float) = InitializeParameter[f] : &:r1051_8 -# 1051| r1051_10(glval) = VariableAddress[#return] : -# 1051| r1051_11(glval) = VariableAddress[#this] : -# 1051| r1051_12(lambda [] type at line 1051, col. 32 *) = Load[#this] : &:r1051_11, ~m? -# 1051| r1051_13(glval) = FieldAddress[s] : r1051_12 -# 1051| r1051_14(String &) = Load[?] : &:r1051_13, ~m? -# 1051| r1051_15(glval) = CopyValue : r1051_14 -# 1051| r1051_16(glval) = FunctionAddress[c_str] : -# 1051| r1051_17(char *) = Call[c_str] : func:r1051_16, this:r1051_15 -# 1051| mu1051_18(unknown) = ^CallSideEffect : ~m? -# 1051| v1051_19(void) = ^BufferReadSideEffect[-1] : &:r1051_15, ~m? -# 1051| mu1051_20(String) = ^IndirectMayWriteSideEffect[-1] : &:r1051_15 -# 1051| r1051_21(glval) = VariableAddress[#this] : -# 1051| r1051_22(lambda [] type at line 1051, col. 32 *) = Load[#this] : &:r1051_21, ~m? -# 1051| r1051_23(glval) = FieldAddress[x] : r1051_22 -# 1051| r1051_24(int) = Load[?] : &:r1051_23, ~m? -# 1051| r1051_25(glval) = PointerAdd[1] : r1051_17, r1051_24 -# 1051| r1051_26(char) = Load[?] : &:r1051_25, ~m? -# 1051| mu1051_27(char) = Store[#return] : &:r1051_10, r1051_26 -# 1051| v1051_28(void) = ReturnIndirection[#this] : &:r1051_6, ~m? -# 1051| r1051_29(glval) = VariableAddress[#return] : -# 1051| v1051_30(void) = ReturnValue : &:r1051_29, ~m? -# 1051| v1051_31(void) = AliasedUse : ~m? -# 1051| v1051_32(void) = ExitFunction : +# 1051| v1051_1(void) = EnterFunction : +# 1051| mu1051_2(unknown) = AliasedDefinition : +# 1051| mu1051_3(unknown) = InitializeNonLocal : +# 1051| r1051_4(glval) = VariableAddress[#this] : +# 1051| mu1051_5(glval) = InitializeParameter[#this] : &:r1051_4 +# 1051| r1051_6(glval) = Load[#this] : &:r1051_4, ~m? +# 1051| mu1051_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1051_6 +# 1051| r1051_8(glval) = VariableAddress[f] : +# 1051| mu1051_9(float) = InitializeParameter[f] : &:r1051_8 +# 1051| r1051_10(glval) = VariableAddress[#return] : +# 1051| r1051_11(glval) = VariableAddress[#this] : +# 1051| r1051_12(lambda [] type at line 1051, col. 32 *) = Load[#this] : &:r1051_11, ~m? +# 1051| r1051_13(glval) = FieldAddress[s] : r1051_12 +# 1051| r1051_14(String &) = Load[?] : &:r1051_13, ~m? +# 1051| r1051_15(glval) = CopyValue : r1051_14 +# 1051| r1051_16(glval) = FunctionAddress[c_str] : +# 1051| r1051_17(char *) = Call[c_str] : func:r1051_16, this:r1051_15 +# 1051| mu1051_18(unknown) = ^CallSideEffect : ~m? +# 1051| v1051_19(void) = ^BufferReadSideEffect[-1] : &:r1051_15, ~m? +# 1051| r1051_20(glval) = VariableAddress[#this] : +# 1051| r1051_21(lambda [] type at line 1051, col. 32 *) = Load[#this] : &:r1051_20, ~m? +# 1051| r1051_22(glval) = FieldAddress[x] : r1051_21 +# 1051| r1051_23(int) = Load[?] : &:r1051_22, ~m? +# 1051| r1051_24(glval) = PointerAdd[1] : r1051_17, r1051_23 +# 1051| r1051_25(char) = Load[?] : &:r1051_24, ~m? +# 1051| mu1051_26(char) = Store[#return] : &:r1051_10, r1051_25 +# 1051| v1051_27(void) = ReturnIndirection[#this] : &:r1051_6, ~m? +# 1051| r1051_28(glval) = VariableAddress[#return] : +# 1051| v1051_29(void) = ReturnValue : &:r1051_28, ~m? +# 1051| v1051_30(void) = AliasedUse : ~m? +# 1051| v1051_31(void) = ExitFunction : # 1054| char (void Lambda(int, String const&))::(lambda [] type at line 1054, col. 23)::operator()(float) const # 1054| Block 0 -# 1054| v1054_1(void) = EnterFunction : -# 1054| mu1054_2(unknown) = AliasedDefinition : -# 1054| mu1054_3(unknown) = InitializeNonLocal : -# 1054| r1054_4(glval) = VariableAddress[#this] : -# 1054| mu1054_5(glval) = InitializeParameter[#this] : &:r1054_4 -# 1054| r1054_6(glval) = Load[#this] : &:r1054_4, ~m? -# 1054| mu1054_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1054_6 -# 1054| r1054_8(glval) = VariableAddress[f] : -# 1054| mu1054_9(float) = InitializeParameter[f] : &:r1054_8 -# 1054| r1054_10(glval) = VariableAddress[#return] : -# 1054| r1054_11(glval) = VariableAddress[#this] : -# 1054| r1054_12(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_11, ~m? -# 1054| r1054_13(glval) = FieldAddress[s] : r1054_12 -# 1054| r1054_14(String &) = Load[?] : &:r1054_13, ~m? -# 1054| r1054_15(glval) = CopyValue : r1054_14 -# 1054| r1054_16(glval) = FunctionAddress[c_str] : -# 1054| r1054_17(char *) = Call[c_str] : func:r1054_16, this:r1054_15 -# 1054| mu1054_18(unknown) = ^CallSideEffect : ~m? -# 1054| v1054_19(void) = ^BufferReadSideEffect[-1] : &:r1054_15, ~m? -# 1054| mu1054_20(String) = ^IndirectMayWriteSideEffect[-1] : &:r1054_15 -# 1054| r1054_21(glval) = VariableAddress[#this] : -# 1054| r1054_22(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_21, ~m? -# 1054| r1054_23(glval) = FieldAddress[x] : r1054_22 -# 1054| r1054_24(int) = Load[?] : &:r1054_23, ~m? -# 1054| r1054_25(glval) = VariableAddress[#this] : -# 1054| r1054_26(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_25, ~m? -# 1054| r1054_27(glval) = FieldAddress[i] : r1054_26 -# 1054| r1054_28(int) = Load[?] : &:r1054_27, ~m? -# 1054| r1054_29(int) = Add : r1054_24, r1054_28 -# 1054| r1054_30(glval) = VariableAddress[#this] : -# 1054| r1054_31(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_30, ~m? -# 1054| r1054_32(glval) = FieldAddress[j] : r1054_31 -# 1054| r1054_33(int &) = Load[?] : &:r1054_32, ~m? -# 1054| r1054_34(int) = Load[?] : &:r1054_33, ~m? -# 1054| r1054_35(int) = Sub : r1054_29, r1054_34 -# 1054| r1054_36(glval) = PointerAdd[1] : r1054_17, r1054_35 -# 1054| r1054_37(char) = Load[?] : &:r1054_36, ~m? -# 1054| mu1054_38(char) = Store[#return] : &:r1054_10, r1054_37 -# 1054| v1054_39(void) = ReturnIndirection[#this] : &:r1054_6, ~m? -# 1054| r1054_40(glval) = VariableAddress[#return] : -# 1054| v1054_41(void) = ReturnValue : &:r1054_40, ~m? -# 1054| v1054_42(void) = AliasedUse : ~m? -# 1054| v1054_43(void) = ExitFunction : +# 1054| v1054_1(void) = EnterFunction : +# 1054| mu1054_2(unknown) = AliasedDefinition : +# 1054| mu1054_3(unknown) = InitializeNonLocal : +# 1054| r1054_4(glval) = VariableAddress[#this] : +# 1054| mu1054_5(glval) = InitializeParameter[#this] : &:r1054_4 +# 1054| r1054_6(glval) = Load[#this] : &:r1054_4, ~m? +# 1054| mu1054_7(decltype([...](...){...})) = InitializeIndirection[#this] : &:r1054_6 +# 1054| r1054_8(glval) = VariableAddress[f] : +# 1054| mu1054_9(float) = InitializeParameter[f] : &:r1054_8 +# 1054| r1054_10(glval) = VariableAddress[#return] : +# 1054| r1054_11(glval) = VariableAddress[#this] : +# 1054| r1054_12(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_11, ~m? +# 1054| r1054_13(glval) = FieldAddress[s] : r1054_12 +# 1054| r1054_14(String &) = Load[?] : &:r1054_13, ~m? +# 1054| r1054_15(glval) = CopyValue : r1054_14 +# 1054| r1054_16(glval) = FunctionAddress[c_str] : +# 1054| r1054_17(char *) = Call[c_str] : func:r1054_16, this:r1054_15 +# 1054| mu1054_18(unknown) = ^CallSideEffect : ~m? +# 1054| v1054_19(void) = ^BufferReadSideEffect[-1] : &:r1054_15, ~m? +# 1054| r1054_20(glval) = VariableAddress[#this] : +# 1054| r1054_21(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_20, ~m? +# 1054| r1054_22(glval) = FieldAddress[x] : r1054_21 +# 1054| r1054_23(int) = Load[?] : &:r1054_22, ~m? +# 1054| r1054_24(glval) = VariableAddress[#this] : +# 1054| r1054_25(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_24, ~m? +# 1054| r1054_26(glval) = FieldAddress[i] : r1054_25 +# 1054| r1054_27(int) = Load[?] : &:r1054_26, ~m? +# 1054| r1054_28(int) = Add : r1054_23, r1054_27 +# 1054| r1054_29(glval) = VariableAddress[#this] : +# 1054| r1054_30(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_29, ~m? +# 1054| r1054_31(glval) = FieldAddress[j] : r1054_30 +# 1054| r1054_32(int &) = Load[?] : &:r1054_31, ~m? +# 1054| r1054_33(int) = Load[?] : &:r1054_32, ~m? +# 1054| r1054_34(int) = Sub : r1054_28, r1054_33 +# 1054| r1054_35(glval) = PointerAdd[1] : r1054_17, r1054_34 +# 1054| r1054_36(char) = Load[?] : &:r1054_35, ~m? +# 1054| mu1054_37(char) = Store[#return] : &:r1054_10, r1054_36 +# 1054| v1054_38(void) = ReturnIndirection[#this] : &:r1054_6, ~m? +# 1054| r1054_39(glval) = VariableAddress[#return] : +# 1054| v1054_40(void) = ReturnValue : &:r1054_39, ~m? +# 1054| v1054_41(void) = AliasedUse : ~m? +# 1054| v1054_42(void) = ExitFunction : # 1077| void RangeBasedFor(vector const&) # 1077| Block 0 -# 1077| v1077_1(void) = EnterFunction : -# 1077| mu1077_2(unknown) = AliasedDefinition : -# 1077| mu1077_3(unknown) = InitializeNonLocal : -# 1077| r1077_4(glval &>) = VariableAddress[v] : -# 1077| mu1077_5(vector &) = InitializeParameter[v] : &:r1077_4 -# 1077| r1077_6(vector &) = Load[v] : &:r1077_4, ~m? -# 1077| mu1077_7(unknown) = InitializeIndirection[v] : &:r1077_6 -# 1078| r1078_1(glval &>) = VariableAddress[(__range)] : -# 1078| r1078_2(glval &>) = VariableAddress[v] : -# 1078| r1078_3(vector &) = Load[v] : &:r1078_2, ~m? -# 1078| r1078_4(glval>) = CopyValue : r1078_3 -# 1078| r1078_5(vector &) = CopyValue : r1078_4 -# 1078| mu1078_6(vector &) = Store[(__range)] : &:r1078_1, r1078_5 -# 1078| r1078_7(glval) = VariableAddress[(__begin)] : -# 1078| r1078_8(glval &>) = VariableAddress[(__range)] : -# 1078| r1078_9(vector &) = Load[(__range)] : &:r1078_8, ~m? -#-----| r0_1(glval>) = CopyValue : r1078_9 -# 1078| r1078_10(glval) = FunctionAddress[begin] : -# 1078| r1078_11(iterator) = Call[begin] : func:r1078_10, this:r0_1 -# 1078| mu1078_12(unknown) = ^CallSideEffect : ~m? -#-----| v0_2(void) = ^BufferReadSideEffect[-1] : &:r0_1, ~m? -#-----| mu0_3(vector) = ^IndirectMayWriteSideEffect[-1] : &:r0_1 -# 1078| mu1078_13(iterator) = Store[(__begin)] : &:r1078_7, r1078_11 -# 1078| r1078_14(glval) = VariableAddress[(__end)] : -# 1078| r1078_15(glval &>) = VariableAddress[(__range)] : -# 1078| r1078_16(vector &) = Load[(__range)] : &:r1078_15, ~m? -#-----| r0_4(glval>) = CopyValue : r1078_16 -# 1078| r1078_17(glval) = FunctionAddress[end] : -# 1078| r1078_18(iterator) = Call[end] : func:r1078_17, this:r0_4 -# 1078| mu1078_19(unknown) = ^CallSideEffect : ~m? -#-----| v0_5(void) = ^BufferReadSideEffect[-1] : &:r0_4, ~m? -#-----| mu0_6(vector) = ^IndirectMayWriteSideEffect[-1] : &:r0_4 -# 1078| mu1078_20(iterator) = Store[(__end)] : &:r1078_14, r1078_18 +# 1077| v1077_1(void) = EnterFunction : +# 1077| mu1077_2(unknown) = AliasedDefinition : +# 1077| mu1077_3(unknown) = InitializeNonLocal : +# 1077| r1077_4(glval &>) = VariableAddress[v] : +# 1077| mu1077_5(vector &) = InitializeParameter[v] : &:r1077_4 +# 1077| r1077_6(vector &) = Load[v] : &:r1077_4, ~m? +# 1077| mu1077_7(unknown) = InitializeIndirection[v] : &:r1077_6 +# 1078| r1078_1(glval &>) = VariableAddress[(__range)] : +# 1078| r1078_2(glval &>) = VariableAddress[v] : +# 1078| r1078_3(vector &) = Load[v] : &:r1078_2, ~m? +# 1078| r1078_4(glval>) = CopyValue : r1078_3 +# 1078| r1078_5(vector &) = CopyValue : r1078_4 +# 1078| mu1078_6(vector &) = Store[(__range)] : &:r1078_1, r1078_5 +# 1078| r1078_7(glval) = VariableAddress[(__begin)] : +# 1078| r1078_8(glval &>) = VariableAddress[(__range)] : +# 1078| r1078_9(vector &) = Load[(__range)] : &:r1078_8, ~m? +#-----| r0_1(glval>) = CopyValue : r1078_9 +# 1078| r1078_10(glval) = FunctionAddress[begin] : +# 1078| r1078_11(iterator) = Call[begin] : func:r1078_10, this:r0_1 +# 1078| mu1078_12(unknown) = ^CallSideEffect : ~m? +#-----| v0_2(void) = ^BufferReadSideEffect[-1] : &:r0_1, ~m? +# 1078| mu1078_13(iterator) = Store[(__begin)] : &:r1078_7, r1078_11 +# 1078| r1078_14(glval) = VariableAddress[(__end)] : +# 1078| r1078_15(glval &>) = VariableAddress[(__range)] : +# 1078| r1078_16(vector &) = Load[(__range)] : &:r1078_15, ~m? +#-----| r0_3(glval>) = CopyValue : r1078_16 +# 1078| r1078_17(glval) = FunctionAddress[end] : +# 1078| r1078_18(iterator) = Call[end] : func:r1078_17, this:r0_3 +# 1078| mu1078_19(unknown) = ^CallSideEffect : ~m? +#-----| v0_4(void) = ^BufferReadSideEffect[-1] : &:r0_3, ~m? +# 1078| mu1078_20(iterator) = Store[(__end)] : &:r1078_14, r1078_18 #-----| Goto -> Block 1 # 1078| Block 1 -# 1078| r1078_21(glval) = VariableAddress[(__begin)] : -#-----| r0_7(glval) = Convert : r1078_21 -# 1078| r1078_22(glval) = FunctionAddress[operator!=] : -# 1078| r1078_23(glval) = VariableAddress[(__end)] : -# 1078| r1078_24(iterator) = Load[(__end)] : &:r1078_23, ~m? -# 1078| r1078_25(bool) = Call[operator!=] : func:r1078_22, this:r0_7, 0:r1078_24 -# 1078| mu1078_26(unknown) = ^CallSideEffect : ~m? -#-----| v0_8(void) = ^BufferReadSideEffect[-1] : &:r0_7, ~m? -#-----| mu0_9(iterator) = ^IndirectMayWriteSideEffect[-1] : &:r0_7 -# 1078| v1078_27(void) = ConditionalBranch : r1078_25 +# 1078| r1078_21(glval) = VariableAddress[(__begin)] : +#-----| r0_5(glval) = Convert : r1078_21 +# 1078| r1078_22(glval) = FunctionAddress[operator!=] : +# 1078| r1078_23(glval) = VariableAddress[(__end)] : +# 1078| r1078_24(iterator) = Load[(__end)] : &:r1078_23, ~m? +# 1078| r1078_25(bool) = Call[operator!=] : func:r1078_22, this:r0_5, 0:r1078_24 +# 1078| mu1078_26(unknown) = ^CallSideEffect : ~m? +#-----| v0_6(void) = ^BufferReadSideEffect[-1] : &:r0_5, ~m? +# 1078| v1078_27(void) = ConditionalBranch : r1078_25 #-----| False -> Block 5 #-----| True -> Block 2 # 1078| Block 2 -# 1078| r1078_28(glval) = VariableAddress[e] : -# 1078| r1078_29(glval) = VariableAddress[(__begin)] : -#-----| r0_10(glval) = Convert : r1078_29 -# 1078| r1078_30(glval) = FunctionAddress[operator*] : -# 1078| r1078_31(int &) = Call[operator*] : func:r1078_30, this:r0_10 -# 1078| mu1078_32(unknown) = ^CallSideEffect : ~m? -#-----| v0_11(void) = ^BufferReadSideEffect[-1] : &:r0_10, ~m? -#-----| mu0_12(iterator) = ^IndirectMayWriteSideEffect[-1] : &:r0_10 -# 1078| r1078_33(int) = Load[?] : &:r1078_31, ~m? -# 1078| mu1078_34(int) = Store[e] : &:r1078_28, r1078_33 -# 1079| r1079_1(glval) = VariableAddress[e] : -# 1079| r1079_2(int) = Load[e] : &:r1079_1, ~m? -# 1079| r1079_3(int) = Constant[0] : -# 1079| r1079_4(bool) = CompareGT : r1079_2, r1079_3 -# 1079| v1079_5(void) = ConditionalBranch : r1079_4 +# 1078| r1078_28(glval) = VariableAddress[e] : +# 1078| r1078_29(glval) = VariableAddress[(__begin)] : +#-----| r0_7(glval) = Convert : r1078_29 +# 1078| r1078_30(glval) = FunctionAddress[operator*] : +# 1078| r1078_31(int &) = Call[operator*] : func:r1078_30, this:r0_7 +# 1078| mu1078_32(unknown) = ^CallSideEffect : ~m? +#-----| v0_8(void) = ^BufferReadSideEffect[-1] : &:r0_7, ~m? +# 1078| r1078_33(int) = Load[?] : &:r1078_31, ~m? +# 1078| mu1078_34(int) = Store[e] : &:r1078_28, r1078_33 +# 1079| r1079_1(glval) = VariableAddress[e] : +# 1079| r1079_2(int) = Load[e] : &:r1079_1, ~m? +# 1079| r1079_3(int) = Constant[0] : +# 1079| r1079_4(bool) = CompareGT : r1079_2, r1079_3 +# 1079| v1079_5(void) = ConditionalBranch : r1079_4 #-----| False -> Block 4 #-----| True -> Block 3 @@ -6220,45 +6175,42 @@ ir.cpp: #-----| Goto (back edge) -> Block 1 # 1084| Block 5 -# 1084| r1084_1(glval &>) = VariableAddress[(__range)] : -# 1084| r1084_2(glval &>) = VariableAddress[v] : -# 1084| r1084_3(vector &) = Load[v] : &:r1084_2, ~m? -# 1084| r1084_4(glval>) = CopyValue : r1084_3 -# 1084| r1084_5(vector &) = CopyValue : r1084_4 -# 1084| mu1084_6(vector &) = Store[(__range)] : &:r1084_1, r1084_5 -# 1084| r1084_7(glval) = VariableAddress[(__begin)] : -# 1084| r1084_8(glval &>) = VariableAddress[(__range)] : -# 1084| r1084_9(vector &) = Load[(__range)] : &:r1084_8, ~m? -#-----| r0_13(glval>) = CopyValue : r1084_9 -# 1084| r1084_10(glval) = FunctionAddress[begin] : -# 1084| r1084_11(iterator) = Call[begin] : func:r1084_10, this:r0_13 -# 1084| mu1084_12(unknown) = ^CallSideEffect : ~m? -#-----| v0_14(void) = ^BufferReadSideEffect[-1] : &:r0_13, ~m? -#-----| mu0_15(vector) = ^IndirectMayWriteSideEffect[-1] : &:r0_13 -# 1084| mu1084_13(iterator) = Store[(__begin)] : &:r1084_7, r1084_11 -# 1084| r1084_14(glval) = VariableAddress[(__end)] : -# 1084| r1084_15(glval &>) = VariableAddress[(__range)] : -# 1084| r1084_16(vector &) = Load[(__range)] : &:r1084_15, ~m? -#-----| r0_16(glval>) = CopyValue : r1084_16 -# 1084| r1084_17(glval) = FunctionAddress[end] : -# 1084| r1084_18(iterator) = Call[end] : func:r1084_17, this:r0_16 -# 1084| mu1084_19(unknown) = ^CallSideEffect : ~m? -#-----| v0_17(void) = ^BufferReadSideEffect[-1] : &:r0_16, ~m? -#-----| mu0_18(vector) = ^IndirectMayWriteSideEffect[-1] : &:r0_16 -# 1084| mu1084_20(iterator) = Store[(__end)] : &:r1084_14, r1084_18 +# 1084| r1084_1(glval &>) = VariableAddress[(__range)] : +# 1084| r1084_2(glval &>) = VariableAddress[v] : +# 1084| r1084_3(vector &) = Load[v] : &:r1084_2, ~m? +# 1084| r1084_4(glval>) = CopyValue : r1084_3 +# 1084| r1084_5(vector &) = CopyValue : r1084_4 +# 1084| mu1084_6(vector &) = Store[(__range)] : &:r1084_1, r1084_5 +# 1084| r1084_7(glval) = VariableAddress[(__begin)] : +# 1084| r1084_8(glval &>) = VariableAddress[(__range)] : +# 1084| r1084_9(vector &) = Load[(__range)] : &:r1084_8, ~m? +#-----| r0_9(glval>) = CopyValue : r1084_9 +# 1084| r1084_10(glval) = FunctionAddress[begin] : +# 1084| r1084_11(iterator) = Call[begin] : func:r1084_10, this:r0_9 +# 1084| mu1084_12(unknown) = ^CallSideEffect : ~m? +#-----| v0_10(void) = ^BufferReadSideEffect[-1] : &:r0_9, ~m? +# 1084| mu1084_13(iterator) = Store[(__begin)] : &:r1084_7, r1084_11 +# 1084| r1084_14(glval) = VariableAddress[(__end)] : +# 1084| r1084_15(glval &>) = VariableAddress[(__range)] : +# 1084| r1084_16(vector &) = Load[(__range)] : &:r1084_15, ~m? +#-----| r0_11(glval>) = CopyValue : r1084_16 +# 1084| r1084_17(glval) = FunctionAddress[end] : +# 1084| r1084_18(iterator) = Call[end] : func:r1084_17, this:r0_11 +# 1084| mu1084_19(unknown) = ^CallSideEffect : ~m? +#-----| v0_12(void) = ^BufferReadSideEffect[-1] : &:r0_11, ~m? +# 1084| mu1084_20(iterator) = Store[(__end)] : &:r1084_14, r1084_18 #-----| Goto -> Block 6 # 1084| Block 6 -# 1084| r1084_21(glval) = VariableAddress[(__begin)] : -#-----| r0_19(glval) = Convert : r1084_21 -# 1084| r1084_22(glval) = FunctionAddress[operator!=] : -# 1084| r1084_23(glval) = VariableAddress[(__end)] : -# 1084| r1084_24(iterator) = Load[(__end)] : &:r1084_23, ~m? -# 1084| r1084_25(bool) = Call[operator!=] : func:r1084_22, this:r0_19, 0:r1084_24 -# 1084| mu1084_26(unknown) = ^CallSideEffect : ~m? -#-----| v0_20(void) = ^BufferReadSideEffect[-1] : &:r0_19, ~m? -#-----| mu0_21(iterator) = ^IndirectMayWriteSideEffect[-1] : &:r0_19 -# 1084| v1084_27(void) = ConditionalBranch : r1084_25 +# 1084| r1084_21(glval) = VariableAddress[(__begin)] : +#-----| r0_13(glval) = Convert : r1084_21 +# 1084| r1084_22(glval) = FunctionAddress[operator!=] : +# 1084| r1084_23(glval) = VariableAddress[(__end)] : +# 1084| r1084_24(iterator) = Load[(__end)] : &:r1084_23, ~m? +# 1084| r1084_25(bool) = Call[operator!=] : func:r1084_22, this:r0_13, 0:r1084_24 +# 1084| mu1084_26(unknown) = ^CallSideEffect : ~m? +#-----| v0_14(void) = ^BufferReadSideEffect[-1] : &:r0_13, ~m? +# 1084| v1084_27(void) = ConditionalBranch : r1084_25 #-----| False -> Block 10 #-----| True -> Block 8 @@ -6273,24 +6225,23 @@ ir.cpp: #-----| Goto (back edge) -> Block 6 # 1084| Block 8 -# 1084| r1084_35(glval) = VariableAddress[e] : -# 1084| r1084_36(glval) = VariableAddress[(__begin)] : -#-----| r0_22(glval) = Convert : r1084_36 -# 1084| r1084_37(glval) = FunctionAddress[operator*] : -# 1084| r1084_38(int &) = Call[operator*] : func:r1084_37, this:r0_22 -# 1084| mu1084_39(unknown) = ^CallSideEffect : ~m? -#-----| v0_23(void) = ^BufferReadSideEffect[-1] : &:r0_22, ~m? -#-----| mu0_24(iterator) = ^IndirectMayWriteSideEffect[-1] : &:r0_22 -# 1084| r1084_40(glval) = CopyValue : r1084_38 -# 1084| r1084_41(glval) = Convert : r1084_40 -# 1084| r1084_42(int &) = CopyValue : r1084_41 -# 1084| mu1084_43(int &) = Store[e] : &:r1084_35, r1084_42 -# 1085| r1085_1(glval) = VariableAddress[e] : -# 1085| r1085_2(int &) = Load[e] : &:r1085_1, ~m? -# 1085| r1085_3(int) = Load[?] : &:r1085_2, ~m? -# 1085| r1085_4(int) = Constant[5] : -# 1085| r1085_5(bool) = CompareLT : r1085_3, r1085_4 -# 1085| v1085_6(void) = ConditionalBranch : r1085_5 +# 1084| r1084_35(glval) = VariableAddress[e] : +# 1084| r1084_36(glval) = VariableAddress[(__begin)] : +#-----| r0_15(glval) = Convert : r1084_36 +# 1084| r1084_37(glval) = FunctionAddress[operator*] : +# 1084| r1084_38(int &) = Call[operator*] : func:r1084_37, this:r0_15 +# 1084| mu1084_39(unknown) = ^CallSideEffect : ~m? +#-----| v0_16(void) = ^BufferReadSideEffect[-1] : &:r0_15, ~m? +# 1084| r1084_40(glval) = CopyValue : r1084_38 +# 1084| r1084_41(glval) = Convert : r1084_40 +# 1084| r1084_42(int &) = CopyValue : r1084_41 +# 1084| mu1084_43(int &) = Store[e] : &:r1084_35, r1084_42 +# 1085| r1085_1(glval) = VariableAddress[e] : +# 1085| r1085_2(int &) = Load[e] : &:r1085_1, ~m? +# 1085| r1085_3(int) = Load[?] : &:r1085_2, ~m? +# 1085| r1085_4(int) = Constant[5] : +# 1085| r1085_5(bool) = CompareLT : r1085_3, r1085_4 +# 1085| v1085_6(void) = ConditionalBranch : r1085_5 #-----| False -> Block 7 #-----| True -> Block 9 @@ -6475,8 +6426,7 @@ ir.cpp: # 1149| mu1149_17(unknown) = ^CallSideEffect : ~m? # 1149| mu1149_18(String) = ^IndirectMayWriteSideEffect[-1] : &:r1149_11 # 1149| v1149_19(void) = ^BufferReadSideEffect[0] : &:r1149_15, ~m? -# 1149| mu1149_20(unknown) = ^BufferMayWriteSideEffect[0] : &:r1149_15 -# 1149| v1149_21(void) = ThrowValue : &:r1149_11, ~m? +# 1149| v1149_20(void) = ThrowValue : &:r1149_11, ~m? #-----| Exception -> Block 9 # 1151| Block 8 @@ -6504,8 +6454,7 @@ ir.cpp: # 1154| mu1154_7(unknown) = ^CallSideEffect : ~m? # 1154| mu1154_8(String) = ^IndirectMayWriteSideEffect[-1] : &:r1154_1 # 1154| v1154_9(void) = ^BufferReadSideEffect[0] : &:r1154_5, ~m? -# 1154| mu1154_10(unknown) = ^BufferMayWriteSideEffect[0] : &:r1154_5 -# 1154| v1154_11(void) = ThrowValue : &:r1154_1, ~m? +# 1154| v1154_10(void) = ThrowValue : &:r1154_1, ~m? #-----| Exception -> Block 2 # 1156| Block 11 @@ -6631,7 +6580,6 @@ ir.cpp: # 1179| mu1179_7(unknown) = ^CallSideEffect : ~m? # 1179| mu1179_8(String) = ^IndirectMayWriteSideEffect[-1] : &:r1179_1 # 1179| v1179_9(void) = ^BufferReadSideEffect[0] : &:r1179_5, ~m? -# 1179| mu1179_10(unknown) = ^BufferMayWriteSideEffect[0] : &:r1179_5 # 1178| r1178_4(glval) = VariableAddress[#return] : # 1178| v1178_5(void) = ReturnValue : &:r1178_4, ~m? # 1178| v1178_6(void) = AliasedUse : ~m? @@ -6887,9 +6835,8 @@ ir.cpp: # 1242| mu1242_9(unknown) = ^CallSideEffect : ~m? # 1242| mu1242_10(String) = ^IndirectMayWriteSideEffect[-1] : &:r1242_4 # 1242| v1242_11(void) = ^BufferReadSideEffect[0] : &:r1242_7, ~m? -# 1242| mu1242_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r1242_7 -# 1242| r1242_13(bool) = Constant[1] : -# 1242| mu1242_14(bool) = Store[b#init] : &:r1242_1, r1242_13 +# 1242| r1242_12(bool) = Constant[1] : +# 1242| mu1242_13(bool) = Store[b#init] : &:r1242_1, r1242_12 #-----| Goto -> Block 4 # 1243| Block 4 @@ -6908,9 +6855,8 @@ ir.cpp: # 1243| mu1243_9(unknown) = ^CallSideEffect : ~m? # 1243| mu1243_10(String) = ^IndirectMayWriteSideEffect[-1] : &:r1243_4 # 1243| v1243_11(void) = ^BufferReadSideEffect[0] : &:r1243_7, ~m? -# 1243| mu1243_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r1243_7 -# 1243| r1243_13(bool) = Constant[1] : -# 1243| mu1243_14(bool) = Store[c#init] : &:r1243_1, r1243_13 +# 1243| r1243_12(bool) = Constant[1] : +# 1243| mu1243_13(bool) = Store[c#init] : &:r1243_1, r1243_12 #-----| Goto -> Block 6 # 1244| Block 6 @@ -7528,7 +7474,6 @@ ir.cpp: # 1369| v1369_5(void) = Call[acceptRef] : func:r1369_1, 0:r1369_4 # 1369| mu1369_6(unknown) = ^CallSideEffect : ~m? # 1369| v1369_7(void) = ^BufferReadSideEffect[0] : &:r1369_4, ~m? -# 1369| mu1369_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r1369_4 # 1370| r1370_1(glval) = FunctionAddress[acceptRef] : # 1370| r1370_2(glval) = VariableAddress[#temp1370:23] : # 1370| mu1370_3(String) = Uninitialized[#temp1370:23] : &:r1370_2 @@ -7539,12 +7484,10 @@ ir.cpp: # 1370| mu1370_8(unknown) = ^CallSideEffect : ~m? # 1370| mu1370_9(String) = ^IndirectMayWriteSideEffect[-1] : &:r1370_2 # 1370| v1370_10(void) = ^BufferReadSideEffect[0] : &:r1370_6, ~m? -# 1370| mu1370_11(unknown) = ^BufferMayWriteSideEffect[0] : &:r1370_6 -# 1370| r1370_12(String &) = CopyValue : r1370_2 -# 1370| v1370_13(void) = Call[acceptRef] : func:r1370_1, 0:r1370_12 -# 1370| mu1370_14(unknown) = ^CallSideEffect : ~m? -# 1370| v1370_15(void) = ^BufferReadSideEffect[0] : &:r1370_12, ~m? -# 1370| mu1370_16(unknown) = ^BufferMayWriteSideEffect[0] : &:r1370_12 +# 1370| r1370_11(String &) = CopyValue : r1370_2 +# 1370| v1370_12(void) = Call[acceptRef] : func:r1370_1, 0:r1370_11 +# 1370| mu1370_13(unknown) = ^CallSideEffect : ~m? +# 1370| v1370_14(void) = ^BufferReadSideEffect[0] : &:r1370_11, ~m? # 1371| r1371_1(glval) = FunctionAddress[acceptValue] : # 1371| r1371_2(glval) = VariableAddress[#temp1371:17] : # 1371| mu1371_3(String) = Uninitialized[#temp1371:17] : &:r1371_2 @@ -7556,10 +7499,9 @@ ir.cpp: # 1371| mu1371_9(unknown) = ^CallSideEffect : ~m? # 1371| mu1371_10(String) = ^IndirectMayWriteSideEffect[-1] : &:r1371_2 # 1371| v1371_11(void) = ^BufferReadSideEffect[0] : &:r1371_7, ~m? -# 1371| mu1371_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r1371_7 -# 1371| r1371_13(String) = Load[#temp1371:17] : &:r1371_2, ~m? -# 1371| v1371_14(void) = Call[acceptValue] : func:r1371_1, 0:r1371_13 -# 1371| mu1371_15(unknown) = ^CallSideEffect : ~m? +# 1371| r1371_12(String) = Load[#temp1371:17] : &:r1371_2, ~m? +# 1371| v1371_13(void) = Call[acceptValue] : func:r1371_1, 0:r1371_12 +# 1371| mu1371_14(unknown) = ^CallSideEffect : ~m? # 1372| r1372_1(glval) = FunctionAddress[acceptValue] : # 1372| r1372_2(glval) = VariableAddress[#temp1372:25] : # 1372| mu1372_3(String) = Uninitialized[#temp1372:25] : &:r1372_2 @@ -7570,10 +7512,9 @@ ir.cpp: # 1372| mu1372_8(unknown) = ^CallSideEffect : ~m? # 1372| mu1372_9(String) = ^IndirectMayWriteSideEffect[-1] : &:r1372_2 # 1372| v1372_10(void) = ^BufferReadSideEffect[0] : &:r1372_6, ~m? -# 1372| mu1372_11(unknown) = ^BufferMayWriteSideEffect[0] : &:r1372_6 -# 1372| r1372_12(String) = Load[#temp1372:25] : &:r1372_2, ~m? -# 1372| v1372_13(void) = Call[acceptValue] : func:r1372_1, 0:r1372_12 -# 1372| mu1372_14(unknown) = ^CallSideEffect : ~m? +# 1372| r1372_11(String) = Load[#temp1372:25] : &:r1372_2, ~m? +# 1372| v1372_12(void) = Call[acceptValue] : func:r1372_1, 0:r1372_11 +# 1372| mu1372_13(unknown) = ^CallSideEffect : ~m? # 1373| r1373_1(glval) = VariableAddress[#temp1373:5] : # 1373| mu1373_2(String) = Uninitialized[#temp1373:5] : &:r1373_1 # 1373| r1373_3(glval) = FunctionAddress[String] : @@ -7585,7 +7526,6 @@ ir.cpp: # 1373| r1373_9(char *) = Call[c_str] : func:r1373_8, this:r1373_7 # 1373| mu1373_10(unknown) = ^CallSideEffect : ~m? # 1373| v1373_11(void) = ^BufferReadSideEffect[-1] : &:r1373_7, ~m? -# 1373| mu1373_12(String) = ^IndirectMayWriteSideEffect[-1] : &:r1373_7 # 1374| r1374_1(glval) = VariableAddress[#temp1374:5] : # 1374| r1374_2(glval) = FunctionAddress[returnValue] : # 1374| r1374_3(String) = Call[returnValue] : func:r1374_2 @@ -7596,7 +7536,6 @@ ir.cpp: # 1374| r1374_8(char *) = Call[c_str] : func:r1374_7, this:r1374_6 # 1374| mu1374_9(unknown) = ^CallSideEffect : ~m? # 1374| v1374_10(void) = ^BufferReadSideEffect[-1] : &:r1374_6, ~m? -# 1374| mu1374_11(String) = ^IndirectMayWriteSideEffect[-1] : &:r1374_6 # 1376| r1376_1(glval) = VariableAddress[#temp1376:5] : # 1376| r1376_2(glval) = FunctionAddress[defaultConstruct] : # 1376| r1376_3(String) = Call[defaultConstruct] : func:r1376_2 @@ -7636,7 +7575,6 @@ ir.cpp: # 1383| v1383_5(void) = Call[acceptRef] : func:r1383_1, 0:r1383_4 # 1383| mu1383_6(unknown) = ^CallSideEffect : ~m? # 1383| v1383_7(void) = ^BufferReadSideEffect[0] : &:r1383_4, ~m? -# 1383| mu1383_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r1383_4 # 1384| r1384_1(glval) = FunctionAddress[acceptValue] : # 1384| r1384_2(glval) = VariableAddress[#temp1384:17] : # 1384| r1384_3(glval) = VariableAddress[d] : @@ -7706,7 +7644,6 @@ ir.cpp: # 1395| v1395_5(void) = Call[acceptRef] : func:r1395_1, 0:r1395_4 # 1395| mu1395_6(unknown) = ^CallSideEffect : ~m? # 1395| v1395_7(void) = ^BufferReadSideEffect[0] : &:r1395_4, ~m? -# 1395| mu1395_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r1395_4 # 1396| r1396_1(glval) = FunctionAddress[acceptValue] : # 1396| r1396_2(glval) = VariableAddress[#temp1396:17] : # 1396| mu1396_3(copy_constructor) = Uninitialized[#temp1396:17] : &:r1396_2 @@ -7718,10 +7655,9 @@ ir.cpp: # 1396| mu1396_9(unknown) = ^CallSideEffect : ~m? # 1396| mu1396_10(copy_constructor) = ^IndirectMayWriteSideEffect[-1] : &:r1396_2 # 1396| v1396_11(void) = ^BufferReadSideEffect[0] : &:r1396_7, ~m? -# 1396| mu1396_12(unknown) = ^BufferMayWriteSideEffect[0] : &:r1396_7 -# 1396| r1396_13(copy_constructor) = Load[#temp1396:17] : &:r1396_2, ~m? -# 1396| v1396_14(void) = Call[acceptValue] : func:r1396_1, 0:r1396_13 -# 1396| mu1396_15(unknown) = ^CallSideEffect : ~m? +# 1396| r1396_12(copy_constructor) = Load[#temp1396:17] : &:r1396_2, ~m? +# 1396| v1396_13(void) = Call[acceptValue] : func:r1396_1, 0:r1396_12 +# 1396| mu1396_14(unknown) = ^CallSideEffect : ~m? # 1397| r1397_1(glval) = VariableAddress[#temp1397:5] : # 1397| mu1397_2(copy_constructor) = Uninitialized[#temp1397:5] : &:r1397_1 # 1397| r1397_3(glval) = FunctionAddress[copy_constructor] : @@ -7789,7 +7725,6 @@ ir.cpp: # 1408| v1408_5(void) = Call[acceptRef] : func:r1408_1, 0:r1408_4 # 1408| mu1408_6(unknown) = ^CallSideEffect : ~m? # 1408| v1408_7(void) = ^BufferReadSideEffect[0] : &:r1408_4, ~m? -# 1408| mu1408_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r1408_4 # 1409| r1409_1(glval) = FunctionAddress[acceptValue] : # 1409| r1409_2(glval) = VariableAddress[p] : # 1409| r1409_3(Point) = Load[p] : &:r1409_2, ~m? @@ -7917,8 +7852,7 @@ ir.cpp: # 1447| r1447_10(float) = Call[f] : func:r1447_9, this:r1447_8 # 1447| mu1447_11(unknown) = ^CallSideEffect : ~m? # 1447| v1447_12(void) = ^BufferReadSideEffect[-1] : &:r1447_8, ~m? -# 1447| mu1447_13(POD_Base) = ^IndirectMayWriteSideEffect[-1] : &:r1447_8 -# 1447| mu1447_14(float) = Store[f] : &:r1447_1, r1447_10 +# 1447| mu1447_13(float) = Store[f] : &:r1447_1, r1447_10 # 1448| v1448_1(void) = NoOp : # 1443| v1443_4(void) = ReturnVoid : # 1443| v1443_5(void) = AliasedUse : ~m? From 40c592ab8535c88c3e22ec8531d7eb5b98d26ffa Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 19 Feb 2021 15:29:23 +0100 Subject: [PATCH 105/757] Python: Introduce DataFlowOnlyInternalUse to avoid re-evaluation --- config/identical-files.json | 3 +- python/ql/src/semmle/python/Concepts.qll | 3 +- .../dataflow/new/DataFlowOnlyInternalUse.qll | 40 + .../internal/DataFlowImplOnlyInternalUse.qll | 4153 +++++++++++++++++ 4 files changed, 4197 insertions(+), 2 deletions(-) create mode 100644 python/ql/src/semmle/python/dataflow/new/DataFlowOnlyInternalUse.qll create mode 100644 python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplOnlyInternalUse.qll diff --git a/config/identical-files.json b/config/identical-files.json index d68dabba861..5b84fab45cc 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -22,7 +22,8 @@ "python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll", "python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll", "python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll", - "python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll" + "python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll", + "python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplOnlyInternalUse.qll" ], "DataFlow Java/C++/C#/Python Common": [ "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll", diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll index 4f0d4ed55d9..ed0a19d197a 100644 --- a/python/ql/src/semmle/python/Concepts.qll +++ b/python/ql/src/semmle/python/Concepts.qll @@ -6,6 +6,7 @@ import python private import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.DataFlowOnlyInternalUse private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.TaintTracking private import semmle.python.Frameworks @@ -565,7 +566,7 @@ module Cryptography { /** * A data-flow configuration for tracking integer literals. */ - private class IntegerLiteralTrackerConfiguration extends DataFlow::Configuration { + private class IntegerLiteralTrackerConfiguration extends DataFlowOnlyInternalUse::Configuration { IntegerLiteralTrackerConfiguration() { this = "IntegerLiteralTrackerConfiguration" } override predicate isSource(DataFlow::Node source) { diff --git a/python/ql/src/semmle/python/dataflow/new/DataFlowOnlyInternalUse.qll b/python/ql/src/semmle/python/dataflow/new/DataFlowOnlyInternalUse.qll new file mode 100644 index 00000000000..8768f25a534 --- /dev/null +++ b/python/ql/src/semmle/python/dataflow/new/DataFlowOnlyInternalUse.qll @@ -0,0 +1,40 @@ +/** + * INTERNAL: Do not use. + * + * This copy exists to allow internal non-query usage of global data-flow analyses. If + * we used the same copy as was used in multiple queries (A, B, C), then all internal + * non-query configurations would have to be re-evaluated for _each_ query, which is + * expensive. By having a separate copy, we avoid this re-evaluation. + * + * Provides a library for local (intra-procedural) and global (inter-procedural) + * data flow analysis: deciding whether data can flow from a _source_ to a + * _sink_. + * + * Unless configured otherwise, _flow_ means that the exact value of + * the source may reach the sink. We do not track flow across pointer + * dereferences or array indexing. To track these types of flow, where the + * exact value may not be preserved, import + * `semmle.python.dataflow.new.TaintTracking`. + * + * To use global (interprocedural) data flow, extend the class + * `DataFlow::Configuration` as documented on that class. To use local + * (intraprocedural) data flow, call `DataFlow::localFlow` or + * `DataFlow::localFlowStep` with arguments of type `DataFlow::Node`. + */ + +private import python + +/** + * INTERNAL: Do not use. + * + * This copy exists to allow internal non-query usage of global data-flow analyses. If + * we used the same copy as was used in multiple queries (A, B, C), then all internal + * non-query configurations would have to be re-evaluated for _each_ query, which is + * expensive. By having a separate copy, we avoid this re-evaluation. + * + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ +module DataFlowOnlyInternalUse { + import semmle.python.dataflow.new.internal.DataFlowImplOnlyInternalUse +} diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplOnlyInternalUse.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplOnlyInternalUse.qll new file mode 100644 index 00000000000..59cc8d529a7 --- /dev/null +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplOnlyInternalUse.qll @@ -0,0 +1,4153 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ```ql + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ```ql + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + abstract predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + abstract predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** Holds if data flow through nodes guarded by `guard` is prohibited. */ + predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * Holds if the additional flow step from `node1` to `node2` must be taken + * into account in the analysis. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlow(_, sink) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + /** + * Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev` + * measured in approximate number of interprocedural steps. + */ + int explorationLimit() { none() } + + /** + * Holds if there is a partial data flow path from `source` to `node`. The + * approximate distance between `node` and the closest source is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards sink definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sources is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + */ + final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) { + partialFlow(source, node, this) and + dist = node.getSourceDistance() + } + + /** + * Holds if there is a partial data flow path from `node` to `sink`. The + * approximate distance between `node` and the closest sink is `dist` and + * is restricted to be less than or equal to `explorationLimit()`. This + * predicate completely disregards source definitions. + * + * This predicate is intended for data-flow exploration and debugging and may + * perform poorly if the number of sinks is too big and/or the exploration + * limit is set too high without using barriers. + * + * This predicate is disabled (has no results) by default. Override + * `explorationLimit()` with a suitable number to enable this predicate. + * + * To use this in a `path-problem` query, import the module `PartialPathGraph`. + * + * Note that reverse flow has slightly lower precision than the corresponding + * forward flow, as reverse flow disregards type pruning among other features. + */ + final predicate hasPartialFlowRev(PartialPathNode node, PartialPathNode sink, int dist) { + revPartialFlow(node, sink, this) and + dist = node.getSinkDistance() + } +} + +/** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + */ +abstract private class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } +} + +private predicate inBarrier(Node node, Configuration config) { + config.isBarrierIn(node) and + config.isSource(node) +} + +private predicate outBarrier(Node node, Configuration config) { + config.isBarrierOut(node) and + config.isSink(node) +} + +private predicate fullBarrier(Node node, Configuration config) { + config.isBarrier(node) + or + config.isBarrierIn(node) and + not config.isSource(node) + or + config.isBarrierOut(node) and + not config.isSink(node) + or + exists(BarrierGuard g | + config.isBarrierGuard(g) and + node = g.getAGuardedNode() + ) +} + +private class AdditionalFlowStepSource extends Node { + AdditionalFlowStepSource() { any(Configuration c).isAdditionalFlowStep(this, _) } +} + +pragma[noinline] +private predicate isAdditionalFlowStep( + AdditionalFlowStepSource node1, Node node2, DataFlowCallable callable1, Configuration config +) { + config.isAdditionalFlowStep(node1, node2) and + callable1 = node1.getEnclosingCallable() +} + +/** + * Holds if data can flow in one local step from `node1` to `node2`. + */ +private predicate localFlowStep(Node node1, Node node2, Configuration config) { + simpleLocalFlowStep(node1, node2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) +} + +/** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ +private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { + isAdditionalFlowStep(node1, node2, node2.getEnclosingCallable(), config) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) +} + +/** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ +private predicate jumpStep(Node node1, Node node2, Configuration config) { + jumpStep(node1, node2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) +} + +/** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ +private predicate additionalJumpStep(Node node1, Node node2, Configuration config) { + exists(DataFlowCallable callable1 | + isAdditionalFlowStep(node1, node2, callable1, config) and + node2.getEnclosingCallable() != callable1 and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) +} + +/** + * Holds if field flow should be used for the given configuration. + */ +private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } + +private module Stage1 { + class ApApprox = Unit; + + class Ap = Unit; + + class ApOption = Unit; + + class Cc = boolean; + + /* Begin: Stage 1 logic. */ + /** + * Holds if `node` is reachable from a source in the configuration `config`. + * + * The Boolean `cc` records whether the node is reached through an + * argument in a call. + */ + predicate fwdFlow(Node node, Cc cc, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and + cc = false + or + exists(Node mid | + fwdFlow(mid, cc, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, cc, config) and + additionalLocalFlowStep(mid, node, config) + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + jumpStep(mid, node, config) and + cc = false + ) + or + exists(Node mid | + fwdFlow(mid, _, config) and + additionalJumpStep(mid, node, config) and + cc = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + fwdFlow(mid, cc, config) and + store(mid, _, node, _) and + not outBarrier(mid, config) + ) + or + // read + exists(Content c | + fwdFlowRead(c, node, cc, config) and + fwdFlowConsCand(c, config) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + fwdFlow(arg, _, config) and + viableParamArg(_, node, arg) and + cc = true + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, false, config) and + cc = false + or + fwdFlowOutFromArg(call, node, config) and + fwdFlowIsEntered(call, cc, config) + ) + ) + } + + private predicate fwdFlow(Node node, Configuration config) { fwdFlow(node, _, config) } + + pragma[nomagic] + private predicate fwdFlowRead(Content c, Node node, Cc cc, Configuration config) { + exists(Node mid | + fwdFlow(mid, cc, config) and + read(mid, c, node) + ) + } + + /** + * Holds if `c` is the target of a store in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Content c, Configuration config) { + exists(Node mid, Node node, TypedContent tc | + not fullBarrier(node, config) and + useFieldFlow(config) and + fwdFlow(mid, _, config) and + store(mid, tc, node, _) and + c = tc.getContent() + ) + } + + pragma[nomagic] + private predicate fwdFlowReturnPosition(ReturnPosition pos, Cc cc, Configuration config) { + exists(ReturnNodeExt ret | + fwdFlow(ret, cc, config) and + getReturnPosition(ret) = pos + ) + } + + pragma[nomagic] + private predicate fwdFlowOut(DataFlowCall call, Node out, Cc cc, Configuration config) { + exists(ReturnPosition pos | + fwdFlowReturnPosition(pos, cc, config) and + viableReturnPosOut(call, pos, out) + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg(DataFlowCall call, Node out, Configuration config) { + fwdFlowOut(call, out, true, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { + exists(ArgumentNode arg | + fwdFlow(arg, cc, config) and + viableParamArg(call, _, arg) + ) + } + + /** + * Holds if `node` is part of a path from a source to a sink in the + * configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from + * the enclosing callable in order to reach a sink. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, Configuration config) { + revFlow0(node, toReturn, config) and + fwdFlow(node, config) + } + + pragma[nomagic] + private predicate revFlow0(Node node, boolean toReturn, Configuration config) { + fwdFlow(node, config) and + config.isSink(node) and + toReturn = false + or + exists(Node mid | + localFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + revFlow(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + revFlow(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content c | + revFlowStore(c, node, toReturn, config) and + revFlowConsCand(c, config) + ) + or + // read + exists(Node mid, Content c | + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, false, config) and + toReturn = false + or + revFlowInToReturn(call, node, config) and + revFlowIsReturned(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + revFlowOut(pos, config) and + getReturnPosition(node) = pos and + toReturn = true + ) + } + + /** + * Holds if `c` is the target of a read in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Content c, Configuration config) { + exists(Node mid, Node node | + fwdFlow(node, unbind(config)) and + read(node, c, mid) and + fwdFlowConsCand(c, unbind(config)) and + revFlow(mid, _, config) + ) + } + + pragma[nomagic] + private predicate revFlowStore(Content c, Node node, boolean toReturn, Configuration config) { + exists(Node mid, TypedContent tc | + revFlow(mid, toReturn, config) and + fwdFlowConsCand(c, unbind(config)) and + store(node, tc, mid, _) and + c = tc.getContent() + ) + } + + /** + * Holds if `c` is the target of both a read and a store in the flow covered + * by `revFlow`. + */ + private predicate revFlowIsReadAndStored(Content c, Configuration conf) { + revFlowConsCand(c, conf) and + revFlowStore(c, _, _, conf) + } + + pragma[nomagic] + predicate viableReturnPosOutNodeCandFwd1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config + ) { + fwdFlowReturnPosition(pos, _, config) and + viableReturnPosOut(call, pos, out) + } + + pragma[nomagic] + private predicate revFlowOut(ReturnPosition pos, Configuration config) { + exists(DataFlowCall call, Node out | + revFlow(out, _, config) and + viableReturnPosOutNodeCandFwd1(call, pos, out, config) + ) + } + + pragma[nomagic] + predicate viableParamArgNodeCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + ) { + viableParamArg(call, p, arg) and + fwdFlow(arg, config) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config + ) { + exists(ParameterNode p | + revFlow(p, toReturn, config) and + viableParamArgNodeCandFwd1(call, p, arg, config) + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + revFlowIn(call, arg, true, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow`. + */ + pragma[nomagic] + private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + revFlow(out, toReturn, config) and + fwdFlowOutFromArg(call, out, config) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Content c | + revFlowIsReadAndStored(c, config) and + revFlow(node2, unbind(config)) and + store(node1, tc, node2, contentType) and + c = tc.getContent() and + exists(ap1) + ) + } + + pragma[nomagic] + predicate readStepCand(Node n1, Content c, Node n2, Configuration config) { + revFlowIsReadAndStored(c, config) and + revFlow(n2, unbind(config)) and + read(n1, c, n2) + } + + pragma[nomagic] + predicate revFlow(Node node, Configuration config) { revFlow(node, _, config) } + + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow(node, toReturn, config) and exists(returnAp) and exists(ap) + } + + private predicate throughFlowNodeCand(Node node, Configuration config) { + revFlow(node, true, config) and + fwdFlow(node, true, config) and + not inBarrier(node, config) and + not outBarrier(node, config) + } + + /** Holds if flow may return from `callable`. */ + pragma[nomagic] + private predicate returnFlowCallableNodeCand( + DataFlowCallable callable, ReturnKindExt kind, Configuration config + ) { + exists(ReturnNodeExt ret | + throughFlowNodeCand(ret, config) and + callable = ret.getEnclosingCallable() and + kind = ret.getKind() + ) + } + + /** + * Holds if flow may enter through `p` and reach a return node making `p` a + * candidate for the origin of a summary. + */ + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnKindExt kind | + throughFlowNodeCand(p, config) and + returnFlowCallableNodeCand(c, kind, config) and + p.getEnclosingCallable() = c and + exists(ap) and + // we don't expect a parameter to return stored in itself + not exists(int pos | + kind.(ParamUpdateReturnKind).getPosition() = pos and p.isParameterOf(_, pos) + ) + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, config)) and + fields = count(Content f0 | fwdFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | fwdFlow(n, b, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, config)) and + fields = count(Content f0 | revFlowConsCand(f0, config)) and + conscand = -1 and + tuples = count(Node n, boolean b | revFlow(n, b, config)) + } + /* End: Stage 1 logic. */ +} + +bindingset[result, b] +private boolean unbindBool(boolean b) { result != b.booleanNot() } + +pragma[noinline] +private predicate localFlowStepNodeCand1(Node node1, Node node2, Configuration config) { + Stage1::revFlow(node2, config) and + localFlowStep(node1, node2, config) +} + +pragma[noinline] +private predicate additionalLocalFlowStepNodeCand1(Node node1, Node node2, Configuration config) { + Stage1::revFlow(node2, config) and + additionalLocalFlowStep(node1, node2, config) +} + +pragma[nomagic] +private predicate viableReturnPosOutNodeCand1( + DataFlowCall call, ReturnPosition pos, Node out, Configuration config +) { + Stage1::revFlow(out, config) and + Stage1::viableReturnPosOutNodeCandFwd1(call, pos, out, config) +} + +/** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ +pragma[nomagic] +private predicate flowOutOfCallNodeCand1( + DataFlowCall call, ReturnNodeExt ret, Node out, Configuration config +) { + viableReturnPosOutNodeCand1(call, getReturnPosition(ret), out, config) and + Stage1::revFlow(ret, config) and + not outBarrier(ret, config) and + not inBarrier(out, config) +} + +pragma[nomagic] +private predicate viableParamArgNodeCand1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and + Stage1::revFlow(arg, config) +} + +/** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. + */ +pragma[nomagic] +private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config +) { + viableParamArgNodeCand1(call, p, arg, config) and + Stage1::revFlow(p, config) and + not outBarrier(arg, config) and + not inBarrier(p, config) +} + +/** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int branch(Node n1, Configuration conf) { + result = + strictcount(Node n | + flowOutOfCallNodeCand1(_, n1, n, conf) or flowIntoCallNodeCand1(_, n1, n, conf) + ) +} + +/** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int join(Node n2, Configuration conf) { + result = + strictcount(Node n | + flowOutOfCallNodeCand1(_, n, n2, conf) or flowIntoCallNodeCand1(_, n, n2, conf) + ) +} + +/** + * Holds if data can flow out of `call` from `ret` to `out`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ +pragma[nomagic] +private predicate flowOutOfCallNodeCand1( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallNodeCand1(call, ret, out, config) and + exists(int b, int j | + b = branch(ret, config) and + j = join(out, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +/** + * Holds if data can flow into `call` and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ +pragma[nomagic] +private predicate flowIntoCallNodeCand1( + DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, + Configuration config +) { + flowIntoCallNodeCand1(call, arg, p, config) and + exists(int b, int j | + b = branch(arg, config) and + j = join(p, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +private module Stage2 { + module PrevStage = Stage1; + + class ApApprox = PrevStage::Ap; + + class Ap = boolean; + + class ApNil extends Ap { + ApNil() { this = false } + } + + bindingset[result, ap] + private ApApprox getApprox(Ap ap) { any() } + + private ApNil getApNil(Node node) { any() } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = true and exists(tc) and exists(tail) } + + pragma[inline] + private Content getHeadContent(Ap ap) { exists(result) and ap = true } + + class ApOption = BooleanOption; + + ApOption apNone() { result = TBooleanNone() } + + ApOption apSome(Ap ap) { result = TBooleanSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + ( + preservesValue = true and + localFlowStepNodeCand1(node1, node2, config) + or + preservesValue = false and + additionalLocalFlowStepNodeCand1(node1, node2, config) + ) and + exists(ap) and + exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand1/5; + + private predicate flowIntoCall = flowIntoCallNodeCand1/5; + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 2 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil + ) + or + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink in the configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from the + * enclosing callable in order to reach a sink, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ unbindBool(ap2), _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 2 logic. */ +} + +pragma[nomagic] +private predicate flowOutOfCallNodeCand2( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) +} + +pragma[nomagic] +private predicate flowIntoCallNodeCand2( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config +) { + flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and + Stage2::revFlow(node2, config) and + Stage2::revFlow(node1, unbind(config)) +} + +private module LocalFlowBigStep { + /** + * A node where some checking is required, and hence the big-step relation + * is not allowed to step over. + */ + private class FlowCheckNode extends Node { + FlowCheckNode() { + this instanceof CastNode or + clearsContent(this, _) + } + } + + /** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + predicate localFlowEntry(Node node, Configuration config) { + Stage2::revFlow(node, config) and + ( + config.isSource(node) or + jumpStep(_, node, config) or + additionalJumpStep(_, node, config) or + node instanceof ParameterNode or + node instanceof OutNodeExt or + store(_, _, node, _) or + read(_, _, node) or + node instanceof FlowCheckNode + ) + } + + /** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ + private predicate localFlowExit(Node node, Configuration config) { + exists(Node next | Stage2::revFlow(next, config) | + jumpStep(node, next, config) or + additionalJumpStep(node, next, config) or + flowIntoCallNodeCand1(_, node, next, config) or + flowOutOfCallNodeCand1(_, node, next, config) or + store(node, _, next, _) or + read(node, _, next) + ) + or + node instanceof FlowCheckNode + or + config.isSink(node) + } + + pragma[noinline] + private predicate additionalLocalFlowStepNodeCand2(Node node1, Node node2, Configuration config) { + additionalLocalFlowStepNodeCand1(node1, node2, config) and + Stage2::revFlow(node1, _, _, false, config) and + Stage2::revFlow(node2, _, _, false, unbind(config)) + } + + /** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ + pragma[nomagic] + private predicate localFlowStepPlus( + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc + ) { + not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + ( + localFlowEntry(node1, config) and + ( + localFlowStepNodeCand1(node1, node2, config) and + preservesValue = true and + t = getNodeType(node1) + or + additionalLocalFlowStepNodeCand2(node1, node2, config) and + preservesValue = false and + t = getNodeType(node2) + ) and + node1 != node2 and + cc.relevantFor(node1.getEnclosingCallable()) and + not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + Stage2::revFlow(node2, unbind(config)) + or + exists(Node mid | + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and + localFlowStepNodeCand1(mid, node2, config) and + not mid instanceof FlowCheckNode and + Stage2::revFlow(node2, unbind(config)) + ) + or + exists(Node mid | + localFlowStepPlus(node1, mid, _, _, config, cc) and + additionalLocalFlowStepNodeCand2(mid, node2, config) and + not mid instanceof FlowCheckNode and + preservesValue = false and + t = getNodeType(node2) and + Stage2::revFlow(node2, unbind(config)) + ) + ) + } + + /** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ + pragma[nomagic] + predicate localFlowBigStep( + Node node1, Node node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config, + LocalCallContext callContext + ) { + localFlowStepPlus(node1, node2, preservesValue, apf.getType(), config, callContext) and + localFlowExit(node2, config) + } +} + +private import LocalFlowBigStep + +private module Stage3 { + module PrevStage = Stage2; + + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathFront; + + class ApNil = AccessPathFrontNil; + + private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } + + private ApNil getApNil(Node node) { result = TFrontNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result.getHead() = tc and exists(tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathFrontOption; + + ApOption apNone() { result = TAccessPathFrontNone() } + + ApOption apSome(Ap ap) { result = TAccessPathFrontSome(ap) } + + class Cc = boolean; + + class CcCall extends Cc { + CcCall() { this = true } + + /** Holds if this call context may be `call`. */ + predicate matchesCall(DataFlowCall call) { any() } + } + + class CcNoCall extends Cc { + CcNoCall() { this = false } + } + + Cc ccNone() { result = false } + + private class LocalCc = Unit; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { any() } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + any() + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { any() } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap, config, _) and exists(lcc) + } + + private predicate flowOutOfCall = flowOutOfCallNodeCand2/5; + + private predicate flowIntoCall = flowIntoCallNodeCand2/5; + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { + not ap.isClearedAt(node) and + if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + } + + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { + // We need to typecheck stores here, since reverse flow through a getter + // might have a different type here compared to inside the getter. + compatibleTypes(ap.getType(), contentType) + } + + /* Begin: Stage 3 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, unbindBool(getApprox(ap)), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil + ) + or + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, unbindBool(getApprox(ap1)), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, unbindBool(getApprox(ap)), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink in the configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from the + * enclosing callable in order to reach a sink, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 3 logic. */ +} + +/** + * Holds if `argApf` is recorded as the summary context for flow reaching `node` + * and remains relevant for the following pruning stage. + */ +private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) { + exists(AccessPathFront apf | + Stage3::revFlow(node, true, _, apf, config) and + Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config) + ) +} + +/** + * Holds if a length 2 access path approximation with the head `tc` is expected + * to be expensive. + */ +private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) { + exists(int tails, int nodes, int apLimit, int tupleLimit | + tails = strictcount(AccessPathFront apf | Stage3::consCand(tc, apf, config)) and + nodes = + strictcount(Node n | + Stage3::revFlow(n, _, _, any(AccessPathFrontHead apf | apf.getHead() = tc), config) + or + flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.getHead() = tc), config) + ) and + accessPathApproxCostLimits(apLimit, tupleLimit) and + apLimit < tails and + tupleLimit < (tails - 1) * nodes + ) +} + +private newtype TAccessPathApprox = + TNil(DataFlowType t) or + TConsNil(TypedContent tc, DataFlowType t) { + Stage3::consCand(tc, TFrontNil(t), _) and + not expensiveLen2unfolding(tc, _) + } or + TConsCons(TypedContent tc1, TypedContent tc2, int len) { + Stage3::consCand(tc1, TFrontHead(tc2), _) and + len in [2 .. accessPathLimit()] and + not expensiveLen2unfolding(tc1, _) + } or + TCons1(TypedContent tc, int len) { + len in [1 .. accessPathLimit()] and + expensiveLen2unfolding(tc, _) + } + +/** + * Conceptually a list of `TypedContent`s followed by a `DataFlowType`, but only + * the first two elements of the list and its length are tracked. If data flows + * from a source to a given node with a given `AccessPathApprox`, this indicates + * the sequence of dereference operations needed to get from the value in the node + * to the tracked object. The final type indicates the type of the tracked object. + */ +abstract private class AccessPathApprox extends TAccessPathApprox { + abstract string toString(); + + abstract TypedContent getHead(); + + abstract int len(); + + abstract DataFlowType getType(); + + abstract AccessPathFront getFront(); + + /** Gets the access path obtained by popping `head` from this path, if any. */ + abstract AccessPathApprox pop(TypedContent head); +} + +private class AccessPathApproxNil extends AccessPathApprox, TNil { + private DataFlowType t; + + AccessPathApproxNil() { this = TNil(t) } + + override string toString() { result = concat(": " + ppReprType(t)) } + + override TypedContent getHead() { none() } + + override int len() { result = 0 } + + override DataFlowType getType() { result = t } + + override AccessPathFront getFront() { result = TFrontNil(t) } + + override AccessPathApprox pop(TypedContent head) { none() } +} + +abstract private class AccessPathApproxCons extends AccessPathApprox { } + +private class AccessPathApproxConsNil extends AccessPathApproxCons, TConsNil { + private TypedContent tc; + private DataFlowType t; + + AccessPathApproxConsNil() { this = TConsNil(tc, t) } + + override string toString() { + // The `concat` becomes "" if `ppReprType` has no result. + result = "[" + tc.toString() + "]" + concat(" : " + ppReprType(t)) + } + + override TypedContent getHead() { result = tc } + + override int len() { result = 1 } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { head = tc and result = TNil(t) } +} + +private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons { + private TypedContent tc1; + private TypedContent tc2; + private int len; + + AccessPathApproxConsCons() { this = TConsCons(tc1, tc2, len) } + + override string toString() { + if len = 2 + then result = "[" + tc1.toString() + ", " + tc2.toString() + "]" + else result = "[" + tc1.toString() + ", " + tc2.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc1 } + + override int len() { result = len } + + override DataFlowType getType() { result = tc1.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc1) } + + override AccessPathApprox pop(TypedContent head) { + head = tc1 and + ( + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + } +} + +private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 { + private TypedContent tc; + private int len; + + AccessPathApproxCons1() { this = TCons1(tc, len) } + + override string toString() { + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + } + + override TypedContent getHead() { result = tc } + + override int len() { result = len } + + override DataFlowType getType() { result = tc.getContainerType() } + + override AccessPathFront getFront() { result = TFrontHead(tc) } + + override AccessPathApprox pop(TypedContent head) { + head = tc and + ( + exists(TypedContent tc2 | Stage3::consCand(tc, TFrontHead(tc2), _) | + result = TConsCons(tc2, _, len - 1) + or + len = 2 and + result = TConsNil(tc2, _) + or + result = TCons1(tc2, len - 1) + ) + or + exists(DataFlowType t | + len = 1 and + Stage3::consCand(tc, TFrontNil(t), _) and + result = TNil(t) + ) + ) + } +} + +/** Gets the access path obtained by popping `tc` from `ap`, if any. */ +private AccessPathApprox pop(TypedContent tc, AccessPathApprox apa) { result = apa.pop(tc) } + +/** Gets the access path obtained by pushing `tc` onto `ap`. */ +private AccessPathApprox push(TypedContent tc, AccessPathApprox apa) { apa = pop(tc, result) } + +private newtype TAccessPathApproxOption = + TAccessPathApproxNone() or + TAccessPathApproxSome(AccessPathApprox apa) + +private class AccessPathApproxOption extends TAccessPathApproxOption { + string toString() { + this = TAccessPathApproxNone() and result = "" + or + this = TAccessPathApproxSome(any(AccessPathApprox apa | result = apa.toString())) + } +} + +private module Stage4 { + module PrevStage = Stage3; + + class ApApprox = PrevStage::Ap; + + class Ap = AccessPathApprox; + + class ApNil = AccessPathApproxNil; + + private ApApprox getApprox(Ap ap) { result = ap.getFront() } + + private ApNil getApNil(Node node) { result = TNil(getNodeType(node)) } + + bindingset[tc, tail] + private Ap apCons(TypedContent tc, Ap tail) { result = push(tc, tail) } + + pragma[noinline] + private Content getHeadContent(Ap ap) { result = ap.getHead().getContent() } + + class ApOption = AccessPathApproxOption; + + ApOption apNone() { result = TAccessPathApproxNone() } + + ApOption apSome(Ap ap) { result = TAccessPathApproxSome(ap) } + + class Cc = CallContext; + + class CcCall = CallContextCall; + + class CcNoCall = CallContextNoCall; + + Cc ccNone() { result instanceof CallContextAny } + + private class LocalCc = LocalCallContext; + + bindingset[call, c, outercc] + private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { + c = resolveCall(call, outercc) and + if recordDataFlowCallSite(call, c) then result = TSpecificCall(call) else result = TSomeCall() + } + + bindingset[call, c] + private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call) { + if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone() + } + + bindingset[innercc, inner, call] + private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { + resolveReturn(innercc, inner, call) + or + innercc.(CallContextCall).matchesCall(call) + } + + bindingset[node, cc, config] + private LocalCc getLocalCc(Node node, Cc cc, Configuration config) { + localFlowEntry(node, config) and + result = getLocalCallContext(cc, node.getEnclosingCallable()) + } + + private predicate localStep( + Node node1, Node node2, boolean preservesValue, ApNil ap, Configuration config, LocalCc lcc + ) { + localFlowBigStep(node1, node2, preservesValue, ap.getFront(), config, lcc) + } + + pragma[nomagic] + private predicate flowOutOfCall( + DataFlowCall call, ReturnNodeExt node1, Node node2, boolean allowsFieldFlow, + Configuration config + ) { + flowOutOfCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + pragma[nomagic] + private predicate flowIntoCall( + DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, + Configuration config + ) { + flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and + PrevStage::revFlow(node2, _, _, _, config) and + PrevStage::revFlow(node1, _, _, _, unbind(config)) + } + + bindingset[node, ap] + private predicate filter(Node node, Ap ap) { any() } + + // Type checking is not necessary here as it has already been done in stage 3. + bindingset[ap, contentType] + private predicate typecheckStore(Ap ap, DataFlowType contentType) { any() } + + /* Begin: Stage 4 logic. */ + private predicate flowCand(Node node, ApApprox apa, Configuration config) { + PrevStage::revFlow(node, _, _, apa, config) + } + + /** + * Holds if `node` is reachable with access path `ap` from a source in the + * configuration `config`. + * + * The call context `cc` records whether the node is reached through an + * argument in a call, and if so, `argAp` records the access path of that + * argument. + */ + pragma[nomagic] + predicate fwdFlow(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + fwdFlow0(node, cc, argAp, ap, config) and + flowCand(node, getApprox(ap), config) and + filter(node, ap) + } + + pragma[nomagic] + private predicate fwdFlow0(Node node, Cc cc, ApOption argAp, Ap ap, Configuration config) { + flowCand(node, _, config) and + config.isSource(node) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + or + exists(Node mid, Ap ap0, LocalCc localCc | + fwdFlow(mid, cc, argAp, ap0, config) and + localCc = getLocalCc(mid, cc, config) + | + localStep(mid, node, true, _, config, localCc) and + ap = ap0 + or + localStep(mid, node, false, ap, config, localCc) and + ap0 instanceof ApNil + ) + or + exists(Node mid | + fwdFlow(mid, _, _, ap, config) and + flowCand(node, _, unbind(config)) and + jumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(mid, _, _, nil, config) and + flowCand(node, _, unbind(config)) and + additionalJumpStep(mid, node, config) and + cc = ccNone() and + argAp = apNone() and + ap = getApNil(node) + ) + or + // store + exists(TypedContent tc, Ap ap0 | + fwdFlowStore(_, ap0, tc, node, cc, argAp, config) and + ap = apCons(tc, ap0) + ) + or + // read + exists(Ap ap0, Content c | + fwdFlowRead(ap0, c, _, node, cc, argAp, config) and + fwdFlowConsCand(ap0, c, ap, config) + ) + or + // flow into a callable + exists(ApApprox apa | + fwdFlowIn(_, node, _, cc, _, ap, config) and + apa = getApprox(ap) and + if PrevStage::parameterMayFlowThrough(node, _, apa, config) + then argAp = apSome(ap) + else argAp = apNone() + ) + or + // flow out of a callable + exists(DataFlowCall call | + fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) + or + exists(Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) + ) + ) + } + + pragma[nomagic] + private predicate fwdFlowStore( + Node node1, Ap ap1, TypedContent tc, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + exists(DataFlowType contentType | + fwdFlow(node1, cc, argAp, ap1, config) and + PrevStage::storeStepCand(node1, getApprox(ap1), tc, node2, contentType, config) and + typecheckStore(ap1, contentType) + ) + } + + /** + * Holds if forward flow with access path `tail` reaches a store of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate fwdFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(TypedContent tc | + fwdFlowStore(_, tail, tc, _, _, _, config) and + tc.getContent() = c and + cons = apCons(tc, tail) + ) + } + + pragma[nomagic] + private predicate fwdFlowRead( + Ap ap, Content c, Node node1, Node node2, Cc cc, ApOption argAp, Configuration config + ) { + fwdFlow(node1, cc, argAp, ap, config) and + PrevStage::readStepCand(node1, c, node2, config) and + getHeadContent(ap) = c + } + + pragma[nomagic] + private predicate fwdFlowIn( + DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + Configuration config + ) { + exists(ArgumentNode arg, boolean allowsFieldFlow | + fwdFlow(arg, outercc, argAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + innercc = getCallContextCall(call, p.getEnclosingCallable(), outercc) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + /** + * Holds if flow may exit from `call` at `out` with access path `ap`. The + * inner call context is `innercc`, but `ccOut` is just the call context + * based on the return step. In the case of through-flow `ccOut` is discarded + * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. + */ + pragma[nomagic] + private predicate fwdFlowOut( + DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + fwdFlow(ret, innercc, argAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) and + inner = ret.getEnclosingCallable() and + checkCallContextReturn(innercc, inner, call) and + ccOut = getCallContextReturn(inner, call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate fwdFlowOutFromArg( + DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config + ) { + fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + } + + /** + * Holds if an argument to `call` is reached in the flow covered by `fwdFlow` + * and data might flow through the target callable and back out at `call`. + */ + pragma[nomagic] + private predicate fwdFlowIsEntered( + DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config + ) { + exists(ParameterNode p | + fwdFlowIn(call, p, cc, _, argAp, ap, config) and + PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) + ) + } + + pragma[nomagic] + private predicate storeStepFwd( + Node node1, Ap ap1, TypedContent tc, Node node2, Ap ap2, Configuration config + ) { + fwdFlowStore(node1, ap1, tc, node2, _, _, config) and + ap2 = apCons(tc, ap1) and + fwdFlowRead(ap2, tc.getContent(), _, _, _, _, config) + } + + private predicate readStepFwd(Node n1, Ap ap1, Content c, Node n2, Ap ap2, Configuration config) { + fwdFlowRead(ap1, c, n1, n2, _, _, config) and + fwdFlowConsCand(ap1, c, ap2, config) + } + + /** + * Holds if `node` with access path `ap` is part of a path from a source to a + * sink in the configuration `config`. + * + * The Boolean `toReturn` records whether the node must be returned from the + * enclosing callable in order to reach a sink, and if so, `returnAp` records + * the access path of the returned value. + */ + pragma[nomagic] + predicate revFlow(Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config) { + revFlow0(node, toReturn, returnAp, ap, config) and + fwdFlow(node, _, _, ap, config) + } + + pragma[nomagic] + private predicate revFlow0( + Node node, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + fwdFlow(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + or + exists(Node mid | + localStep(node, mid, true, _, config, _) and + revFlow(mid, toReturn, returnAp, ap, config) + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + localStep(node, mid, false, _, config, _) and + revFlow(mid, toReturn, returnAp, nil, config) and + ap instanceof ApNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + revFlow(mid, _, _, ap, config) and + toReturn = false and + returnAp = apNone() + ) + or + exists(Node mid, ApNil nil | + fwdFlow(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + revFlow(mid, _, _, nil, config) and + toReturn = false and + returnAp = apNone() and + ap instanceof ApNil + ) + or + // store + exists(Ap ap0, Content c | + revFlowStore(ap0, c, ap, node, _, _, toReturn, returnAp, config) and + revFlowConsCand(ap0, c, ap, config) + ) + or + // read + exists(Node mid, Ap ap0 | + revFlow(mid, toReturn, returnAp, ap0, config) and + readStepFwd(node, ap, _, mid, ap0, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + revFlowIn(call, node, toReturn, returnAp, ap, config) and + toReturn = false + or + exists(Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + ) + or + // flow out of a callable + revFlowOut(_, node, _, _, ap, config) and + toReturn = true and + if fwdFlow(node, any(CcCall ccc), apSome(_), ap, config) + then returnAp = apSome(ap) + else returnAp = apNone() + } + + pragma[nomagic] + private predicate revFlowStore( + Ap ap0, Content c, Ap ap, Node node, TypedContent tc, Node mid, boolean toReturn, + ApOption returnAp, Configuration config + ) { + revFlow(mid, toReturn, returnAp, ap0, config) and + storeStepFwd(node, ap, tc, mid, ap0, config) and + tc.getContent() = c + } + + /** + * Holds if reverse flow with access path `tail` reaches a read of `c` + * resulting in access path `cons`. + */ + pragma[nomagic] + private predicate revFlowConsCand(Ap cons, Content c, Ap tail, Configuration config) { + exists(Node mid | + revFlow(mid, _, _, tail, config) and + readStepFwd(_, cons, c, mid, tail, config) + ) + } + + pragma[nomagic] + private predicate revFlowOut( + DataFlowCall call, ReturnNodeExt ret, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(Node out, boolean allowsFieldFlow | + revFlow(out, toReturn, returnAp, ap, config) and + flowOutOfCall(call, ret, out, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowIn( + DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, + Configuration config + ) { + exists(ParameterNode p, boolean allowsFieldFlow | + revFlow(p, toReturn, returnAp, ap, config) and + flowIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) + } + + pragma[nomagic] + private predicate revFlowInToReturn( + DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + ) { + revFlowIn(call, arg, true, apSome(returnAp), ap, config) + } + + /** + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. + */ + pragma[nomagic] + private predicate revFlowIsReturned( + DataFlowCall call, boolean toReturn, ApOption returnAp, Ap ap, Configuration config + ) { + exists(ReturnNodeExt ret, CcCall ccc | + revFlowOut(call, ret, toReturn, returnAp, ap, config) and + fwdFlow(ret, ccc, apSome(_), ap, config) and + ccc.matchesCall(call) + ) + } + + pragma[nomagic] + predicate storeStepCand( + Node node1, Ap ap1, TypedContent tc, Node node2, DataFlowType contentType, Configuration config + ) { + exists(Ap ap2, Content c | + store(node1, tc, node2, contentType) and + revFlowStore(ap2, c, ap1, node1, tc, node2, _, _, config) and + revFlowConsCand(ap2, c, ap1, config) + ) + } + + predicate readStepCand(Node node1, Content c, Node node2, Configuration config) { + exists(Ap ap1, Ap ap2 | + revFlow(node2, _, _, ap2, config) and + readStepFwd(node1, ap1, c, node2, ap2, config) and + revFlowStore(ap1, c, /*unbind*/ ap2, _, _, _, _, _, unbind(config)) + ) + } + + predicate revFlow(Node node, Configuration config) { revFlow(node, _, _, _, config) } + + private predicate fwdConsCand(TypedContent tc, Ap ap, Configuration config) { + storeStepFwd(_, ap, tc, _, _, config) + } + + predicate consCand(TypedContent tc, Ap ap, Configuration config) { + storeStepCand(_, ap, tc, _, _, config) + } + + pragma[noinline] + private predicate parameterFlow( + ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ) { + revFlow(p, true, apSome(ap0), ap, config) and + c = p.getEnclosingCallable() + } + + predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | + parameterFlow(p, ap, ap0, c, config) and + c = ret.getEnclosingCallable() and + revFlow(ret, true, apSome(_), ap0, config) and + fwdFlow(ret, any(CcCall ccc), apSome(ap), ap0, config) and + kind = ret.getKind() and + p.isParameterOf(_, pos) and + // we don't expect a parameter to return stored in itself + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) + } + + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { + fwd = true and + nodes = count(Node node | fwdFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | fwdConsCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | fwdConsCand(f0, ap, config)) and + tuples = count(Node n, Cc cc, ApOption argAp, Ap ap | fwdFlow(n, cc, argAp, ap, config)) + or + fwd = false and + nodes = count(Node node | revFlow(node, _, _, _, config)) and + fields = count(TypedContent f0 | consCand(f0, _, config)) and + conscand = count(TypedContent f0, Ap ap | consCand(f0, ap, config)) and + tuples = count(Node n, boolean b, ApOption retAp, Ap ap | revFlow(n, b, retAp, ap, config)) + } + /* End: Stage 4 logic. */ +} + +bindingset[conf, result] +private Configuration unbind(Configuration conf) { result >= conf and result <= conf } + +private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration config) { + exists(DataFlowCallable c, AccessPathApprox apa0 | + Stage4::parameterMayFlowThrough(_, c, apa, _) and + Stage4::revFlow(n, true, _, apa0, config) and + Stage4::fwdFlow(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), apa0, config) and + n.getEnclosingCallable() = c + ) +} + +private newtype TSummaryCtx = + TSummaryCtxNone() or + TSummaryCtxSome(ParameterNode p, AccessPath ap) { + Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) + } + +/** + * A context for generating flow summaries. This represents flow entry through + * a specific parameter with an access path of a specific shape. + * + * Summaries are only created for parameters that may flow through. + */ +abstract private class SummaryCtx extends TSummaryCtx { + abstract string toString(); +} + +/** A summary context from which no flow summary can be generated. */ +private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { + override string toString() { result = "" } +} + +/** A summary context from which a flow summary can be generated. */ +private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { + private ParameterNode p; + private AccessPath ap; + + SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } + + int getParameterPos() { p.isParameterOf(_, result) } + + override string toString() { result = p + ": " + ap } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + p.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** + * Gets the number of length 2 access path approximations that correspond to `apa`. + */ +private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) { + exists(TypedContent tc, int len | + tc = apa.getHead() and + len = apa.len() and + result = + strictcount(AccessPathFront apf | + Stage4::consCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1), + config) + ) + ) +} + +private int countNodesUsingAccessPath(AccessPathApprox apa, Configuration config) { + result = + strictcount(Node n | Stage4::revFlow(n, _, _, apa, config) or nodeMayUseSummary(n, apa, config)) +} + +/** + * Holds if a length 2 access path approximation matching `apa` is expected + * to be expensive. + */ +private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) { + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = count1to2unfold(apa, config) and + nodes = countNodesUsingAccessPath(apa, config) and + accessPathCostLimits(apLimit, tupleLimit) and + apLimit < aps and + tupleLimit < (aps - 1) * nodes + ) +} + +private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) { + exists(TypedContent head | + apa.pop(head) = result and + Stage4::consCand(head, result, config) + ) +} + +/** + * Holds with `unfold = false` if a precise head-tail representation of `apa` is + * expected to be expensive. Holds with `unfold = true` otherwise. + */ +private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) { + exists(int aps, int nodes, int apLimit, int tupleLimit | + aps = countPotentialAps(apa, config) and + nodes = countNodesUsingAccessPath(apa, config) and + accessPathCostLimits(apLimit, tupleLimit) and + if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true + ) +} + +/** + * Gets the number of `AccessPath`s that correspond to `apa`. + */ +private int countAps(AccessPathApprox apa, Configuration config) { + evalUnfold(apa, false, config) and + result = 1 and + (not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config)) + or + evalUnfold(apa, false, config) and + result = count1to2unfold(apa, config) and + not expensiveLen1to2unfolding(apa, config) + or + evalUnfold(apa, true, config) and + result = countPotentialAps(apa, config) +} + +/** + * Gets the number of `AccessPath`s that would correspond to `apa` assuming + * that it is expanded to a precise head-tail representation. + */ +language[monotonicAggregates] +private int countPotentialAps(AccessPathApprox apa, Configuration config) { + apa instanceof AccessPathApproxNil and result = 1 + or + result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config)) +} + +private newtype TAccessPath = + TAccessPathNil(DataFlowType t) or + TAccessPathCons(TypedContent head, AccessPath tail) { + exists(AccessPathApproxCons apa | + not evalUnfold(apa, false, _) and + head = apa.getHead() and + tail.getApprox() = getATail(apa, _) + ) + } or + TAccessPathCons2(TypedContent head1, TypedContent head2, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false, _) and + not expensiveLen1to2unfolding(apa, _) and + apa.len() = len and + head1 = apa.getHead() and + head2 = getATail(apa, _).getHead() + ) + } or + TAccessPathCons1(TypedContent head, int len) { + exists(AccessPathApproxCons apa | + evalUnfold(apa, false, _) and + expensiveLen1to2unfolding(apa, _) and + apa.len() = len and + head = apa.getHead() + ) + } + +private newtype TPathNode = + TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) { + // A PathNode is introduced by a source ... + Stage4::revFlow(node, config) and + config.isSource(node) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = TAccessPathNil(getNodeType(node)) + or + // ... or a step from an existing PathNode to another node. + exists(PathNodeMid mid | + pathStep(mid, node, cc, sc, ap) and + config = mid.getConfiguration() and + Stage4::revFlow(node, _, _, ap.getApprox(), unbind(config)) + ) + } or + TPathNodeSink(Node node, Configuration config) { + config.isSink(node) and + Stage4::revFlow(node, unbind(config)) and + ( + // A sink that is also a source ... + config.isSource(node) + or + // ... or a sink that can be reached from a source + exists(PathNodeMid mid | + pathStep(mid, node, _, _, TAccessPathNil(_)) and + config = unbind(mid.getConfiguration()) + ) + ) + } + +/** + * A list of `TypedContent`s followed by a `DataFlowType`. If data flows from a + * source to a given node with a given `AccessPath`, this indicates the sequence + * of dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ +abstract private class AccessPath extends TAccessPath { + /** Gets the head of this access path, if any. */ + abstract TypedContent getHead(); + + /** Gets the tail of this access path, if any. */ + abstract AccessPath getTail(); + + /** Gets the front of this access path. */ + abstract AccessPathFront getFront(); + + /** Gets the approximation of this access path. */ + abstract AccessPathApprox getApprox(); + + /** Gets the length of this access path. */ + abstract int length(); + + /** Gets a textual representation of this access path. */ + abstract string toString(); + + /** Gets the access path obtained by popping `tc` from this access path, if any. */ + final AccessPath pop(TypedContent tc) { + result = this.getTail() and + tc = this.getHead() + } + + /** Gets the access path obtained by pushing `tc` onto this access path. */ + final AccessPath push(TypedContent tc) { this = result.pop(tc) } +} + +private class AccessPathNil extends AccessPath, TAccessPathNil { + private DataFlowType t; + + AccessPathNil() { this = TAccessPathNil(t) } + + DataFlowType getType() { result = t } + + override TypedContent getHead() { none() } + + override AccessPath getTail() { none() } + + override AccessPathFrontNil getFront() { result = TFrontNil(t) } + + override AccessPathApproxNil getApprox() { result = TNil(t) } + + override int length() { result = 0 } + + override string toString() { result = concat(": " + ppReprType(t)) } +} + +private class AccessPathCons extends AccessPath, TAccessPathCons { + private TypedContent head; + private AccessPath tail; + + AccessPathCons() { this = TAccessPathCons(head, tail) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { result = tail } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + override AccessPathApproxCons getApprox() { + result = TConsNil(head, tail.(AccessPathNil).getType()) + or + result = TConsCons(head, tail.getHead(), this.length()) + or + result = TCons1(head, this.length()) + } + + override int length() { result = 1 + tail.length() } + + private string toStringImpl(boolean needsSuffix) { + exists(DataFlowType t | + tail = TAccessPathNil(t) and + needsSuffix = false and + result = head.toString() + "]" + concat(" : " + ppReprType(t)) + ) + or + result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix) + or + exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) | + result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true + or + result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false + ) + or + exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) | + result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true + or + result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false + ) + } + + override string toString() { + result = "[" + this.toStringImpl(true) + length().toString() + ")]" + or + result = "[" + this.toStringImpl(false) + } +} + +private class AccessPathCons2 extends AccessPath, TAccessPathCons2 { + private TypedContent head1; + private TypedContent head2; + private int len; + + AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) } + + override TypedContent getHead() { result = head1 } + + override AccessPath getTail() { + Stage4::consCand(head1, result.getApprox(), _) and + result.getHead() = head2 and + result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head1) } + + override AccessPathApproxCons getApprox() { + result = TConsCons(head1, head2, len) or + result = TCons1(head1, len) + } + + override int length() { result = len } + + override string toString() { + if len = 2 + then result = "[" + head1.toString() + ", " + head2.toString() + "]" + else + result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]" + } +} + +private class AccessPathCons1 extends AccessPath, TAccessPathCons1 { + private TypedContent head; + private int len; + + AccessPathCons1() { this = TAccessPathCons1(head, len) } + + override TypedContent getHead() { result = head } + + override AccessPath getTail() { + Stage4::consCand(head, result.getApprox(), _) and result.length() = len - 1 + } + + override AccessPathFrontHead getFront() { result = TFrontHead(head) } + + override AccessPathApproxCons getApprox() { result = TCons1(head, len) } + + override int length() { result = len } + + override string toString() { + if len = 1 + then result = "[" + head.toString() + "]" + else result = "[" + head.toString() + ", ... (" + len.toString() + ")]" + } +} + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source are generated. + */ +class PathNode extends TPathNode { + /** Gets a textual representation of this element. */ + string toString() { none() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { none() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } + + /** Gets the underlying `Node`. */ + Node getNode() { none() } + + /** Gets the associated configuration. */ + Configuration getConfiguration() { none() } + + private predicate isHidden() { + nodeIsHidden(this.getNode()) and + not this.isSource() and + not this instanceof PathNodeSink + } + + private PathNode getASuccessorIfHidden() { + this.isHidden() and + result = this.(PathNodeImpl).getASuccessorImpl() + } + + /** Gets a successor of this node, if any. */ + final PathNode getASuccessor() { + result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and + not this.isHidden() and + not result.isHidden() + } + + /** Holds if this node is a source. */ + predicate isSource() { none() } +} + +abstract private class PathNodeImpl extends PathNode { + abstract PathNode getASuccessorImpl(); + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } + + override string toString() { result = this.getNode().toString() + ppAp() } + + override string toStringWithContext() { result = this.getNode().toString() + ppAp() + ppCtx() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** Holds if `n` can reach a sink. */ +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } + +/** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } + +private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) + +/** + * Provides the query predicates needed to include a graph in a path-problem query. + */ +module PathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PathNode a, PathNode b) { pathSucc(a, b) } + + /** Holds if `n` is a node in the graph of data flow path explanations. */ + query predicate nodes(PathNode n, string key, string val) { + reach(n) and key = "semmle.label" and val = n.toString() + } +} + +/** + * An intermediate flow graph node. This is a triple consisting of a `Node`, + * a `CallContext`, and a `Configuration`. + */ +private class PathNodeMid extends PathNodeImpl, TPathNodeMid { + Node node; + CallContext cc; + SummaryCtx sc; + AccessPath ap; + Configuration config; + + PathNodeMid() { this = TPathNodeMid(node, cc, sc, ap, config) } + + override Node getNode() { result = node } + + CallContext getCallContext() { result = cc } + + SummaryCtx getSummaryCtx() { result = sc } + + AccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + private PathNodeMid getSuccMid() { + pathStep(this, result.getNode(), result.getCallContext(), result.getSummaryCtx(), result.getAp()) and + result.getConfiguration() = unbind(this.getConfiguration()) + } + + override PathNodeImpl getASuccessorImpl() { + // an intermediate step to another intermediate node + result = getSuccMid() + or + // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges + exists(PathNodeMid mid, PathNodeSink sink | + mid = getSuccMid() and + mid.getNode() = sink.getNode() and + mid.getAp() instanceof AccessPathNil and + sink.getConfiguration() = unbind(mid.getConfiguration()) and + result = sink + ) + } + + override predicate isSource() { + config.isSource(node) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap instanceof AccessPathNil + } +} + +/** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ +private class PathNodeSink extends PathNodeImpl, TPathNodeSink { + Node node; + Configuration config; + + PathNodeSink() { this = TPathNodeSink(node, config) } + + override Node getNode() { result = node } + + override Configuration getConfiguration() { result = config } + + override PathNode getASuccessorImpl() { none() } + + override predicate isSource() { config.isSource(node) } +} + +/** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ +private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { + exists(AccessPath ap0, Node midnode, Configuration conf, LocalCallContext localCC | + midnode = mid.getNode() and + conf = mid.getConfiguration() and + cc = mid.getCallContext() and + sc = mid.getSummaryCtx() and + localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and + ap0 = mid.getAp() + | + localFlowBigStep(midnode, node, true, _, conf, localCC) and + ap = ap0 + or + localFlowBigStep(midnode, node, false, ap.getFront(), conf, localCC) and + ap0 instanceof AccessPathNil + ) + or + jumpStep(mid.getNode(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + ap = mid.getAp() + or + additionalJumpStep(mid.getNode(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + sc instanceof SummaryCtxNone and + mid.getAp() instanceof AccessPathNil and + ap = TAccessPathNil(getNodeType(node)) + or + exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + exists(TypedContent tc | pathReadStep(mid, node, ap.push(tc), tc, cc)) and + sc = mid.getSummaryCtx() + or + pathIntoCallable(mid, node, _, cc, sc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, cc) and ap = mid.getAp() and sc instanceof SummaryCtxNone + or + pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() +} + +pragma[nomagic] +private predicate pathReadStep( + PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc +) { + ap0 = mid.getAp() and + tc = ap0.getHead() and + Stage4::readStepCand(mid.getNode(), tc.getContent(), node, mid.getConfiguration()) and + cc = mid.getCallContext() +} + +pragma[nomagic] +private predicate pathStoreStep( + PathNodeMid mid, Node node, AccessPath ap0, TypedContent tc, CallContext cc +) { + ap0 = mid.getAp() and + Stage4::storeStepCand(mid.getNode(), _, tc, node, _, mid.getConfiguration()) and + cc = mid.getCallContext() +} + +private predicate pathOutOfCallable0( + PathNodeMid mid, ReturnPosition pos, CallContext innercc, AccessPathApprox apa, + Configuration config +) { + pos = getReturnPosition(mid.getNode()) and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and + apa = mid.getAp().getApprox() and + config = mid.getConfiguration() +} + +pragma[nomagic] +private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, AccessPathApprox apa, + Configuration config +) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + pathOutOfCallable0(mid, pos, innercc, apa, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) +} + +pragma[noinline] +private Node getAnOutNodeFlow( + ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config +) { + result = kind.getAnOutNode(call) and + Stage4::revFlow(result, _, _, apa, config) +} + +/** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ +pragma[noinline] +private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) { + exists(ReturnKindExt kind, DataFlowCall call, AccessPathApprox apa, Configuration config | + pathOutOfCallable1(mid, call, kind, cc, apa, config) and + out = getAnOutNodeFlow(kind, call, apa, config) + ) +} + +/** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ +pragma[noinline] +private predicate pathIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa +) { + exists(ArgumentNode arg | + arg = mid.getNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + apa = ap.getApprox() + ) +} + +pragma[noinline] +private predicate parameterCand( + DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config +) { + exists(ParameterNode p | + Stage4::revFlow(p, _, _, apa, config) and + p.isParameterOf(callable, i) + ) +} + +pragma[nomagic] +private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + AccessPath ap +) { + exists(AccessPathApprox apa | + pathIntoArg(mid, i, outercc, call, ap, apa) and + callable = resolveCall(call, outercc) and + parameterCand(callable, any(int j | j <= i and j >= i), apa, mid.getConfiguration()) + ) +} + +/** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ +private predicate pathIntoCallable( + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + DataFlowCall call +) { + exists(int i, DataFlowCallable callable, AccessPath ap | + pathIntoCallable0(mid, callable, i, outercc, call, ap) and + p.isParameterOf(callable, i) and + ( + sc = TSummaryCtxSome(p, ap) + or + not exists(TSummaryCtxSome(p, ap)) and + sc = TSummaryCtxNone() + ) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) +} + +/** Holds if data may flow from a parameter given by `sc` to a return of kind `kind`. */ +pragma[nomagic] +private predicate paramFlowsThrough( + ReturnKindExt kind, CallContextCall cc, SummaryCtxSome sc, AccessPath ap, AccessPathApprox apa, + Configuration config +) { + exists(PathNodeMid mid, ReturnNodeExt ret, int pos | + mid.getNode() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + sc = mid.getSummaryCtx() and + config = mid.getConfiguration() and + ap = mid.getAp() and + apa = ap.getApprox() and + pos = sc.getParameterPos() and + not kind.(ParamUpdateReturnKind).getPosition() = pos + ) +} + +pragma[nomagic] +private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPath ap, + AccessPathApprox apa +) { + exists(CallContext innercc, SummaryCtx sc | + pathIntoCallable(mid, _, cc, innercc, sc, call) and + paramFlowsThrough(kind, innercc, sc, ap, apa, unbind(mid.getConfiguration())) + ) +} + +/** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ +pragma[noinline] +private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPath ap) { + exists(DataFlowCall call, ReturnKindExt kind, AccessPathApprox apa | + pathThroughCallable0(call, mid, kind, cc, ap, apa) and + out = getAnOutNodeFlow(kind, call, apa, unbind(mid.getConfiguration())) + ) +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +private predicate flowsTo( + PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration +) { + flowsource.isSource() and + flowsource.getConfiguration() = configuration and + flowsource.getNode() = source and + (flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and + flowsink.getNode() = sink +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +predicate flowsTo(Node source, Node sink, Configuration configuration) { + flowsTo(_, _, source, sink, configuration) +} + +private predicate finalStats(boolean fwd, int nodes, int fields, int conscand, int tuples) { + fwd = true and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = n0)) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0)) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap)) and + tuples = count(PathNode pn) + or + fwd = false and + nodes = count(Node n0 | exists(PathNode pn | pn.getNode() = n0 and reach(pn))) and + fields = count(TypedContent f0 | exists(PathNodeMid pn | pn.getAp().getHead() = f0 and reach(pn))) and + conscand = count(AccessPath ap | exists(PathNodeMid pn | pn.getAp() = ap and reach(pn))) and + tuples = count(PathNode pn | reach(pn)) +} + +/** + * INTERNAL: Only for debugging. + * + * Calculates per-stage metrics for data flow. + */ +predicate stageStats( + int n, string stage, int nodes, int fields, int conscand, int tuples, Configuration config +) { + stage = "1 Fwd" and n = 10 and Stage1::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "1 Rev" and n = 15 and Stage1::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "2 Fwd" and n = 20 and Stage2::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "2 Rev" and n = 25 and Stage2::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "3 Fwd" and n = 30 and Stage3::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "3 Rev" and n = 35 and Stage3::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "4 Fwd" and n = 40 and Stage4::stats(true, nodes, fields, conscand, tuples, config) + or + stage = "4 Rev" and n = 45 and Stage4::stats(false, nodes, fields, conscand, tuples, config) + or + stage = "5 Fwd" and n = 50 and finalStats(true, nodes, fields, conscand, tuples) + or + stage = "5 Rev" and n = 55 and finalStats(false, nodes, fields, conscand, tuples) +} + +private module FlowExploration { + private predicate callableStep(DataFlowCallable c1, DataFlowCallable c2, Configuration config) { + exists(Node node1, Node node2 | + jumpStep(node1, node2, config) + or + additionalJumpStep(node1, node2, config) + or + // flow into callable + viableParamArg(_, node2, node1) + or + // flow out of a callable + viableReturnPosOut(_, getReturnPosition(node1), node2) + | + c1 = node1.getEnclosingCallable() and + c2 = node2.getEnclosingCallable() and + c1 != c2 + ) + } + + private predicate interestingCallableSrc(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSource(n) and c = n.getEnclosingCallable()) + or + exists(DataFlowCallable mid | + interestingCallableSrc(mid, config) and callableStep(mid, c, config) + ) + } + + private predicate interestingCallableSink(DataFlowCallable c, Configuration config) { + exists(Node n | config.isSink(n) and c = n.getEnclosingCallable()) + or + exists(DataFlowCallable mid | + interestingCallableSink(mid, config) and callableStep(c, mid, config) + ) + } + + private newtype TCallableExt = + TCallable(DataFlowCallable c, Configuration config) { + interestingCallableSrc(c, config) or + interestingCallableSink(c, config) + } or + TCallableSrc() or + TCallableSink() + + private predicate callableExtSrc(TCallableSrc src) { any() } + + private predicate callableExtSink(TCallableSink sink) { any() } + + private predicate callableExtStepFwd(TCallableExt ce1, TCallableExt ce2) { + exists(DataFlowCallable c1, DataFlowCallable c2, Configuration config | + callableStep(c1, c2, config) and + ce1 = TCallable(c1, config) and + ce2 = TCallable(c2, unbind(config)) + ) + or + exists(Node n, Configuration config | + ce1 = TCallableSrc() and + config.isSource(n) and + ce2 = TCallable(n.getEnclosingCallable(), config) + ) + or + exists(Node n, Configuration config | + ce2 = TCallableSink() and + config.isSink(n) and + ce1 = TCallable(n.getEnclosingCallable(), config) + ) + } + + private predicate callableExtStepRev(TCallableExt ce1, TCallableExt ce2) { + callableExtStepFwd(ce2, ce1) + } + + private int distSrcExt(TCallableExt c) = + shortestDistances(callableExtSrc/1, callableExtStepFwd/2)(_, c, result) + + private int distSinkExt(TCallableExt c) = + shortestDistances(callableExtSink/1, callableExtStepRev/2)(_, c, result) + + private int distSrc(DataFlowCallable c, Configuration config) { + result = distSrcExt(TCallable(c, config)) - 1 + } + + private int distSink(DataFlowCallable c, Configuration config) { + result = distSinkExt(TCallable(c, config)) - 1 + } + + private newtype TPartialAccessPath = + TPartialNil(DataFlowType t) or + TPartialCons(TypedContent tc, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `TypedContent`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ + private class PartialAccessPath extends TPartialAccessPath { + abstract string toString(); + + TypedContent getHead() { this = TPartialCons(result, _) } + + int len() { + this = TPartialNil(_) and result = 0 + or + this = TPartialCons(_, result) + } + + DataFlowType getType() { + this = TPartialNil(result) + or + exists(TypedContent head | this = TPartialCons(head, _) | result = head.getContainerType()) + } + } + + private class PartialAccessPathNil extends PartialAccessPath, TPartialNil { + override string toString() { + exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t))) + } + } + + private class PartialAccessPathCons extends PartialAccessPath, TPartialCons { + override string toString() { + exists(TypedContent tc, int len | this = TPartialCons(tc, len) | + if len = 1 + then result = "[" + tc.toString() + "]" + else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TRevPartialAccessPath = + TRevPartialNil() or + TRevPartialCons(Content c, int len) { len in [1 .. accessPathLimit()] } + + /** + * Conceptually a list of `Content`s, but only the first + * element of the list and its length are tracked. + */ + private class RevPartialAccessPath extends TRevPartialAccessPath { + abstract string toString(); + + Content getHead() { this = TRevPartialCons(result, _) } + + int len() { + this = TRevPartialNil() and result = 0 + or + this = TRevPartialCons(_, result) + } + } + + private class RevPartialAccessPathNil extends RevPartialAccessPath, TRevPartialNil { + override string toString() { result = "" } + } + + private class RevPartialAccessPathCons extends RevPartialAccessPath, TRevPartialCons { + override string toString() { + exists(Content c, int len | this = TRevPartialCons(c, len) | + if len = 1 + then result = "[" + c.toString() + "]" + else result = "[" + c.toString() + ", ... (" + len.toString() + ")]" + ) + } + } + + private newtype TSummaryCtx1 = + TSummaryCtx1None() or + TSummaryCtx1Param(ParameterNode p) + + private newtype TSummaryCtx2 = + TSummaryCtx2None() or + TSummaryCtx2Some(PartialAccessPath ap) + + private newtype TRevSummaryCtx1 = + TRevSummaryCtx1None() or + TRevSummaryCtx1Some(ReturnPosition pos) + + private newtype TRevSummaryCtx2 = + TRevSummaryCtx2None() or + TRevSummaryCtx2Some(RevPartialAccessPath ap) + + private newtype TPartialPathNode = + TPartialPathNodeFwd( + Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, + Configuration config + ) { + config.isSource(node) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap = TPartialNil(getNodeType(node)) and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + partialPathNodeMk0(node, cc, sc1, sc2, ap, config) and + distSrc(node.getEnclosingCallable(), config) <= config.explorationLimit() + } or + TPartialPathNodeRev( + Node node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, RevPartialAccessPath ap, + Configuration config + ) { + config.isSink(node) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = TRevPartialNil() and + not fullBarrier(node, config) and + exists(config.explorationLimit()) + or + exists(PartialPathNodeRev mid | + revPartialPathStep(mid, node, sc1, sc2, ap, config) and + not clearsContent(node, ap.getHead()) and + not fullBarrier(node, config) and + distSink(node.getEnclosingCallable(), config) <= config.explorationLimit() + ) + } + + pragma[nomagic] + private predicate partialPathNodeMk0( + Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, + Configuration config + ) { + exists(PartialPathNodeFwd mid | + partialPathStep(mid, node, cc, sc1, sc2, ap, config) and + not fullBarrier(node, config) and + not clearsContent(node, ap.getHead().getContent()) and + if node instanceof CastingNode + then compatibleTypes(getNodeType(node), ap.getType()) + else any() + ) + } + + /** + * A `Node` augmented with a call context, an access path, and a configuration. + */ + class PartialPathNode extends TPartialPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = this.getNode().toString() + this.ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = this.getNode().toString() + this.ppAp() + this.ppCtx() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + Node getNode() { none() } + + /** Gets the associated configuration. */ + Configuration getConfiguration() { none() } + + /** Gets a successor of this node, if any. */ + PartialPathNode getASuccessor() { none() } + + /** + * Gets the approximate distance to the nearest source measured in number + * of interprocedural steps. + */ + int getSourceDistance() { + result = distSrc(this.getNode().getEnclosingCallable(), this.getConfiguration()) + } + + /** + * Gets the approximate distance to the nearest sink measured in number + * of interprocedural steps. + */ + int getSinkDistance() { + result = distSink(this.getNode().getEnclosingCallable(), this.getConfiguration()) + } + + private string ppAp() { + exists(string s | + s = this.(PartialPathNodeFwd).getAp().toString() or + s = this.(PartialPathNodeRev).getAp().toString() + | + if s = "" then result = "" else result = " " + s + ) + } + + private string ppCtx() { + result = " <" + this.(PartialPathNodeFwd).getCallContext().toString() + ">" + } + + /** Holds if this is a source in a forward-flow path. */ + predicate isFwdSource() { this.(PartialPathNodeFwd).isSource() } + + /** Holds if this is a sink in a reverse-flow path. */ + predicate isRevSink() { this.(PartialPathNodeRev).isSink() } + } + + /** + * Provides the query predicates needed to include a graph in a path-problem query. + */ + module PartialPathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PartialPathNode a, PartialPathNode b) { a.getASuccessor() = b } + } + + private class PartialPathNodeFwd extends PartialPathNode, TPartialPathNodeFwd { + Node node; + CallContext cc; + TSummaryCtx1 sc1; + TSummaryCtx2 sc2; + PartialAccessPath ap; + Configuration config; + + PartialPathNodeFwd() { this = TPartialPathNodeFwd(node, cc, sc1, sc2, ap, config) } + + override Node getNode() { result = node } + + CallContext getCallContext() { result = cc } + + TSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TSummaryCtx2 getSummaryCtx2() { result = sc2 } + + PartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + override PartialPathNodeFwd getASuccessor() { + partialPathStep(this, result.getNode(), result.getCallContext(), result.getSummaryCtx1(), + result.getSummaryCtx2(), result.getAp(), result.getConfiguration()) + } + + predicate isSource() { + config.isSource(node) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap instanceof TPartialNil + } + } + + private class PartialPathNodeRev extends PartialPathNode, TPartialPathNodeRev { + Node node; + TRevSummaryCtx1 sc1; + TRevSummaryCtx2 sc2; + RevPartialAccessPath ap; + Configuration config; + + PartialPathNodeRev() { this = TPartialPathNodeRev(node, sc1, sc2, ap, config) } + + override Node getNode() { result = node } + + TRevSummaryCtx1 getSummaryCtx1() { result = sc1 } + + TRevSummaryCtx2 getSummaryCtx2() { result = sc2 } + + RevPartialAccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + override PartialPathNodeRev getASuccessor() { + revPartialPathStep(result, this.getNode(), this.getSummaryCtx1(), this.getSummaryCtx2(), + this.getAp(), this.getConfiguration()) + } + + predicate isSink() { + config.isSink(node) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = TRevPartialNil() + } + } + + private predicate partialPathStep( + PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + PartialAccessPath ap, Configuration config + ) { + not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + ( + localFlowStep(mid.getNode(), node, config) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(mid.getNode(), node, config) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(getNodeType(node)) and + config = mid.getConfiguration() + ) + or + jumpStep(mid.getNode(), node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(mid.getNode(), node, config) and + cc instanceof CallContextAny and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() and + mid.getAp() instanceof PartialAccessPathNil and + ap = TPartialNil(getNodeType(node)) and + config = mid.getConfiguration() + or + partialPathStoreStep(mid, _, _, node, ap) and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() + or + exists(PartialAccessPath ap0, TypedContent tc | + partialPathReadStep(mid, ap0, tc, node, cc, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + apConsFwd(ap, tc, ap0, config) and + compatibleTypes(ap.getType(), getNodeType(node)) + ) + or + partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) + or + partialPathOutOfCallable(mid, node, cc, ap, config) and + sc1 = TSummaryCtx1None() and + sc2 = TSummaryCtx2None() + or + partialPathThroughCallable(mid, node, cc, ap, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() + } + + bindingset[result, i] + private int unbindInt(int i) { i <= result and i >= result } + + pragma[inline] + private predicate partialPathStoreStep( + PartialPathNodeFwd mid, PartialAccessPath ap1, TypedContent tc, Node node, PartialAccessPath ap2 + ) { + exists(Node midNode, DataFlowType contentType | + midNode = mid.getNode() and + ap1 = mid.getAp() and + store(midNode, tc, node, contentType) and + ap2.getHead() = tc and + ap2.len() = unbindInt(ap1.len() + 1) and + compatibleTypes(ap1.getType(), contentType) + ) + } + + pragma[nomagic] + private predicate apConsFwd( + PartialAccessPath ap1, TypedContent tc, PartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodeFwd mid | + partialPathStoreStep(mid, ap1, tc, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathReadStep( + PartialPathNodeFwd mid, PartialAccessPath ap, TypedContent tc, Node node, CallContext cc, + Configuration config + ) { + exists(Node midNode | + midNode = mid.getNode() and + ap = mid.getAp() and + read(midNode, tc.getContent(), node) and + ap.getHead() = tc and + config = mid.getConfiguration() and + cc = mid.getCallContext() + ) + } + + private predicate partialPathOutOfCallable0( + PartialPathNodeFwd mid, ReturnPosition pos, CallContext innercc, PartialAccessPath ap, + Configuration config + ) { + pos = getReturnPosition(mid.getNode()) and + innercc = mid.getCallContext() and + innercc instanceof CallContextNoCall and + ap = mid.getAp() and + config = mid.getConfiguration() + } + + pragma[nomagic] + private predicate partialPathOutOfCallable1( + PartialPathNodeFwd mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + partialPathOutOfCallable0(mid, pos, innercc, ap, config) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) + } + + private predicate partialPathOutOfCallable( + PartialPathNodeFwd mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(ReturnKindExt kind, DataFlowCall call | + partialPathOutOfCallable1(mid, call, kind, cc, ap, config) + | + out = kind.getAnOutNode(call) + ) + } + + pragma[noinline] + private predicate partialPathIntoArg( + PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, + Configuration config + ) { + exists(ArgumentNode arg | + arg = mid.getNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate partialPathIntoCallable0( + PartialPathNodeFwd mid, DataFlowCallable callable, int i, CallContext outercc, + DataFlowCall call, PartialAccessPath ap, Configuration config + ) { + partialPathIntoArg(mid, i, outercc, call, ap, config) and + callable = resolveCall(call, outercc) + } + + private predicate partialPathIntoCallable( + PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, + Configuration config + ) { + exists(int i, DataFlowCallable callable | + partialPathIntoCallable0(mid, callable, i, outercc, call, ap, config) and + p.isParameterOf(callable, i) and + sc1 = TSummaryCtx1Param(p) and + sc2 = TSummaryCtx2Some(ap) + | + if recordDataFlowCallSite(call, callable) + then innercc = TSpecificCall(call) + else innercc = TSomeCall() + ) + } + + pragma[nomagic] + private predicate paramFlowsThroughInPartialPath( + ReturnKindExt kind, CallContextCall cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, + PartialAccessPath ap, Configuration config + ) { + exists(PartialPathNodeFwd mid, ReturnNodeExt ret | + mid.getNode() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() and + ap = mid.getAp() + ) + } + + pragma[noinline] + private predicate partialPathThroughCallable0( + DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, + PartialAccessPath ap, Configuration config + ) { + exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and + paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) + ) + } + + private predicate partialPathThroughCallable( + PartialPathNodeFwd mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, ReturnKindExt kind | + partialPathThroughCallable0(call, mid, kind, cc, ap, config) and + out = kind.getAnOutNode(call) + ) + } + + private predicate revPartialPathStep( + PartialPathNodeRev mid, Node node, TRevSummaryCtx1 sc1, TRevSummaryCtx2 sc2, + RevPartialAccessPath ap, Configuration config + ) { + localFlowStep(node, mid.getNode(), config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalLocalFlowStep(node, mid.getNode(), config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() and + config = mid.getConfiguration() + or + jumpStep(node, mid.getNode(), config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + or + additionalJumpStep(node, mid.getNode(), config) and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + mid.getAp() instanceof RevPartialAccessPathNil and + ap = TRevPartialNil() and + config = mid.getConfiguration() + or + revPartialPathReadStep(mid, _, _, node, ap) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + config = mid.getConfiguration() + or + exists(RevPartialAccessPath ap0, Content c | + revPartialPathStoreStep(mid, ap0, c, node, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + apConsRev(ap, c, ap0, config) + ) + or + exists(ParameterNode p | + mid.getNode() = p and + viableParamArg(_, p, node) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + sc1 = TRevSummaryCtx1None() and + sc2 = TRevSummaryCtx2None() and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + or + exists(ReturnPosition pos | + revPartialPathIntoReturn(mid, pos, sc1, sc2, _, ap, config) and + pos = getReturnPosition(node) + ) + or + revPartialPathThroughCallable(mid, node, ap, config) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() + } + + pragma[inline] + private predicate revPartialPathReadStep( + PartialPathNodeRev mid, RevPartialAccessPath ap1, Content c, Node node, RevPartialAccessPath ap2 + ) { + exists(Node midNode | + midNode = mid.getNode() and + ap1 = mid.getAp() and + read(node, c, midNode) and + ap2.getHead() = c and + ap2.len() = unbindInt(ap1.len() + 1) + ) + } + + pragma[nomagic] + private predicate apConsRev( + RevPartialAccessPath ap1, Content c, RevPartialAccessPath ap2, Configuration config + ) { + exists(PartialPathNodeRev mid | + revPartialPathReadStep(mid, ap1, c, _, ap2) and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathStoreStep( + PartialPathNodeRev mid, RevPartialAccessPath ap, Content c, Node node, Configuration config + ) { + exists(Node midNode, TypedContent tc | + midNode = mid.getNode() and + ap = mid.getAp() and + store(node, tc, midNode, _) and + ap.getHead() = c and + config = mid.getConfiguration() and + tc.getContent() = c + ) + } + + pragma[nomagic] + private predicate revPartialPathIntoReturn( + PartialPathNodeRev mid, ReturnPosition pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, + DataFlowCall call, RevPartialAccessPath ap, Configuration config + ) { + exists(Node out | + mid.getNode() = out and + viableReturnPosOut(call, pos, out) and + sc1 = TRevSummaryCtx1Some(pos) and + sc2 = TRevSummaryCtx2Some(ap) and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathFlowsThrough( + int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, + Configuration config + ) { + exists(PartialPathNodeRev mid, ParameterNode p | + mid.getNode() = p and + p.isParameterOf(_, pos) and + sc1 = mid.getSummaryCtx1() and + sc2 = mid.getSummaryCtx2() and + ap = mid.getAp() and + config = mid.getConfiguration() + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable0( + DataFlowCall call, PartialPathNodeRev mid, int pos, RevPartialAccessPath ap, + Configuration config + ) { + exists(TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2 | + revPartialPathIntoReturn(mid, _, sc1, sc2, call, _, config) and + revPartialPathFlowsThrough(pos, sc1, sc2, ap, config) + ) + } + + pragma[nomagic] + private predicate revPartialPathThroughCallable( + PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + ) { + exists(DataFlowCall call, int pos | + revPartialPathThroughCallable0(call, mid, pos, ap, config) and + node.argumentOf(call, pos) + ) + } +} + +import FlowExploration + +private predicate partialFlow( + PartialPathNode source, PartialPathNode node, Configuration configuration +) { + source.getConfiguration() = configuration and + source.isFwdSource() and + node = source.getASuccessor+() +} + +private predicate revPartialFlow( + PartialPathNode node, PartialPathNode sink, Configuration configuration +) { + sink.getConfiguration() = configuration and + sink.isRevSink() and + node.getASuccessor+() = sink +} From 299f3717158ca90bfea8dc0ea20637f54a1bb679 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 19 Feb 2021 16:01:31 +0100 Subject: [PATCH 106/757] C++: Accept more test changes. --- .../library-tests/syntax-zoo/dataflow-ir-consistency.expected | 4 ---- .../Security/CWE/CWE-134/semmle/argv/argvLocal.expected | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected index 6aeadb2f174..511edc7e45a 100644 --- a/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected +++ b/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected @@ -1492,12 +1492,8 @@ postWithInFlow | cpp11.cpp:65:19:65:45 | Store | PostUpdateNode should not be the target of local flow. | | cpp11.cpp:82:17:82:55 | Chi | PostUpdateNode should not be the target of local flow. | | cpp11.cpp:82:17:82:55 | Chi | PostUpdateNode should not be the target of local flow. | -| cpp11.cpp:82:45:82:48 | Chi | PostUpdateNode should not be the target of local flow. | | defdestructordeleteexpr.cpp:4:9:4:15 | Chi | PostUpdateNode should not be the target of local flow. | | deleteexpr.cpp:7:9:7:15 | Chi | PostUpdateNode should not be the target of local flow. | -| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. | -| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. | -| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. | | ir.cpp:177:5:177:12 | Chi | PostUpdateNode should not be the target of local flow. | | ir.cpp:178:5:178:12 | Chi | PostUpdateNode should not be the target of local flow. | | ir.cpp:183:5:183:12 | Chi | PostUpdateNode should not be the target of local flow. | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected index 9e73ca54e98..a636bfe6de8 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected @@ -154,9 +154,8 @@ edges | argvLocal.c:168:18:168:21 | argv | argvLocal.c:170:24:170:26 | i10 | nodes | argvLocal.c:9:25:9:31 | *correct | semmle.label | *correct | +| argvLocal.c:9:25:9:31 | *correct | semmle.label | *correct | | argvLocal.c:9:25:9:31 | correct | semmle.label | correct | -| argvLocal.c:10:9:10:15 | Chi | semmle.label | Chi | -| argvLocal.c:10:9:10:15 | Chi | semmle.label | Chi | | argvLocal.c:95:9:95:12 | argv | semmle.label | argv | | argvLocal.c:95:9:95:12 | argv | semmle.label | argv | | argvLocal.c:95:9:95:15 | (const char *)... | semmle.label | (const char *)... | From 5264d24f34f3da8089ed139da9648166dc71b5bc Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Mon, 15 Feb 2021 11:11:53 +0000 Subject: [PATCH 107/757] JS: Model vue-router --- .../change-notes/2021-02-16-vue-router.md | 3 + .../src/semmle/javascript/frameworks/Vue.qll | 90 ++++++++++++++++++- .../frameworks/Vue/compont-with-route.vue | 51 +++++++++++ .../library-tests/frameworks/Vue/router.js | 37 ++++++++ .../frameworks/Vue/tests.expected | 37 ++++++++ .../library-tests/frameworks/Vue/tests.ql | 2 + 6 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 javascript/change-notes/2021-02-16-vue-router.md create mode 100644 javascript/ql/test/library-tests/frameworks/Vue/compont-with-route.vue create mode 100644 javascript/ql/test/library-tests/frameworks/Vue/router.js diff --git a/javascript/change-notes/2021-02-16-vue-router.md b/javascript/change-notes/2021-02-16-vue-router.md new file mode 100644 index 00000000000..56556dd2dd9 --- /dev/null +++ b/javascript/change-notes/2021-02-16-vue-router.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Support for Vue has improved. Taint sources from [vue-router](https://npmjs.com/package/vue-router) + route parameters are now recognized. diff --git a/javascript/ql/src/semmle/javascript/frameworks/Vue.qll b/javascript/ql/src/semmle/javascript/frameworks/Vue.qll index 3424c24fee1..b4316cd61fb 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Vue.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Vue.qll @@ -35,7 +35,8 @@ module Vue { result = [ "beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated", - "deactivated", "beforeDestroy", "destroyed", "errorCaptured" + "deactivated", "beforeDestroy", "destroyed", "errorCaptured", "beforeRouteEnter", + "beforeRouteUpdate", "beforeRouteLeave" ] } @@ -210,6 +211,24 @@ module Vue { */ DataFlow::Node getComputed() { result = getOption("computed") } + /** + * Gets the node for the `watch` option of this instance. + */ + DataFlow::Node getWatch() { result = getOption("watch") } + + /** + * Gets the function responding to changes to the given `propName`. + */ + DataFlow::FunctionNode getWatchHandler(string propName) { + exists(DataFlow::SourceNode watcher | + watcher = getWatch().getALocalSource().getAPropertySource(propName) + | + result = watcher + or + result = watcher.getAPropertySource("handler") + ) + } + /** * Gets a node for a member of the `methods` option of this instance. */ @@ -269,7 +288,7 @@ module Vue { * Gets the node for the life cycle hook of the `hookName` option of this instance. */ pragma[noinline] - private DataFlow::Node getALifecycleHook(string hookName) { + DataFlow::Node getALifecycleHook(string hookName) { hookName = lifecycleHookName() and ( result = getOption(hookName) @@ -287,6 +306,10 @@ module Vue { result = getAnAccessor(_) or result = getALifecycleHook(_) + or + result = getOption(_).(DataFlow::FunctionNode) + or + result = getOption(_).getALocalSource().getAPropertySource().(DataFlow::FunctionNode) } /** @@ -552,4 +575,67 @@ module Vue { HTML::Element getElement() { result = elem } } } + + /** An API node referring to a `RouteConfig` being passed to `vue-router`. */ + private API::Node routeConfig() { + result = API::moduleImport("vue-router").getParameter(0).getMember("routes").getAMember() + or + result = routeConfig().getMember("children").getAMember() + } + + /** Gets a data flow node that refers to a `Route` object from `vue-router`. */ + private DataFlow::SourceNode routeObject(DataFlow::TypeTracker t) { + t.start() and + ( + exists(API::Node router | router = API::moduleImport("vue-router") | + result = router.getInstance().getMember("currentRoute").getAnImmediateUse() + or + result = + router + .getInstance() + .getMember(["beforeEach", "beforeResolve", "afterEach"]) + .getParameter(0) + .getParameter([0, 1]) + .getAnImmediateUse() + or + result = + router + .getParameter(0) + .getMember("scrollBehavior") + .getParameter([0, 1]) + .getAnImmediateUse() + ) + or + result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).getAnImmediateUse() + or + exists(Instance i | + result = i.getABoundFunction().getAFunctionValue().getReceiver().getAPropertyRead("$route") + or + result = + i.getALifecycleHook(["beforeRouteEnter", "beforeRouteUpdate", "beforeRouteLeave"]) + .getAFunctionValue() + .getParameter([0, 1]) + or + result = i.getWatchHandler("$route").getParameter([0, 1]) + ) + ) + or + exists(DataFlow::TypeTracker t2 | result = routeObject(t2).track(t2, t)) + } + + /** Gets a data flow node that refers to a `Route` object from `vue-router`. */ + DataFlow::SourceNode routeObject() { result = routeObject(DataFlow::TypeTracker::end()) } + + private class VueRouterFlowSource extends RemoteFlowSource { + VueRouterFlowSource() { + this = routeObject().getAPropertyRead(["params", "query", "hash", "path", "fullPath"]) + or + exists(Instance i, string prop | + this = i.getWatchHandler(prop).getParameter([0, 1]) and + prop.regexpMatch("\\$route\\.(params|query|hash|path|fullPath)\\b.*") + ) + } + + override string getSourceType() { result = "Vue route parameter" } + } } diff --git a/javascript/ql/test/library-tests/frameworks/Vue/compont-with-route.vue b/javascript/ql/test/library-tests/frameworks/Vue/compont-with-route.vue new file mode 100644 index 00000000000..97fdac0ff93 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Vue/compont-with-route.vue @@ -0,0 +1,51 @@ + + + diff --git a/javascript/ql/test/library-tests/frameworks/Vue/router.js b/javascript/ql/test/library-tests/frameworks/Vue/router.js new file mode 100644 index 00000000000..acbbefecc01 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Vue/router.js @@ -0,0 +1,37 @@ +import Router from 'vue-router'; + +export const router = new Router({ + routes: [ + { + path: '/foo', + beforeEnter: (to, from, next) => { + to.query.x; + from.query.x; + }, + children: [ + { + path: '/bar', + beforeEnter: (to, from, next) => { + to.query.x; + from.query.x; + } + } + ] + } + ], + scrollBehavior(to, from, savedPosition) { + to.query.x; + from.query.x; + } +}); + +router.beforeEach((to, from, next) => { + to.query.x; + from.query.x; +}); + +router.afterEach((to, from) => { + to.query.x; + from.query.x; +}); + diff --git a/javascript/ql/test/library-tests/frameworks/Vue/tests.expected b/javascript/ql/test/library-tests/frameworks/Vue/tests.expected index 81f3733f007..0b290671d5c 100644 --- a/javascript/ql/test/library-tests/frameworks/Vue/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Vue/tests.expected @@ -1,4 +1,6 @@ instance_getAPropertyValue +| compont-with-route.vue:0:0:0:0 | compont-with-route.vue | dataA | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | +| compont-with-route.vue:0:0:0:0 | compont-with-route.vue | message | compont-with-route.vue:19:15:19:22 | 'Hello!' | | single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | dataA | single-component-file-1.vue:6:40:6:41 | 42 | | single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | dataA | single-file-component-3-script.js:4:37:4:38 | 42 | | single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataA | single-file-component-4.vue:15:14:15:15 | 42 | @@ -27,6 +29,7 @@ instance_getAPropertyValue | tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) | dataA | tst.js:89:22:89:23 | 42 | | tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | dataA | tst.js:100:18:100:19 | 42 | instance_getOption +| compont-with-route.vue:0:0:0:0 | compont-with-route.vue | watch | compont-with-route.vue:10:12:16:5 | {\\n ... }\\n } | | single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | data | single-component-file-1.vue:6:11:6:45 | functio ... 42 } } | | single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | data | single-file-component-3-script.js:4:8:4:42 | functio ... 42 } } | | single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | render | single-file-component-4.vue:9:13:9:22 | (h) => { } | @@ -56,6 +59,7 @@ instance_getOption | tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | data | tst.js:100:9:100:21 | { dataA: 42 } | | tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | methods | tst.js:101:12:103:3 | {\\n\\t\\t\\tm: ... ; }\\n\\t\\t} | instance +| compont-with-route.vue:0:0:0:0 | compont-with-route.vue | | single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | | single-file-component-2.vue:0:0:0:0 | single-file-component-2.vue | | single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | @@ -80,6 +84,10 @@ instance_heapStep | tst.js:102:20:102:29 | this.dataA | tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA | | tst.js:102:20:102:29 | this.dataA | tst.js:102:20:102:23 | this | tst.js:102:20:102:29 | this.dataA | templateElement +| compont-with-route.vue:1:1:3:11 |