Skip to content

Commit

Permalink
Merge branch 'main' into criemen/bzlmod-upgrades
Browse files Browse the repository at this point in the history
  • Loading branch information
criemen authored Sep 6, 2024
2 parents f76a190 + b73b868 commit c75f55d
Show file tree
Hide file tree
Showing 36 changed files with 1,193 additions and 6 deletions.
26 changes: 26 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/Brotli.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* https://github.com/google/brotli
*/

import cpp
import DecompressionBomb

/**
* The `BrotliDecoderDecompress` function is used in flow sink.
* See https://www.brotli.org/decode.html.
*/
class BrotliDecoderDecompressFunction extends DecompressionFunction {
BrotliDecoderDecompressFunction() { this.hasGlobalName("BrotliDecoderDecompress") }

override int getArchiveParameterIndex() { result = 1 }
}

/**
* The `BrotliDecoderDecompressStream` function is used in flow sink.
* See https://www.brotli.org/decode.html.
*/
class BrotliDecoderDecompressStreamFunction extends DecompressionFunction {
BrotliDecoderDecompressStreamFunction() { this.hasGlobalName("BrotliDecoderDecompressStream") }

override int getArchiveParameterIndex() { result = 2 }
}
26 changes: 26 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/DecompressionBomb.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import cpp
import semmle.code.cpp.ir.dataflow.TaintTracking
import MiniZip
import ZlibGzopen
import ZlibInflator
import ZlibUncompress
import LibArchive
import ZSTD
import Brotli

/**
* The Decompression Sink instances, extend this class to define new decompression sinks.
*/
abstract class DecompressionFunction extends Function {
abstract int getArchiveParameterIndex();
}

/**
* The Decompression Flow Steps, extend this class to define new decompression sinks.
*/
abstract class DecompressionFlowStep extends string {
bindingset[this]
DecompressionFlowStep() { any() }

abstract predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting Compressed files with any compression algorithm like gzip can cause denial of service attacks.</p>
<p>Attackers can compress a huge file consisting of repeated similiar bytes into a small compressed file.</p>
</overview>
<recommendation>

<p>When you want to decompress a user-provided compressed file you must be careful about the decompression ratio or read these files within a loop byte by byte to be able to manage the decompressed size in each cycle of the loop.</p>

</recommendation>
<example>

<p>
Reading an uncompressed Gzip file within a loop and check for a threshold size in each cycle.
</p>
<sample src="example_good.cpp"/>

<p>
The following example is unsafe, as we do not check the uncompressed size.
</p>
<sample src="example_bad.cpp" />

</example>

<references>

<li>
<a href="https://zlib.net/manual.html">Zlib documentation</a>
</li>

<li>
<a href="https://www.bamsoftware.com/hacks/zipbomb/">An explanation of the attack</a>
</li>

</references>
</qhelp>
40 changes: 40 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/DecompressionBombs.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @name User-controlled file decompression
* @description User-controlled data that flows into decompression library APIs without checking the compression rate is dangerous
* @kind path-problem
* @problem.severity error
* @precision low
* @id cpp/data-decompression-bomb
* @tags security
* experimental
* external/cwe/cwe-409
*/

import cpp
import semmle.code.cpp.security.FlowSources
import DecompressionBomb

predicate isSink(FunctionCall fc, DataFlow::Node sink) {
exists(DecompressionFunction f | fc.getTarget() = f |
fc.getArgument(f.getArchiveParameterIndex()) = [sink.asExpr(), sink.asIndirectExpr()]
)
}

module DecompressionTaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof FlowSource }

predicate isSink(DataFlow::Node sink) { isSink(_, sink) }

predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
any(DecompressionFlowStep s).isAdditionalFlowStep(node1, node2)
}
}

module DecompressionTaint = TaintTracking::Global<DecompressionTaintConfig>;

import DecompressionTaint::PathGraph

from DecompressionTaint::PathNode source, DecompressionTaint::PathNode sink, FunctionCall fc
where DecompressionTaint::flowPath(source, sink) and isSink(fc, sink.getNode())
select sink.getNode(), source, sink, "The decompression output of $@ is not limited", fc,
fc.getTarget().getName()
32 changes: 32 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/LibArchive.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* https://github.com/libarchive/libarchive/wiki
*/

