diff --git a/java/ql/src/experimental/Security/CWE/CWE-522-DecompressionBombs/DecompressionBomb.ql b/java/ql/src/experimental/Security/CWE/CWE-522-DecompressionBombs/DecompressionBomb.ql index 4aa3afffcd7..eb058e4180a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-522-DecompressionBombs/DecompressionBomb.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-522-DecompressionBombs/DecompressionBomb.ql @@ -24,29 +24,53 @@ import java * Providing Decompression sinks and additional taint steps for `org.xerial.snappy` package */ module XserialSnappy { + /** + * A type that is responsible for `SnappyInputStream` Class + */ class TypeInputStream extends RefType { TypeInputStream() { this.getASupertype*().hasQualifiedName("org.xerial.snappy", "SnappyInputStream") } } + /** + * Gets `n1` and `n2` which `SnappyInputStream n2 = new SnappyInputStream(n2)` or + * `n1.read(n2)`, + * second one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value + */ predicate inputStreamAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(Call call | - ( - call.getCallee().getDeclaringType() instanceof TypeInputStream or - call.(MethodAccess).getReceiverType() instanceof TypeInputStream - ) and + // Constructors + call.getCallee().getDeclaringType() = any(TypeInputStream t) and call.getArgument(0) = n1.asExpr() and call = n2.asExpr() + or + // Method calls + call.(MethodAccess).getReceiverType() = any(TypeInputStream t) and + call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and + ( + call.getArgument(0) = n2.asExpr() and + call.getQualifier() = n1.asExpr() + or + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + ) ) } + /** + * The methods that read bytes and belong to `SnappyInputStream` Types + */ class ReadInputStreamCall extends MethodAccess { ReadInputStreamCall() { this.getReceiverType() instanceof TypeInputStream and this.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // look at Zip4j comments for this method @@ -58,6 +82,9 @@ module XserialSnappy { * Providing Decompression sinks and additional taint steps for `org.apache.commons.compress` package */ module ApacheCommons { + /** + * A type that is responsible for `ArchiveInputStream` Class + */ class TypeArchiveInputStream extends RefType { TypeArchiveInputStream() { this.getASupertype*() @@ -65,6 +92,9 @@ module ApacheCommons { } } + /** + * A type that is responsible for `CompressorInputStream` Class + */ class TypeCompressorInputStream extends RefType { TypeCompressorInputStream() { this.getASupertype*() @@ -72,7 +102,13 @@ module ApacheCommons { } } + /** + * Providing Decompression sinks and additional taint steps for `org.apache.commons.compress.compressors.*` Types + */ module Compressors { + /** + * The types that are responsible for specific compression format of `CompressorInputStream` Class + */ class TypeCompressors extends RefType { TypeCompressors() { this.getASupertype*() @@ -113,23 +149,44 @@ module ApacheCommons { } } + /** + * Gets `n1` and `n2` which `*CompressorInputStream n2 = new *CompressorInputStream(n2)` or + * `n2 = inputStream.read(n1)` or `n1.read(n2)`, + * second one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value + */ predicate inputStreamAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(Call call | - ( - call.getCallee().getDeclaringType() instanceof TypeCompressors or - call.(MethodAccess).getReceiverType() instanceof TypeCompressors - ) and + // Constructors + call.getCallee().getDeclaringType() = any(TypeCompressors t) and call.getArgument(0) = n1.asExpr() and call = n2.asExpr() + or + // Method calls + call.(MethodAccess).getReceiverType() = any(TypeCompressors t) and + call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and + ( + call.getArgument(0) = n2.asExpr() and + call.getQualifier() = n1.asExpr() + or + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + ) ) } + /** + * The methods that read bytes and belong to `*CompressorInputStream` Types + */ class ReadInputStreamCall extends MethodAccess { ReadInputStreamCall() { this.getReceiverType() instanceof TypeCompressors and this.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // look at Zip4j comments for this method @@ -137,7 +194,13 @@ module ApacheCommons { } } + /** + * Providing Decompression sinks and additional taint steps for Types from `org.apache.commons.compress.archivers.*` packages + */ module Archivers { + /** + * The types that are responsible for specific compression format of `ArchiveInputStream` Class + */ class TypeArchivers extends RefType { TypeArchivers() { this.getASupertype*() @@ -156,32 +219,43 @@ module ApacheCommons { } /** - *```java - * ZipArchiveInputStream n2 = new ZipArchiveInputStream(n1); - * ZipArchiveInputStream n = new ZipArchiveInputStream(inputStream); - * n2 = n.read(n1); - *``` + * Gets `n1` and `n2` which `*ArchiveInputStream n2 = new *ArchiveInputStream(n2)` or + * `n2 = inputStream.read(n2)` or `n1.read(n2)`, + * second one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value */ predicate inputStreamAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(Call call | + // Constructors + call.getCallee().getDeclaringType() = any(TypeArchivers t) and + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + or + // Method calls + call.(MethodAccess).getReceiverType() = any(TypeArchivers t) and + call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and ( - // constructors - call.getCallee().getDeclaringType() instanceof TypeArchivers + call.getArgument(0) = n2.asExpr() and + call.getQualifier() = n1.asExpr() or - // Method calls - call.(MethodAccess).getReceiverType() instanceof TypeArchivers - ) and - n1.asExpr() = call.getArgument(0) and - n2.asExpr() = call + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + ) ) } + /** + * The methods that read bytes and belong to `*ArchiveInputStream` Types + */ class ReadInputStreamCall extends MethodAccess { ReadInputStreamCall() { this.getReceiverType() instanceof TypeArchivers and this.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // look at Zip4j comments for this method @@ -189,7 +263,13 @@ module ApacheCommons { } } + /** + * Providing Decompression sinks and additional taint steps for `CompressorStreamFactory` and `ArchiveStreamFactory` Types + */ module Factory { + /** + * A type that is responsible for `ArchiveInputStream` Class + */ class TypeArchivers extends RefType { TypeArchivers() { this.getASupertype*() @@ -197,6 +277,9 @@ module ApacheCommons { } } + /** + * A type that is responsible for `CompressorStreamFactory` Class + */ class TypeCompressors extends RefType { TypeCompressors() { this.getASupertype*() @@ -205,29 +288,43 @@ module ApacheCommons { } /** - * ```java - *CompressorInputStream n2 = new CompressorStreamFactory().createCompressorInputStream(n1) - *ArchiveInputStream n2 = new ArchiveStreamFactory().createArchiveInputStream(n1) - * ``` + * Gets `n1` and `n2` which `CompressorInputStream n2 = new CompressorStreamFactory().createCompressorInputStream(n1)` + * or `ArchiveInputStream n2 = new ArchiveStreamFactory().createArchiveInputStream(n1)` or + * `n1.read(n2)`, + * second one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value */ predicate inputStreamAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(Call call | + // Constructors ( - // Constructors call.getCallee().getDeclaringType() = any(TypeCompressors t) or call.getCallee().getDeclaringType() = any(TypeArchivers t) - or - // Method calls + ) and + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + or + // Method calls + ( call.(MethodAccess).getReceiverType() = any(TypeArchiveInputStream t) or call.(MethodAccess).getReceiverType() = any(TypeCompressorInputStream t) ) and - n1.asExpr() = call.getArgument(0) and - n2.asExpr() = call + call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and + ( + call.getArgument(0) = n2.asExpr() and + call.getQualifier() = n1.asExpr() + or + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + ) ) } + /** + * The methods that read bytes and belong to `CompressorInputStream` or `ArchiveInputStream` Types + */ class ReadInputStreamCall extends MethodAccess { ReadInputStreamCall() { ( @@ -238,6 +335,9 @@ module ApacheCommons { this.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // look at Zip4j comments for this method @@ -250,6 +350,9 @@ module ApacheCommons { * Providing Decompression sinks and additional taint steps for `net.lingala.zip4j.io` package */ module Zip4j { + /** + * A type that is responsible for `ZipInputStream` Class + */ class TypeZipInputStream extends RefType { TypeZipInputStream() { this.hasQualifiedName("net.lingala.zip4j.io.inputstream", "ZipInputStream") @@ -257,10 +360,7 @@ module Zip4j { } /** - * ```java - * n = new net.lingala.zip4j.io.inputstream.ZipInputStream(inputStream); - * this = n.read(readBuffer); - * ``` + * The methods that read bytes and belong to `CompressorInputStream` or `ArchiveInputStream` Types */ class ReadInputStreamCall extends MethodAccess { ReadInputStreamCall() { @@ -268,6 +368,9 @@ module Zip4j { this.getMethod().hasName(["read", "readNBytes", "readAllBytes"]) } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // while ((readLen = zipInputStream.read(readBuffer)) != -1) { @@ -288,22 +391,27 @@ module Zip4j { } /** - * ```java - * n2 = new net.lingala.zip4j.io.inputstream.ZipInputStream(n1); - * // or - * n = new net.lingala.zip4j.io.inputstream.ZipInputStream(inputStream); - * n2 = n.Method(n1); - * ``` + * Gets `n1` and `n2` which `ZipInputStream n2 = new ZipInputStream(n1)` or `n2 = zipInputStream.read(n1)` or `n1.read(n2)`, + * second one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value */ predicate inputStreamAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(Call call | - ( - call.getCallee().getDeclaringType() instanceof TypeZipInputStream or - call.(MethodAccess).getReceiverType() instanceof TypeZipInputStream - ) and + // Constructors + call.getCallee().getDeclaringType() = any(TypeZipInputStream t) and + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + or + // Method calls + call.(MethodAccess).getReceiverType() = any(TypeZipInputStream t) and call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and - call.getArgument(0) = n2.asExpr() and - call.getQualifier() = n1.asExpr() + ( + call.getArgument(0) = n2.asExpr() and + call.getQualifier() = n1.asExpr() + or + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + ) ) } } @@ -312,6 +420,9 @@ module Zip4j { * Providing sinks that can be related to reading uncontrolled buffer and bytes for `org.apache.commons.io` package */ module CommonsIO { + /** + * The Access to Methods which work with byes and inputStreams and buffers + */ class IOUtils extends MethodAccess { IOUtils() { this.getMethod() @@ -328,6 +439,9 @@ module CommonsIO { * Providing Decompression sinks and additional taint steps for `java.util.zip` package */ module Zip { + /** + * The Types that are responsible for `ZipInputStream`, `GZIPInputStream`, `InflaterInputStream` Classes + */ class TypeInputStream extends RefType { TypeInputStream() { this.getASupertype*() @@ -336,44 +450,65 @@ module Zip { } } + /** + * The methods that read bytes and belong to `*InputStream` Types + */ class ReadInputStreamCall extends MethodAccess { ReadInputStreamCall() { this.getReceiverType() instanceof TypeInputStream and this.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // look at Zip4j comments for this method predicate isControlledRead() { none() } } - // *InputStream Izis = new *InputStream(inputStream) + /** + * Gets `n1` and `n2` which `*InputStream n2 = new *InputStream(n1)` or + * `n2 = data.read(n1, 0, BUFFER)` or `n1.read(n2, 0, BUFFER)`, + * second one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value + */ predicate inputStreamAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(Call call | - ( - call.getCallee().getDeclaringType() instanceof TypeInputStream or - call.(MethodAccess).getReceiverType() instanceof TypeInputStream - ) and + // Constructors + call.getCallee().getDeclaringType() = any(TypeInputStream t) and call.getArgument(0) = n1.asExpr() and call = n2.asExpr() + or + // Method calls + call.(MethodAccess).getReceiverType() = any(TypeInputStream t) and + call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and + ( + call.getArgument(0) = n2.asExpr() and + call.getQualifier() = n1.asExpr() + or + call.getArgument(0) = n1.asExpr() and + call = n2.asExpr() + ) ) } + /** + * A type that is responsible for `Inflater` Class + */ class TypeInflator extends RefType { TypeInflator() { this.hasQualifiedName("java.util.zip", "Inflater") } } - class Inflatorsource extends Call { - Inflatorsource() { - exists(Call c | c.getCallee().(Constructor).getDeclaringType() instanceof TypeInflator | - this = c - ) - } - } - + /** + * Gets `n1` and `n2` which `Inflater inflater_As_n2 = new Inflater(); inflater_As_n2 = inflater.setInput(n1)` or `n1.inflate(n2)` or + * `n2 = inflater.inflate(n1)`, + * third one is added because of sanitizer, we want to compare return value of each `read` or similar method + * that whether there is a flow to a comparison between total read of decompressed stream and a constant value + */ predicate inflatorAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { - // inflater.inflate(n2) + // n1.inflate(n2) exists(MethodAccess ma | ma.getReceiverType() instanceof TypeInflator and ma.getArgument(0) = n2.asExpr() and @@ -381,8 +516,16 @@ module Zip { ma.getCallee().hasName("inflate") ) or + // n2 = inflater.inflate(n1) + exists(MethodAccess ma | + ma.getReceiverType() instanceof TypeInflator and + ma = n2.asExpr() and + ma.getArgument(0) = n1.asExpr() and + ma.getCallee().hasName("inflate") + ) + or // Inflater inflater = new Inflater(); - // n2 = inflater.setInput(n1) + // inflater_As_n2 = inflater.setInput(n1) exists(MethodAccess ma | ma.getReceiverType() instanceof TypeInflator and n1.asExpr() = ma.getArgument(0) and @@ -391,32 +534,35 @@ module Zip { ) } + /** + * The methods that read bytes and belong to `Inflater` Type + */ class InflateCall extends MethodAccess { InflateCall() { this.getReceiverType() instanceof TypeInflator and this.getCallee().hasName("inflate") } + /** + * An Argument which responsible for the destination of decompressed bytes and is used as a sink + */ Expr getAWriteArgument() { result = this.getArgument(0) } // look at Zip4j comments for this method predicate isControlledRead() { none() } } + /** + * A type that is responsible for `ZipFile` Class + */ class TypeZipFile extends RefType { TypeZipFile() { this.hasQualifiedName("java.util.zip", "ZipFile") } } - class ZipFilesource extends Call { - ZipFilesource() { - exists(Call c | c.getCallee().(Constructor).getDeclaringType() instanceof TypeZipFile | - this = c - ) - } - } - - // ZipFile zipFileAsn2 = new ZipFile(n1); - // InputStream n2 = zipFile.getInputStream(n1); + /** + * Gets `n1` and `n2` which `ZipFile n2 = new ZipFile(n1);` or + * `InputStream n2 = zipFile.getInputStream(n1);` or `zipFile_As_n1.getInputStream(n2);` + */ predicate zipFileadditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { exists(MethodAccess ma | ma.getReceiverType() instanceof TypeZipFile and @@ -433,6 +579,50 @@ module Zip { } } +/** + * Providing InputStream and it subClasses that mostly related to Sinks of ZipFile Type, + * we can do + */ +module InputStream { + /** + * The Types that are responsible for `InputStream` Class and all classes that are child of InputStream Class + */ + class TypeInputStream extends RefType { + TypeInputStream() { this.getASupertype*().hasQualifiedName("java.io", "InputStream") } + } + + /** + * The methods that read bytes and belong to `InputStream` Type and all Types that are child of InputStream Type + */ + class Read extends MethodAccess { + Read() { + this.getReceiverType() instanceof TypeInputStream and + this.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) + } + } + + /** + * general additional taint steps for all inputStream and all Types that are child of inputStream + */ + predicate additionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { + exists(Call call | + // Method calls + call.(MethodAccess).getReceiverType() = any(InputStream::TypeInputStream t) and + call.getCallee().hasName(["read", "readNBytes", "readAllBytes"]) and + ( + // call.getArgument(0) = n2.asExpr() and + // call.getQualifier() = n1.asExpr() + // or + // call.getArgument(0) = n1.asExpr() and + // call = n2.asExpr() + // or + // TODO: only implement following + call.getQualifier() = n1.asExpr() and + call = n2.asExpr() + ) + ) + } +} module DecompressionBombsConfig implements DataFlow::StateConfigSig { class FlowState = DataFlow::FlowState; @@ -483,9 +673,6 @@ module DecompressionBombsConfig implements DataFlow::StateConfigSig { ) } - // predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - // inputStreamAdditionalTaintStep(nodeFrom, nodeTo) - // } predicate isAdditionalFlowStep( DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo ) {