From 641c5df79fa85dfb96354c9a9ac5b3f585312e3d Mon Sep 17 00:00:00 2001 From: intrigus Date: Fri, 3 Jul 2020 00:25:27 +0200 Subject: [PATCH 1/6] Centralize and model additional path creations. --- .../src/Security/CWE/CWE-022/PathsCommon.qll | 74 -------- .../src/Security/CWE/CWE-022/TaintedPath.ql | 2 +- .../Security/CWE/CWE-022/TaintedPathLocal.ql | 2 +- .../code/java/security/PathCreation.qll | 165 ++++++++++++++++++ .../pathcreation/PathCreation.expected | 21 +++ .../pathcreation/PathCreation.java | 71 ++++++++ .../pathcreation/PathCreation.ql | 5 + 7 files changed, 264 insertions(+), 76 deletions(-) delete mode 100644 java/ql/src/Security/CWE/CWE-022/PathsCommon.qll create mode 100644 java/ql/src/semmle/code/java/security/PathCreation.qll create mode 100644 java/ql/test/library-tests/pathcreation/PathCreation.expected create mode 100644 java/ql/test/library-tests/pathcreation/PathCreation.java create mode 100644 java/ql/test/library-tests/pathcreation/PathCreation.ql diff --git a/java/ql/src/Security/CWE/CWE-022/PathsCommon.qll b/java/ql/src/Security/CWE/CWE-022/PathsCommon.qll deleted file mode 100644 index 362144d8c191..000000000000 --- a/java/ql/src/Security/CWE/CWE-022/PathsCommon.qll +++ /dev/null @@ -1,74 +0,0 @@ -import java -import semmle.code.java.controlflow.Guards - -abstract class PathCreation extends Expr { - abstract Expr getInput(); -} - -class PathsGet extends PathCreation, MethodAccess { - PathsGet() { - exists(Method m | m = this.getMethod() | - m.getDeclaringType() instanceof TypePaths and - m.getName() = "get" - ) - } - - override Expr getInput() { result = this.getAnArgument() } -} - -class FileSystemGetPath extends PathCreation, MethodAccess { - FileSystemGetPath() { - exists(Method m | m = this.getMethod() | - m.getDeclaringType() instanceof TypeFileSystem and - m.getName() = "getPath" - ) - } - - override Expr getInput() { result = this.getAnArgument() } -} - -class FileCreation extends PathCreation, ClassInstanceExpr { - FileCreation() { this.getConstructedType() instanceof TypeFile } - - override Expr getInput() { - result = this.getAnArgument() and - // Relevant arguments include those that are not a `File`. - not result.getType() instanceof TypeFile - } -} - -class FileWriterCreation extends PathCreation, ClassInstanceExpr { - FileWriterCreation() { this.getConstructedType().getQualifiedName() = "java.io.FileWriter" } - - override Expr getInput() { - result = this.getAnArgument() and - // Relevant arguments are those of type `String`. - result.getType() instanceof TypeString - } -} - -predicate inWeakCheck(Expr e) { - // None of these are sufficient to guarantee that a string is safe. - exists(MethodAccess m, Method def | m.getQualifier() = e and m.getMethod() = def | - def.getName() = "startsWith" or - def.getName() = "endsWith" or - def.getName() = "isEmpty" or - def.getName() = "equals" - ) - or - // Checking against `null` has no bearing on path traversal. - exists(EqualityTest b | b.getAnOperand() = e | b.getAnOperand() instanceof NullLiteral) -} - -// Ignore cases where the variable has been checked somehow, -// but allow some particularly obviously bad cases. -predicate guarded(VarAccess e) { - exists(PathCreation p | e = p.getInput()) and - exists(ConditionBlock cb, Expr c | - cb.getCondition().getAChildExpr*() = c and - c = e.getVariable().getAnAccess() and - cb.controls(e.getBasicBlock(), true) and - // Disallow a few obviously bad checks. - not inWeakCheck(c) - ) -} diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql index 2094207dc92b..0911436e8932 100644 --- a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql @@ -14,7 +14,7 @@ import java import semmle.code.java.dataflow.FlowSources -import PathsCommon +import semmle.code.java.security.PathCreation import DataFlow::PathGraph class ContainsDotDotSanitizer extends DataFlow::BarrierGuard { diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql index 4d1c20f923e4..cd65a7567583 100644 --- a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql @@ -14,7 +14,7 @@ import java import semmle.code.java.dataflow.FlowSources -import PathsCommon +import semmle.code.java.security.PathCreation import DataFlow::PathGraph class TaintedPathLocalConfig extends TaintTracking::Configuration { diff --git a/java/ql/src/semmle/code/java/security/PathCreation.qll b/java/ql/src/semmle/code/java/security/PathCreation.qll new file mode 100644 index 000000000000..a26328737c94 --- /dev/null +++ b/java/ql/src/semmle/code/java/security/PathCreation.qll @@ -0,0 +1,165 @@ +/** + * Models the different ways to create paths. Either by using `java.io.File`-related APIs or `java.nio.file.Path`-related APIs. + */ + +import java +import semmle.code.java.controlflow.Guards + +/** Models the creation of a path. */ +abstract class PathCreation extends Expr { + /** Gets an input that is used in the creation of this path. */ + abstract Expr getInput(); +} + +/** Models the `java.nio.file.Paths.get` method. */ +class PathsGet extends PathCreation, MethodAccess { + PathsGet() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypePaths and + m.getName() = "get" + ) + } + + override Expr getInput() { result = this.getAnArgument() } +} + +/** Models the `java.nio.file.FileSystem.getPath` method. */ +class FileSystemGetPath extends PathCreation, MethodAccess { + FileSystemGetPath() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypeFileSystem and + m.getName() = "getPath" + ) + } + + override Expr getInput() { result = this.getAnArgument() } +} + +/** Models the `new java.io.File(...)` constructor. */ +class FileCreation extends PathCreation, ClassInstanceExpr { + FileCreation() { this.getConstructedType() instanceof TypeFile } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments include those that are not a `File`. + not result.getType() instanceof TypeFile + } +} + +/** Models the `java.nio.file.Path.resolveSibling` method. */ +class PathResolveSiblingCreation extends PathCreation, MethodAccess { + PathResolveSiblingCreation() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypePath and + m.getName() = "resolveSibling" + ) + } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +/** Models the `java.nio.file.Path.resolve` method. */ +class PathResolveCreation extends PathCreation, MethodAccess { + PathResolveCreation() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypePath and + m.getName() = "resolve" + ) + } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +/** Models the `java.nio.file.Path.of` method. */ +class PathOfCreation extends PathCreation, MethodAccess { + PathOfCreation() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypePath and + m.getName() = "of" + ) + } + + override Expr getInput() { result = this.getAnArgument() } +} + +/** Models the `new java.io.FileWriter(...)` constructor. */ +class FileWriterCreation extends PathCreation, ClassInstanceExpr { + FileWriterCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileWriter") } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +/** Models the `new java.io.FileReader(...)` constructor. */ +class FileReaderCreation extends PathCreation, ClassInstanceExpr { + FileReaderCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileReader") } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +/** Models the `new java.io.FileInputStream(...)` constructor. */ +class FileInputStreamCreation extends PathCreation, ClassInstanceExpr { + FileInputStreamCreation() { + this.getConstructedType().hasQualifiedName("java.io", "FileInputStream") + } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +/** Models the `new java.io.FileOutputStream(...)` constructor. */ +class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr { + FileOutputStreamCreation() { + this.getConstructedType().hasQualifiedName("java.io", "FileOutputStream") + } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +private predicate inWeakCheck(Expr e) { + // None of these are sufficient to guarantee that a string is safe. + exists(MethodAccess m, Method def | m.getQualifier() = e and m.getMethod() = def | + def.getName() = "startsWith" or + def.getName() = "endsWith" or + def.getName() = "isEmpty" or + def.getName() = "equals" + ) + or + // Checking against `null` has no bearing on path traversal. + exists(EqualityTest b | b.getAnOperand() = e | b.getAnOperand() instanceof NullLiteral) +} + +// Ignore cases where the variable has been checked somehow, +// but allow some particularly obviously bad cases. +predicate guarded(VarAccess e) { + exists(PathCreation p | e = p.getInput()) and + exists(ConditionBlock cb, Expr c | + cb.getCondition().getAChildExpr*() = c and + c = e.getVariable().getAnAccess() and + cb.controls(e.getBasicBlock(), true) and + // Disallow a few obviously bad checks. + not inWeakCheck(c) + ) +} diff --git a/java/ql/test/library-tests/pathcreation/PathCreation.expected b/java/ql/test/library-tests/pathcreation/PathCreation.expected new file mode 100644 index 000000000000..0bfd9cdd8937 --- /dev/null +++ b/java/ql/test/library-tests/pathcreation/PathCreation.expected @@ -0,0 +1,21 @@ +| PathCreation.java:13:18:13:32 | new File(...) | +| PathCreation.java:14:19:14:40 | new File(...) | +| PathCreation.java:18:18:18:49 | new File(...) | +| PathCreation.java:18:27:18:41 | new File(...) | +| PathCreation.java:22:18:22:41 | new File(...) | +| PathCreation.java:26:18:26:31 | of(...) | +| PathCreation.java:27:19:27:39 | of(...) | +| PathCreation.java:31:18:31:40 | of(...) | +| PathCreation.java:35:18:35:33 | get(...) | +| PathCreation.java:36:19:36:41 | get(...) | +| PathCreation.java:40:18:40:42 | get(...) | +| PathCreation.java:44:18:44:56 | getPath(...) | +| PathCreation.java:45:19:45:64 | getPath(...) | +| PathCreation.java:49:18:49:31 | of(...) | +| PathCreation.java:49:18:49:53 | resolveSibling(...) | +| PathCreation.java:53:18:53:31 | of(...) | +| PathCreation.java:53:18:53:46 | resolve(...) | +| PathCreation.java:57:25:57:45 | new FileWriter(...) | +| PathCreation.java:61:25:61:45 | new FileReader(...) | +| PathCreation.java:65:32:65:58 | new FileOutputStream(...) | +| PathCreation.java:69:31:69:56 | new FileInputStream(...) | diff --git a/java/ql/test/library-tests/pathcreation/PathCreation.java b/java/ql/test/library-tests/pathcreation/PathCreation.java new file mode 100644 index 000000000000..dcdb28adf457 --- /dev/null +++ b/java/ql/test/library-tests/pathcreation/PathCreation.java @@ -0,0 +1,71 @@ +import java.io.File; +import java.io.FileWriter; +import java.io.FileReader; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.FileSystems; +import java.net.URI; + +class PathCreation { + public void testNewFileWithString() { + File f = new File("dir"); + File f2 = new File("dir", "sub"); + } + + public void testNewFileWithFileString() { + File f = new File(new File("dir"), "sub"); + } + + public void testNewFileWithURI() { + File f = new File(new URI("dir")); + } + + public void testPathOfWithString() { + Path p = Path.of("dir"); + Path p2 = Path.of("dir", "sub"); + } + + public void testPathOfWithURI() { + Path p = Path.of(new URI("dir")); + } + + public void testPathsGetWithString() { + Path p = Paths.get("dir"); + Path p2 = Paths.get("dir", "sub"); + } + + public void testPathsGetWithURI() { + Path p = Paths.get(new URI("dir")); + } + + public void testFileSystemGetPathWithString() { + Path p = FileSystems.getDefault().getPath("dir"); + Path p2 = FileSystems.getDefault().getPath("dir", "sub"); + } + + public void testPathResolveSiblingWithString() { + Path p = Path.of("dir").resolveSibling("sub"); + } + + public void testPathResolveWithString() { + Path p = Path.of("dir").resolve("sub"); + } + + public void testNewFileWriterWithString() { + FileWriter fw = new FileWriter("dir"); + } + + public void testNewFileReaderWithString() { + FileReader fr = new FileReader("dir"); + } + + public void testNewFileOutputStreamWithString() { + FileOutputStream fos = new FileOutputStream("dir"); + } + + public void testNewFileInputStreamWithString() { + FileInputStream fis = new FileInputStream("dir"); + } +} diff --git a/java/ql/test/library-tests/pathcreation/PathCreation.ql b/java/ql/test/library-tests/pathcreation/PathCreation.ql new file mode 100644 index 000000000000..d634ab1452cc --- /dev/null +++ b/java/ql/test/library-tests/pathcreation/PathCreation.ql @@ -0,0 +1,5 @@ +import java +import semmle.code.java.security.PathCreation + +from PathCreation path +select path From 4570444c7ed019b7e95a28c39f42c0a9d8eb17e9 Mon Sep 17 00:00:00 2001 From: intrigus Date: Sat, 18 Jul 2020 23:57:01 +0200 Subject: [PATCH 2/6] Rename to getAnInput and clarify doc. --- .../src/Security/CWE/CWE-022/TaintedPath.ql | 4 +-- .../Security/CWE/CWE-022/TaintedPathLocal.ql | 4 +-- .../code/java/security/PathCreation.qll | 29 ++++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql index 0911436e8932..1d7b39956eab 100644 --- a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql @@ -34,7 +34,7 @@ class TaintedPathConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(Expr e | e = sink.asExpr() | e = any(PathCreation p).getInput() and not guarded(e)) + exists(Expr e | e = sink.asExpr() | e = any(PathCreation p).getAnInput() and not guarded(e)) } override predicate isSanitizer(DataFlow::Node node) { @@ -48,7 +48,7 @@ class TaintedPathConfig extends TaintTracking::Configuration { from DataFlow::PathNode source, DataFlow::PathNode sink, PathCreation p, TaintedPathConfig conf where - sink.getNode().asExpr() = p.getInput() and + sink.getNode().asExpr() = p.getAnInput() and conf.hasFlowPath(source, sink) select p, source, sink, "$@ flows to here and is used in a path.", source.getNode(), "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql index cd65a7567583..c12230a9922c 100644 --- a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql @@ -22,7 +22,7 @@ class TaintedPathLocalConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } - override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(PathCreation p).getInput() } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(PathCreation p).getAnInput() } } from @@ -30,7 +30,7 @@ from TaintedPathLocalConfig conf where e = sink.getNode().asExpr() and - e = p.getInput() and + e = p.getAnInput() and conf.hasFlowPath(source, sink) and not guarded(e) select p, source, sink, "$@ flows to here and is used in a path.", source.getNode(), diff --git a/java/ql/src/semmle/code/java/security/PathCreation.qll b/java/ql/src/semmle/code/java/security/PathCreation.qll index a26328737c94..41679bdcac22 100644 --- a/java/ql/src/semmle/code/java/security/PathCreation.qll +++ b/java/ql/src/semmle/code/java/security/PathCreation.qll @@ -7,8 +7,11 @@ import semmle.code.java.controlflow.Guards /** Models the creation of a path. */ abstract class PathCreation extends Expr { - /** Gets an input that is used in the creation of this path. */ - abstract Expr getInput(); + /** + * Gets an input that is used in the creation of this path. + * This excludes inputs of type `File` and `Path`. + */ + abstract Expr getAnInput(); } /** Models the `java.nio.file.Paths.get` method. */ @@ -20,7 +23,7 @@ class PathsGet extends PathCreation, MethodAccess { ) } - override Expr getInput() { result = this.getAnArgument() } + override Expr getAnInput() { result = this.getAnArgument() } } /** Models the `java.nio.file.FileSystem.getPath` method. */ @@ -32,14 +35,14 @@ class FileSystemGetPath extends PathCreation, MethodAccess { ) } - override Expr getInput() { result = this.getAnArgument() } + override Expr getAnInput() { result = this.getAnArgument() } } /** Models the `new java.io.File(...)` constructor. */ class FileCreation extends PathCreation, ClassInstanceExpr { FileCreation() { this.getConstructedType() instanceof TypeFile } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments include those that are not a `File`. not result.getType() instanceof TypeFile @@ -55,7 +58,7 @@ class PathResolveSiblingCreation extends PathCreation, MethodAccess { ) } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments are those of type `String`. result.getType() instanceof TypeString @@ -71,7 +74,7 @@ class PathResolveCreation extends PathCreation, MethodAccess { ) } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments are those of type `String`. result.getType() instanceof TypeString @@ -87,14 +90,14 @@ class PathOfCreation extends PathCreation, MethodAccess { ) } - override Expr getInput() { result = this.getAnArgument() } + override Expr getAnInput() { result = this.getAnArgument() } } /** Models the `new java.io.FileWriter(...)` constructor. */ class FileWriterCreation extends PathCreation, ClassInstanceExpr { FileWriterCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileWriter") } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments are those of type `String`. result.getType() instanceof TypeString @@ -105,7 +108,7 @@ class FileWriterCreation extends PathCreation, ClassInstanceExpr { class FileReaderCreation extends PathCreation, ClassInstanceExpr { FileReaderCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileReader") } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments are those of type `String`. result.getType() instanceof TypeString @@ -118,7 +121,7 @@ class FileInputStreamCreation extends PathCreation, ClassInstanceExpr { this.getConstructedType().hasQualifiedName("java.io", "FileInputStream") } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments are those of type `String`. result.getType() instanceof TypeString @@ -131,7 +134,7 @@ class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr { this.getConstructedType().hasQualifiedName("java.io", "FileOutputStream") } - override Expr getInput() { + override Expr getAnInput() { result = this.getAnArgument() and // Relevant arguments are those of type `String`. result.getType() instanceof TypeString @@ -154,7 +157,7 @@ private predicate inWeakCheck(Expr e) { // Ignore cases where the variable has been checked somehow, // but allow some particularly obviously bad cases. predicate guarded(VarAccess e) { - exists(PathCreation p | e = p.getInput()) and + exists(PathCreation p | e = p.getAnInput()) and exists(ConditionBlock cb, Expr c | cb.getCondition().getAChildExpr*() = c and c = e.getVariable().getAnAccess() and From b705f7f3e9fe42db043afd2c56ec2814d2cfae19 Mon Sep 17 00:00:00 2001 From: intrigus Date: Sun, 19 Jul 2020 00:10:39 +0200 Subject: [PATCH 3/6] Improve "PathCreation" Test. --- java/ql/test/library-tests/pathcreation/PathCreation.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/library-tests/pathcreation/PathCreation.ql b/java/ql/test/library-tests/pathcreation/PathCreation.ql index d634ab1452cc..fb27c5383190 100644 --- a/java/ql/test/library-tests/pathcreation/PathCreation.ql +++ b/java/ql/test/library-tests/pathcreation/PathCreation.ql @@ -2,4 +2,4 @@ import java import semmle.code.java.security.PathCreation from PathCreation path -select path +select path, path.getAnInput() From 33526f61a8bdcdc1d82cc5908b68e5799b14238c Mon Sep 17 00:00:00 2001 From: intrigus Date: Sun, 19 Jul 2020 00:11:04 +0200 Subject: [PATCH 4/6] Make path creation subclasses private. --- .../code/java/security/PathCreation.qll | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/java/ql/src/semmle/code/java/security/PathCreation.qll b/java/ql/src/semmle/code/java/security/PathCreation.qll index 41679bdcac22..ee5b5aed009c 100644 --- a/java/ql/src/semmle/code/java/security/PathCreation.qll +++ b/java/ql/src/semmle/code/java/security/PathCreation.qll @@ -15,7 +15,7 @@ abstract class PathCreation extends Expr { } /** Models the `java.nio.file.Paths.get` method. */ -class PathsGet extends PathCreation, MethodAccess { +private class PathsGet extends PathCreation, MethodAccess { PathsGet() { exists(Method m | m = this.getMethod() | m.getDeclaringType() instanceof TypePaths and @@ -27,7 +27,7 @@ class PathsGet extends PathCreation, MethodAccess { } /** Models the `java.nio.file.FileSystem.getPath` method. */ -class FileSystemGetPath extends PathCreation, MethodAccess { +private class FileSystemGetPath extends PathCreation, MethodAccess { FileSystemGetPath() { exists(Method m | m = this.getMethod() | m.getDeclaringType() instanceof TypeFileSystem and @@ -39,7 +39,7 @@ class FileSystemGetPath extends PathCreation, MethodAccess { } /** Models the `new java.io.File(...)` constructor. */ -class FileCreation extends PathCreation, ClassInstanceExpr { +private class FileCreation extends PathCreation, ClassInstanceExpr { FileCreation() { this.getConstructedType() instanceof TypeFile } override Expr getAnInput() { @@ -50,7 +50,7 @@ class FileCreation extends PathCreation, ClassInstanceExpr { } /** Models the `java.nio.file.Path.resolveSibling` method. */ -class PathResolveSiblingCreation extends PathCreation, MethodAccess { +private class PathResolveSiblingCreation extends PathCreation, MethodAccess { PathResolveSiblingCreation() { exists(Method m | m = this.getMethod() | m.getDeclaringType() instanceof TypePath and @@ -66,7 +66,7 @@ class PathResolveSiblingCreation extends PathCreation, MethodAccess { } /** Models the `java.nio.file.Path.resolve` method. */ -class PathResolveCreation extends PathCreation, MethodAccess { +private class PathResolveCreation extends PathCreation, MethodAccess { PathResolveCreation() { exists(Method m | m = this.getMethod() | m.getDeclaringType() instanceof TypePath and @@ -82,7 +82,7 @@ class PathResolveCreation extends PathCreation, MethodAccess { } /** Models the `java.nio.file.Path.of` method. */ -class PathOfCreation extends PathCreation, MethodAccess { +private class PathOfCreation extends PathCreation, MethodAccess { PathOfCreation() { exists(Method m | m = this.getMethod() | m.getDeclaringType() instanceof TypePath and @@ -94,7 +94,7 @@ class PathOfCreation extends PathCreation, MethodAccess { } /** Models the `new java.io.FileWriter(...)` constructor. */ -class FileWriterCreation extends PathCreation, ClassInstanceExpr { +private class FileWriterCreation extends PathCreation, ClassInstanceExpr { FileWriterCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileWriter") } override Expr getAnInput() { @@ -105,7 +105,7 @@ class FileWriterCreation extends PathCreation, ClassInstanceExpr { } /** Models the `new java.io.FileReader(...)` constructor. */ -class FileReaderCreation extends PathCreation, ClassInstanceExpr { +private class FileReaderCreation extends PathCreation, ClassInstanceExpr { FileReaderCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileReader") } override Expr getAnInput() { @@ -116,7 +116,7 @@ class FileReaderCreation extends PathCreation, ClassInstanceExpr { } /** Models the `new java.io.FileInputStream(...)` constructor. */ -class FileInputStreamCreation extends PathCreation, ClassInstanceExpr { +private class FileInputStreamCreation extends PathCreation, ClassInstanceExpr { FileInputStreamCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileInputStream") } @@ -129,7 +129,7 @@ class FileInputStreamCreation extends PathCreation, ClassInstanceExpr { } /** Models the `new java.io.FileOutputStream(...)` constructor. */ -class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr { +private class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr { FileOutputStreamCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileOutputStream") } From f94055fa2c66afa2a06cc80a2ef4be5a9eedf540 Mon Sep 17 00:00:00 2001 From: intrigus Date: Sun, 19 Jul 2020 00:19:29 +0200 Subject: [PATCH 5/6] Move tainted path ad-hoc guard back. --- .../src/Security/CWE/CWE-022/TaintedPath.ql | 1 + .../CWE/CWE-022/TaintedPathCommon.qll | 33 +++++++++++++++++++ .../Security/CWE/CWE-022/TaintedPathLocal.ql | 5 ++- .../code/java/security/PathCreation.qll | 27 --------------- 4 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 java/ql/src/Security/CWE/CWE-022/TaintedPathCommon.qll diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql index 1d7b39956eab..01d89cc8e06e 100644 --- a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql @@ -16,6 +16,7 @@ import java import semmle.code.java.dataflow.FlowSources import semmle.code.java.security.PathCreation import DataFlow::PathGraph +import TaintedPathCommon class ContainsDotDotSanitizer extends DataFlow::BarrierGuard { ContainsDotDotSanitizer() { diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathCommon.qll b/java/ql/src/Security/CWE/CWE-022/TaintedPathCommon.qll new file mode 100644 index 000000000000..beea2d5f6662 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathCommon.qll @@ -0,0 +1,33 @@ +/** + * Models a very basic guard for the tainted path queries. + */ + +import java +import semmle.code.java.controlflow.Guards +import semmle.code.java.security.PathCreation + +private predicate inWeakCheck(Expr e) { + // None of these are sufficient to guarantee that a string is safe. + exists(MethodAccess m, Method def | m.getQualifier() = e and m.getMethod() = def | + def.getName() = "startsWith" or + def.getName() = "endsWith" or + def.getName() = "isEmpty" or + def.getName() = "equals" + ) + or + // Checking against `null` has no bearing on path traversal. + exists(EqualityTest b | b.getAnOperand() = e | b.getAnOperand() instanceof NullLiteral) +} + +// Ignore cases where the variable has been checked somehow, +// but allow some particularly obviously bad cases. +predicate guarded(VarAccess e) { + exists(PathCreation p | e = p.getAnInput()) and + exists(ConditionBlock cb, Expr c | + cb.getCondition().getAChildExpr*() = c and + c = e.getVariable().getAnAccess() and + cb.controls(e.getBasicBlock(), true) and + // Disallow a few obviously bad checks. + not inWeakCheck(c) + ) +} diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql index c12230a9922c..a64f88997e85 100644 --- a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql @@ -16,13 +16,16 @@ import java import semmle.code.java.dataflow.FlowSources import semmle.code.java.security.PathCreation import DataFlow::PathGraph +import TaintedPathCommon class TaintedPathLocalConfig extends TaintTracking::Configuration { TaintedPathLocalConfig() { this = "TaintedPathLocalConfig" } override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } - override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(PathCreation p).getAnInput() } + override predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(PathCreation p).getAnInput() + } } from diff --git a/java/ql/src/semmle/code/java/security/PathCreation.qll b/java/ql/src/semmle/code/java/security/PathCreation.qll index ee5b5aed009c..2954cfa1332b 100644 --- a/java/ql/src/semmle/code/java/security/PathCreation.qll +++ b/java/ql/src/semmle/code/java/security/PathCreation.qll @@ -3,7 +3,6 @@ */ import java -import semmle.code.java.controlflow.Guards /** Models the creation of a path. */ abstract class PathCreation extends Expr { @@ -140,29 +139,3 @@ private class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr { result.getType() instanceof TypeString } } - -private predicate inWeakCheck(Expr e) { - // None of these are sufficient to guarantee that a string is safe. - exists(MethodAccess m, Method def | m.getQualifier() = e and m.getMethod() = def | - def.getName() = "startsWith" or - def.getName() = "endsWith" or - def.getName() = "isEmpty" or - def.getName() = "equals" - ) - or - // Checking against `null` has no bearing on path traversal. - exists(EqualityTest b | b.getAnOperand() = e | b.getAnOperand() instanceof NullLiteral) -} - -// Ignore cases where the variable has been checked somehow, -// but allow some particularly obviously bad cases. -predicate guarded(VarAccess e) { - exists(PathCreation p | e = p.getAnInput()) and - exists(ConditionBlock cb, Expr c | - cb.getCondition().getAChildExpr*() = c and - c = e.getVariable().getAnAccess() and - cb.controls(e.getBasicBlock(), true) and - // Disallow a few obviously bad checks. - not inWeakCheck(c) - ) -} From 1011325cf7d5466057a00e3efa086e11a1ecb8b4 Mon Sep 17 00:00:00 2001 From: intrigus Date: Wed, 5 Aug 2020 21:45:41 +0200 Subject: [PATCH 6/6] Accept test changes. --- .../pathcreation/PathCreation.expected | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/java/ql/test/library-tests/pathcreation/PathCreation.expected b/java/ql/test/library-tests/pathcreation/PathCreation.expected index 0bfd9cdd8937..c0ac69c7da42 100644 --- a/java/ql/test/library-tests/pathcreation/PathCreation.expected +++ b/java/ql/test/library-tests/pathcreation/PathCreation.expected @@ -1,21 +1,25 @@ -| PathCreation.java:13:18:13:32 | new File(...) | -| PathCreation.java:14:19:14:40 | new File(...) | -| PathCreation.java:18:18:18:49 | new File(...) | -| PathCreation.java:18:27:18:41 | new File(...) | -| PathCreation.java:22:18:22:41 | new File(...) | -| PathCreation.java:26:18:26:31 | of(...) | -| PathCreation.java:27:19:27:39 | of(...) | -| PathCreation.java:31:18:31:40 | of(...) | -| PathCreation.java:35:18:35:33 | get(...) | -| PathCreation.java:36:19:36:41 | get(...) | -| PathCreation.java:40:18:40:42 | get(...) | -| PathCreation.java:44:18:44:56 | getPath(...) | -| PathCreation.java:45:19:45:64 | getPath(...) | -| PathCreation.java:49:18:49:31 | of(...) | -| PathCreation.java:49:18:49:53 | resolveSibling(...) | -| PathCreation.java:53:18:53:31 | of(...) | -| PathCreation.java:53:18:53:46 | resolve(...) | -| PathCreation.java:57:25:57:45 | new FileWriter(...) | -| PathCreation.java:61:25:61:45 | new FileReader(...) | -| PathCreation.java:65:32:65:58 | new FileOutputStream(...) | -| PathCreation.java:69:31:69:56 | new FileInputStream(...) | +| PathCreation.java:13:18:13:32 | new File(...) | PathCreation.java:13:27:13:31 | "dir" | +| PathCreation.java:14:19:14:40 | new File(...) | PathCreation.java:14:28:14:32 | "dir" | +| PathCreation.java:14:19:14:40 | new File(...) | PathCreation.java:14:35:14:39 | "sub" | +| PathCreation.java:18:18:18:49 | new File(...) | PathCreation.java:18:44:18:48 | "sub" | +| PathCreation.java:18:27:18:41 | new File(...) | PathCreation.java:18:36:18:40 | "dir" | +| PathCreation.java:22:18:22:41 | new File(...) | PathCreation.java:22:27:22:40 | new URI(...) | +| PathCreation.java:26:18:26:31 | of(...) | PathCreation.java:26:26:26:30 | "dir" | +| PathCreation.java:27:19:27:39 | of(...) | PathCreation.java:27:27:27:31 | "dir" | +| PathCreation.java:27:19:27:39 | of(...) | PathCreation.java:27:34:27:38 | "sub" | +| PathCreation.java:31:18:31:40 | of(...) | PathCreation.java:31:26:31:39 | new URI(...) | +| PathCreation.java:35:18:35:33 | get(...) | PathCreation.java:35:28:35:32 | "dir" | +| PathCreation.java:36:19:36:41 | get(...) | PathCreation.java:36:29:36:33 | "dir" | +| PathCreation.java:36:19:36:41 | get(...) | PathCreation.java:36:36:36:40 | "sub" | +| PathCreation.java:40:18:40:42 | get(...) | PathCreation.java:40:28:40:41 | new URI(...) | +| PathCreation.java:44:18:44:56 | getPath(...) | PathCreation.java:44:51:44:55 | "dir" | +| PathCreation.java:45:19:45:64 | getPath(...) | PathCreation.java:45:52:45:56 | "dir" | +| PathCreation.java:45:19:45:64 | getPath(...) | PathCreation.java:45:59:45:63 | "sub" | +| PathCreation.java:49:18:49:31 | of(...) | PathCreation.java:49:26:49:30 | "dir" | +| PathCreation.java:49:18:49:53 | resolveSibling(...) | PathCreation.java:49:48:49:52 | "sub" | +| PathCreation.java:53:18:53:31 | of(...) | PathCreation.java:53:26:53:30 | "dir" | +| PathCreation.java:53:18:53:46 | resolve(...) | PathCreation.java:53:41:53:45 | "sub" | +| PathCreation.java:57:25:57:45 | new FileWriter(...) | PathCreation.java:57:40:57:44 | "dir" | +| PathCreation.java:61:25:61:45 | new FileReader(...) | PathCreation.java:61:40:61:44 | "dir" | +| PathCreation.java:65:32:65:58 | new FileOutputStream(...) | PathCreation.java:65:53:65:57 | "dir" | +| PathCreation.java:69:31:69:56 | new FileInputStream(...) | PathCreation.java:69:51:69:55 | "dir" |