import cpp
import DecompressionBomb

/**
* The `archive_read_data*` functions are used in flow sink.
* See https://github.com/libarchive/libarchive/wiki/Examples.
*/
class Archive_read_data_block extends DecompressionFunction {
Archive_read_data_block() {
this.hasGlobalName(["archive_read_data_block", "archive_read_data", "archive_read_data_into_fd"])
}

override int getArchiveParameterIndex() { result = 0 }
}

/**
* The `archive_read_open_filename` function as a flow step.
*/
class ReadOpenFunctionStep extends DecompressionFlowStep {
ReadOpenFunctionStep() { this = "ReadOpenFunction" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(FunctionCall fc | fc.getTarget().hasGlobalName("archive_read_open_filename") |
node1.asIndirectExpr() = fc.getArgument(1) and
node2.asIndirectExpr() = fc.getArgument(0)
)
}
}
56 changes: 56 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/MiniZip.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* https://github.com/zlib-ng/minizip-ng
*/

import cpp
import DecompressionBomb

/**
* The `mz_zip_entry` function is used in flow sink.
* See https://github.com/zlib-ng/minizip-ng/blob/master/doc/mz_zip.md.
*/
class Mz_zip_entry extends DecompressionFunction {
Mz_zip_entry() { this.hasGlobalName("mz_zip_entry_read") }

override int getArchiveParameterIndex() { result = 1 }
}

/**
* The `mz_zip_reader_entry_*` and `mz_zip_reader_save_all` functions are used in flow sink.
* See https://github.com/zlib-ng/minizip-ng/blob/master/doc/mz_zip_rw.md.
*/
class Mz_zip_reader_entry extends DecompressionFunction {
Mz_zip_reader_entry() {
this.hasGlobalName([
"mz_zip_reader_entry_save", "mz_zip_reader_entry_read", "mz_zip_reader_entry_save_process",
"mz_zip_reader_entry_save_file", "mz_zip_reader_entry_save_buffer", "mz_zip_reader_save_all"
])
}

override int getArchiveParameterIndex() { result = 0 }
}

/**
* The `UnzOpen*` functions are used in flow sink.
*/
class UnzOpenFunction extends DecompressionFunction {
UnzOpenFunction() { this.hasGlobalName(["UnzOpen", "unzOpen64", "unzOpen2", "unzOpen2_64"]) }

override int getArchiveParameterIndex() { result = 0 }
}

/**
* The `mz_zip_reader_open_file` and `mz_zip_reader_open_file_in_memory` functions as a flow step.
*/
class ReaderOpenFunctionStep extends DecompressionFlowStep {
ReaderOpenFunctionStep() { this = "ReaderOpenFunctionStep" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(FunctionCall fc |
fc.getTarget().hasGlobalName(["mz_zip_reader_open_file_in_memory", "mz_zip_reader_open_file"])
|
node1.asIndirectExpr() = fc.getArgument(1) and
node2.asIndirectExpr() = fc.getArgument(0)
)
}
}
88 changes: 88 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/ZSTD.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* https://github.com/facebook/zstd/blob/dev/examples/streaming_decompression.c
*/

import cpp
import DecompressionBomb

/**
* The `ZSTD_decompress` function is used in flow sink.
*/
class ZstdDecompressFunction extends DecompressionFunction {
ZstdDecompressFunction() { this.hasGlobalName("ZSTD_decompress") }

override int getArchiveParameterIndex() { result = 2 }
}

/**
* The `ZSTD_decompressDCtx` function is used in flow sink.
*/
class ZstdDecompressDctxFunction extends DecompressionFunction {
ZstdDecompressDctxFunction() { this.hasGlobalName("ZSTD_decompressDCtx") }

override int getArchiveParameterIndex() { result = 3 }
}

