diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll b/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll index 5f1996989ad..b797cf5d939 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll @@ -25,7 +25,7 @@ class TestLibrary extends RefType { } /** Holds if the given file is a test file. */ -private predicate isInTestFile(File file) { +predicate isInTestFile(File file) { file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and not file.getAbsolutePath().matches(["%/ql/test/%", "%/ql/automodel/test/%"]) // allows our test cases to work } diff --git a/java/ql/src/utils/modeleditor/ApplicationModeEndpoints.ql b/java/ql/src/utils/modeleditor/ApplicationModeEndpoints.ql new file mode 100644 index 00000000000..653fdc0c824 --- /dev/null +++ b/java/ql/src/utils/modeleditor/ApplicationModeEndpoints.ql @@ -0,0 +1,25 @@ +/** + * @name Fetch endpoints for use in the model editor (application mode) + * @description A list of 3rd party endpoints (methods) used in the codebase. Excludes test and generated code. + * @kind table + * @id java/utils/modeleditor/application-mode-endpoints + * @tags modeleditor endpoints application-mode + */ + +private import java +private import ApplicationModeEndpointsQuery +private import ModelEditor + +private Call aUsage(ExternalEndpoint endpoint) { + result.getCallee().getSourceDeclaration() = endpoint +} + +from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification +where + supported = isSupported(endpoint) and + usage = aUsage(endpoint) and + type = supportedType(endpoint) and + classification = usageClassification(usage) +select usage, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(), + endpoint.getParameterTypes(), supported, endpoint.jarContainer(), endpoint.jarVersion(), type, + classification diff --git a/java/ql/src/utils/modeleditor/ApplicationModeEndpointsQuery.qll b/java/ql/src/utils/modeleditor/ApplicationModeEndpointsQuery.qll new file mode 100644 index 00000000000..1b26eba5685 --- /dev/null +++ b/java/ql/src/utils/modeleditor/ApplicationModeEndpointsQuery.qll @@ -0,0 +1,40 @@ +private import java +private import semmle.code.java.dataflow.ExternalFlow +private import semmle.code.java.dataflow.FlowSources +private import semmle.code.java.dataflow.internal.DataFlowPrivate +private import ModelEditor + +/** + * A class of effectively public callables in library code. + */ +class ExternalEndpoint extends Endpoint { + ExternalEndpoint() { not this.fromSource() } + + /** Gets a node that is an input to a call to this API. */ + private DataFlow::Node getAnInput() { + exists(Call call | call.getCallee().getSourceDeclaration() = this | + result.asExpr().(Argument).getCall() = call or + result.(ArgumentNode).getCall().asCall() = call + ) + } + + /** Gets a node that is an output from a call to this API. */ + private DataFlow::Node getAnOutput() { + exists(Call call | call.getCallee().getSourceDeclaration() = this | + result.asExpr() = call or + result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call + ) + } + + override predicate hasSummary() { + Endpoint.super.hasSummary() + or + TaintTracking::localAdditionalTaintStep(this.getAnInput(), _) + } + + override predicate isSource() { + this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) + } + + override predicate isSink() { sinkNode(this.getAnInput(), _) } +} diff --git a/java/ql/src/utils/modeleditor/FrameworkModeEndpoints.ql b/java/ql/src/utils/modeleditor/FrameworkModeEndpoints.ql new file mode 100644 index 00000000000..9dfe57c53a9 --- /dev/null +++ b/java/ql/src/utils/modeleditor/FrameworkModeEndpoints.ql @@ -0,0 +1,19 @@ +/** + * @name Fetch endpoints for use in the model editor (framework mode) + * @description A list of endpoints accessible (methods) for consumers of the library. Excludes test and generated code. + * @kind table + * @id java/utils/modeleditor/framework-mode-endpoints + * @tags modeleditor endpoints framework-mode + */ + +private import java +private import FrameworkModeEndpointsQuery +private import ModelEditor + +from PublicEndpointFromSource endpoint, boolean supported, string type +where + supported = isSupported(endpoint) and + type = supportedType(endpoint) +select endpoint, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(), + endpoint.getParameterTypes(), supported, + endpoint.getCompilationUnit().getParentContainer().getBaseName(), type diff --git a/java/ql/src/utils/modeleditor/FrameworkModeEndpointsQuery.qll b/java/ql/src/utils/modeleditor/FrameworkModeEndpointsQuery.qll new file mode 100644 index 00000000000..4920ed4f011 --- /dev/null +++ b/java/ql/src/utils/modeleditor/FrameworkModeEndpointsQuery.qll @@ -0,0 +1,14 @@ +private import java +private import semmle.code.java.dataflow.internal.DataFlowPrivate +private import semmle.code.java.dataflow.internal.FlowSummaryImplSpecific +private import semmle.code.java.dataflow.internal.ModelExclusions +private import ModelEditor + +/** + * A class of effectively public callables from source code. + */ +class PublicEndpointFromSource extends Endpoint, ModelApi { + override predicate isSource() { sourceElement(this, _, _, _) } + + override predicate isSink() { sinkElement(this, _, _, _) } +} diff --git a/java/ql/src/utils/modeleditor/ModelEditor.qll b/java/ql/src/utils/modeleditor/ModelEditor.qll new file mode 100644 index 00000000000..2c1a56823f1 --- /dev/null +++ b/java/ql/src/utils/modeleditor/ModelEditor.qll @@ -0,0 +1,119 @@ +/** Provides classes and predicates related to handling APIs for the VS Code extension. */ + +private import java +private import semmle.code.java.dataflow.ExternalFlow +private import semmle.code.java.dataflow.FlowSummary +private import semmle.code.java.dataflow.TaintTracking +private import semmle.code.java.dataflow.internal.ModelExclusions + +/** Holds if the given callable/method is not worth supporting. */ +private predicate isUninteresting(Callable c) { + c.getDeclaringType() instanceof TestLibrary or + c.(Constructor).isParameterless() or + c.getDeclaringType() instanceof AnonymousClass +} + +/** + * A callable method from either the Standard Library, a 3rd party library or from the source. + */ +class Endpoint extends Callable { + Endpoint() { not isUninteresting(this) } + + /** + * Gets the package name of this endpoint. + */ + string getPackageName() { result = this.getDeclaringType().getPackage().getName() } + + /** + * Gets the type name of this endpoint. + */ + string getTypeName() { result = this.getDeclaringType().nestedName() } + + /** + * Gets the parameter types of this endpoint. + */ + string getParameterTypes() { result = paramsString(this) } + + private string getJarName() { + result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName() + } + + private string getJarVersion() { + result = this.getCompilationUnit().getParentContainer*().(JarFile).getSpecificationVersion() + } + + /** + * Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules. + */ + string jarContainer() { + result = this.getJarName() + or + not exists(this.getJarName()) and result = "rt.jar" + } + + /** + * Gets the version of the JAR file containing this API. Empty if no version is found in the JAR. + */ + string jarVersion() { + result = this.getJarVersion() + or + not exists(this.getJarVersion()) and result = "" + } + + /** Holds if this API has a supported summary. */ + pragma[nomagic] + predicate hasSummary() { this = any(SummarizedCallable sc).asCallable() } + + /** Holds if this API is a known source. */ + pragma[nomagic] + abstract predicate isSource(); + + /** Holds if this API is a known sink. */ + pragma[nomagic] + abstract predicate isSink(); + + /** Holds if this API is a known neutral. */ + pragma[nomagic] + predicate isNeutral() { + exists(string namespace, string type, string name, string signature | + neutralModel(namespace, type, name, signature, _, _) and + this = interpretElement(namespace, type, false, name, signature, "") + ) + } + + /** + * Holds if this API is supported by existing CodeQL libraries, that is, it is either a + * recognized source, sink or neutral or it has a flow summary. + */ + predicate isSupported() { + this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral() + } +} + +boolean isSupported(Endpoint endpoint) { + endpoint.isSupported() and result = true + or + not endpoint.isSupported() and result = false +} + +string supportedType(Endpoint endpoint) { + endpoint.isSink() and result = "sink" + or + endpoint.isSource() and result = "source" + or + endpoint.hasSummary() and result = "summary" + or + endpoint.isNeutral() and result = "neutral" + or + not endpoint.isSupported() and result = "" +} + +string usageClassification(Call usage) { + isInTestFile(usage.getLocation().getFile()) and result = "test" + or + usage.getFile() instanceof GeneratedFile and result = "generated" + or + not isInTestFile(usage.getLocation().getFile()) and + not usage.getFile() instanceof GeneratedFile and + result = "source" +} diff --git a/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected b/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected new file mode 100644 index 00000000000..919fc09b261 --- /dev/null +++ b/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.expected @@ -0,0 +1,15 @@ +| com/github/codeql/test/NonPublicClass.java:5:5:5:28 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:8:5:8:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:12:5:12:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:16:5:16:45 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:16:24:16:44 | get(...) | java.nio.file | Paths | get | (String,String[]) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:16:24:16:44 | get(...) | java.nio.file | Paths | get | (String,String[]) | true | rt.jar | | summary | source | +| com/github/codeql/test/PublicClass.java:20:5:20:68 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:20:24:20:47 | getDefault(...) | java.nio.file | FileSystems | getDefault | () | false | rt.jar | | | source | +| com/github/codeql/test/PublicClass.java:20:24:20:67 | getPath(...) | java.nio.file | FileSystem | getPath | (String,String[]) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicClass.java:20:24:20:67 | getPath(...) | java.nio.file | FileSystem | getPath | (String,String[]) | true | rt.jar | | summary | source | +| com/github/codeql/test/PublicClass.java:24:5:24:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicGenericClass.java:7:5:7:27 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicGenericClass.java:11:5:11:27 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicGenericInterface.java:8:7:8:29 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source | +| com/github/codeql/test/PublicInterface.java:7:7:7:29 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source | diff --git a/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.qlref b/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.qlref new file mode 100644 index 00000000000..4787fa5d4b2 --- /dev/null +++ b/java/ql/test/utils/modeleditor/ApplicationModeEndpoints.qlref @@ -0,0 +1 @@ +utils/modeleditor/ApplicationModeEndpoints.ql \ No newline at end of file diff --git a/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.expected b/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.expected new file mode 100644 index 00000000000..444165791ca --- /dev/null +++ b/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.expected @@ -0,0 +1,14 @@ +| com/github/codeql/test/PublicClass.java:7:15:7:19 | stuff | com.github.codeql.test | PublicClass | stuff | (String) | false | test | | +| com/github/codeql/test/PublicClass.java:11:22:11:32 | staticStuff | com.github.codeql.test | PublicClass | staticStuff | (String) | false | test | | +| com/github/codeql/test/PublicClass.java:15:18:15:31 | protectedStuff | com.github.codeql.test | PublicClass | protectedStuff | (String) | false | test | | +| com/github/codeql/test/PublicClass.java:27:17:27:28 | summaryStuff | com.github.codeql.test | PublicClass | summaryStuff | (String) | true | test | summary | +| com/github/codeql/test/PublicClass.java:31:17:31:27 | sourceStuff | com.github.codeql.test | PublicClass | sourceStuff | () | true | test | source | +| com/github/codeql/test/PublicClass.java:35:15:35:23 | sinkStuff | com.github.codeql.test | PublicClass | sinkStuff | (String) | true | test | sink | +| com/github/codeql/test/PublicClass.java:39:15:39:26 | neutralStuff | com.github.codeql.test | PublicClass | neutralStuff | (String) | true | test | neutral | +| com/github/codeql/test/PublicGenericClass.java:6:15:6:19 | stuff | com.github.codeql.test | PublicGenericClass | stuff | (Object) | false | test | | +| com/github/codeql/test/PublicGenericClass.java:10:20:10:25 | stuff2 | com.github.codeql.test | PublicGenericClass | stuff2 | (Object) | false | test | | +| com/github/codeql/test/PublicGenericInterface.java:4:17:4:21 | stuff | com.github.codeql.test | PublicGenericInterface | stuff | (Object) | false | test | | +| com/github/codeql/test/PublicGenericInterface.java:5:22:5:27 | stuff2 | com.github.codeql.test | PublicGenericInterface | stuff2 | (Object) | false | test | | +| com/github/codeql/test/PublicGenericInterface.java:7:24:7:34 | staticStuff | com.github.codeql.test | PublicGenericInterface | staticStuff | (String) | false | test | | +| com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | com.github.codeql.test | PublicInterface | stuff | (String) | false | test | | +| com/github/codeql/test/PublicInterface.java:6:24:6:34 | staticStuff | com.github.codeql.test | PublicInterface | staticStuff | (String) | false | test | | diff --git a/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.ext.yml b/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.ext.yml new file mode 100644 index 00000000000..93ad471385a --- /dev/null +++ b/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.ext.yml @@ -0,0 +1,24 @@ +extensions: + - addsTo: + pack: codeql/java-all + extensible: sourceModel + data: + - ["com.github.codeql.test","PublicClass",true,"sourceStuff","()","","ReturnValue","remote","manual"] + + - addsTo: + pack: codeql/java-all + extensible: sinkModel + data: + - ["com.github.codeql.test","PublicClass",true,"sinkStuff","(String)","","Argument[0]","sql-injection","manual"] + + - addsTo: + pack: codeql/java-all + extensible: summaryModel + data: + - ["com.github.codeql.test","PublicClass",true,"summaryStuff","(String)","","Argument[0]","ReturnValue","taint","manual"] + + - addsTo: + pack: codeql/java-all + extensible: neutralModel + data: + - ["com.github.codeql.test","PublicClass","neutralStuff","(String)","summary","manual"] diff --git a/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.qlref b/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.qlref new file mode 100644 index 00000000000..5ae87455edd --- /dev/null +++ b/java/ql/test/utils/modeleditor/FrameworkModeEndpoints.qlref @@ -0,0 +1 @@ +utils/modeleditor/FrameworkModeEndpoints.ql \ No newline at end of file diff --git a/java/ql/test/utils/modeleditor/com/github/codeql/test/NonPublicClass.java b/java/ql/test/utils/modeleditor/com/github/codeql/test/NonPublicClass.java new file mode 100644 index 00000000000..5ad1f42123a --- /dev/null +++ b/java/ql/test/utils/modeleditor/com/github/codeql/test/NonPublicClass.java @@ -0,0 +1,7 @@ +package com.github.codeql.test; + +class NonPublicClass { + public void noCandidates(String here) { + System.out.println(here); + } +} diff --git a/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicClass.java b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicClass.java new file mode 100644 index 00000000000..b53f70eaf15 --- /dev/null +++ b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicClass.java @@ -0,0 +1,42 @@ +package com.github.codeql.test; + +import java.nio.file.FileSystems; +import java.nio.file.Paths; + +public class PublicClass { + public void stuff(String arg) { + System.out.println(arg); + } + + public static void staticStuff(String arg) { + System.out.println(arg); + } + + protected void protectedStuff(String arg) { + System.out.println(Paths.get("foo", arg)); + } + + private void privateStuff(String arg) { + System.out.println(FileSystems.getDefault().getPath("foo", arg)); + } + + void packagePrivateStuff(String arg) { + System.out.println(arg); + } + + public String summaryStuff(String arg) { + return arg; + } + + public String sourceStuff() { + return "stuff"; + } + + public void sinkStuff(String arg) { + // do nothing + } + + public void neutralStuff(String arg) { + // do nothing + } +} diff --git a/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicGenericClass.java b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicGenericClass.java new file mode 100644 index 00000000000..a14dd64a5b9 --- /dev/null +++ b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicGenericClass.java @@ -0,0 +1,13 @@ +package com.github.codeql.test; + +import java.nio.file.Paths; + +public class PublicGenericClass implements PublicGenericInterface { + public void stuff(T arg) { + System.out.println(arg); + } + + public void stuff2(T3 arg) { + System.out.println(arg); + } +} diff --git a/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicGenericInterface.java b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicGenericInterface.java new file mode 100644 index 00000000000..030de29b681 --- /dev/null +++ b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicGenericInterface.java @@ -0,0 +1,10 @@ +package com.github.codeql.test; + +public interface PublicGenericInterface { + public void stuff(T arg); + public void stuff2(T2 arg); + + public static void staticStuff(String arg) { + System.out.println(arg); + } +} diff --git a/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicInterface.java b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicInterface.java new file mode 100644 index 00000000000..cde005d4c4a --- /dev/null +++ b/java/ql/test/utils/modeleditor/com/github/codeql/test/PublicInterface.java @@ -0,0 +1,9 @@ +package com.github.codeql.test; + +public interface PublicInterface { + public void stuff(String arg); + + public static void staticStuff(String arg) { + System.out.println(arg); + } +}