/**
* The `ZSTD_decompressStream` function is used in flow sink.
*/
class ZstdDecompressStreamFunction extends DecompressionFunction {
ZstdDecompressStreamFunction() { this.hasGlobalName("ZSTD_decompressStream") }

override int getArchiveParameterIndex() { result = 2 }
}

/**
* The `ZSTD_decompress_usingDDict` function is used in flow sink.
*/
class ZstdDecompressUsingDdictFunction extends DecompressionFunction {
ZstdDecompressUsingDdictFunction() { this.hasGlobalName("ZSTD_decompress_usingDDict") }

override int getArchiveParameterIndex() { result = 3 }
}

/**
* The `fopen_orDie` function as a flow step.
*/
class FopenOrDieFunctionStep extends DecompressionFlowStep {
FopenOrDieFunctionStep() { this = "FopenOrDieFunctionStep" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(FunctionCall fc | fc.getTarget().hasGlobalName("fopen_orDie") |
node1.asIndirectExpr() = fc.getArgument(0) and
node2.asExpr() = fc
)
}
}

/**
* The `fread_orDie` function as a flow step.
*/
class FreadOrDieFunctionStep extends DecompressionFlowStep {
FreadOrDieFunctionStep() { this = "FreadOrDieFunctionStep" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(FunctionCall fc | fc.getTarget().hasGlobalName("fread_orDie") |
node1.asExpr() = fc.getArgument(2) and
node2.asIndirectExpr() = fc.getArgument(0)
)
}
}

/**
* The `src` member of a `ZSTD_inBuffer` variable is used in a flow steps.
*/
class SrcMember extends DecompressionFlowStep {
SrcMember() { this = "SrcMember" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(VariableAccess inBufferAccess, Field srcField, ClassAggregateLiteral c |
inBufferAccess.getType().hasName("ZSTD_inBuffer") and
srcField.hasName("src")
|
node2.asExpr() = inBufferAccess and
inBufferAccess.getTarget().getInitializer().getExpr() = c and
node1.asIndirectExpr() = c.getFieldExpr(srcField, _)
)
}
}
71 changes: 71 additions & 0 deletions cpp/ql/src/experimental/Security/CWE/CWE-409/ZlibGzopen.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* https://www.zlib.net/
*/

import cpp
import DecompressionBomb

/**
* The `gzfread` function is used in flow sink.
*
* `gzfread(voidp buf, z_size_t size, z_size_t nitems, gzFile file)`
*/
class GzFreadFunction extends DecompressionFunction {
GzFreadFunction() { this.hasGlobalName("gzfread") }

override int getArchiveParameterIndex() { result = 3 }
}

/**
* The `gzgets` function is used in flow sink.
*
* `gzgets(gzFile file, char *buf, int len)`
*/
class GzGetsFunction extends DecompressionFunction {
GzGetsFunction() { this.hasGlobalName("gzgets") }

override int getArchiveParameterIndex() { result = 0 }
}

/**
* The `gzread` function is used in flow sink.
*
* `gzread(gzFile file, voidp buf, unsigned len)`
*/
class GzReadFunction extends DecompressionFunction {
GzReadFunction() { this.hasGlobalName("gzread") }

override int getArchiveParameterIndex() { result = 0 }
}

/**
* The `gzdopen` function is used in flow steps.
*
* `gzdopen(int fd, const char *mode)`
*/
class GzdopenFunctionStep extends DecompressionFlowStep {
GzdopenFunctionStep() { this = "GzdopenFunctionStep" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(FunctionCall fc | fc.getTarget().hasGlobalName("gzdopen") |
node1.asExpr() = fc.getArgument(0) and
node2.asExpr() = fc
)
}
}

/**
* The `gzopen` function is used in flow steps.
*
* `gzopen(const char *path, const char *mode)`
*/
class GzopenFunctionStep extends DecompressionFlowStep {
GzopenFunctionStep() { this = "GzopenFunctionStep" }

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(FunctionCall fc | fc.getTarget().hasGlobalName("gzopen") |
node1.asIndirectExpr() = fc.getArgument(0) and
node2.asExpr() = fc
)
}
}
Loading

0 comments on commit c75f55d

Please sign in to comment.