mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Merge branch 'main' into brodes/overflow-buffer-fixes-upstream
This commit is contained in:
26
cpp/ql/src/experimental/Security/CWE/CWE-409/Brotli.qll
Normal file
26
cpp/ql/src/experimental/Security/CWE/CWE-409/Brotli.qll
Normal file
@@ -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 }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
cpp/ql/src/experimental/Security/CWE/CWE-409/LibArchive.qll
Normal file
32
cpp/ql/src/experimental/Security/CWE/CWE-409/LibArchive.qll
Normal file
@@ -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
cpp/ql/src/experimental/Security/CWE/CWE-409/MiniZip.qll
Normal file
56
cpp/ql/src/experimental/Security/CWE/CWE-409/MiniZip.qll
Normal file
@@ -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
cpp/ql/src/experimental/Security/CWE/CWE-409/ZSTD.qll
Normal file
88
cpp/ql/src/experimental/Security/CWE/CWE-409/ZSTD.qll
Normal file
@@ -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
cpp/ql/src/experimental/Security/CWE/CWE-409/ZlibGzopen.qll
Normal file
71
cpp/ql/src/experimental/Security/CWE/CWE-409/ZlibGzopen.qll
Normal file
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* https://www.zlib.net/
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import DecompressionBomb
|
||||
|
||||
/**
|
||||
* The `inflate` and `inflateSync` functions are used in flow sink.
|
||||
*
|
||||
* `inflate(z_stream strm, int flush)`
|
||||
*
|
||||
* `inflateSync(z_stream strm)`
|
||||
*/
|
||||
class InflateFunction extends DecompressionFunction {
|
||||
InflateFunction() { this.hasGlobalName(["inflate", "inflateSync"]) }
|
||||
|
||||
override int getArchiveParameterIndex() { result = 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `next_in` member of a `z_stream` variable is used in a flow steps.
|
||||
*/
|
||||
class NextInMemberStep extends DecompressionFlowStep {
|
||||
NextInMemberStep() { this = "NextInMemberStep" }
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(Variable nextInVar |
|
||||
nextInVar.getDeclaringType().hasName("z_stream") and
|
||||
nextInVar.hasName("next_in")
|
||||
|
|
||||
node1.asIndirectExpr() = nextInVar.getAnAssignedValue() and
|
||||
node2.asExpr() =
|
||||
nextInVar.getAnAccess().getQualifier().(VariableAccess).getTarget().getAnAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* https://www.zlib.net/
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import DecompressionBomb
|
||||
|
||||
/**
|
||||
* The `uncompress`/`uncompress2` function is used in flow sink.
|
||||
*/
|
||||
class UncompressFunction extends DecompressionFunction {
|
||||
UncompressFunction() { this.hasGlobalName(["uncompress", "uncompress2"]) }
|
||||
|
||||
override int getArchiveParameterIndex() { result = 2 }
|
||||
}
|
||||
15
cpp/ql/src/experimental/Security/CWE/CWE-409/example_bad.cpp
Normal file
15
cpp/ql/src/experimental/Security/CWE/CWE-409/example_bad.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "zlib.h"
|
||||
|
||||
void UnsafeGzread(gzFile inFileZ) {
|
||||
const int BUFFER_SIZE = 8192;
|
||||
unsigned char unzipBuffer[BUFFER_SIZE];
|
||||
unsigned int unzippedBytes;
|
||||
while (true) {
|
||||
unzippedBytes = gzread(inFileZ, unzipBuffer, BUFFER_SIZE);
|
||||
if (unzippedBytes <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// process buffer
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "zlib.h"
|
||||
|
||||
void SafeGzread(gzFile inFileZ) {
|
||||
const int MAX_READ = 1024 * 1024 * 4;
|
||||
const int BUFFER_SIZE = 8192;
|
||||
unsigned char unzipBuffer[BUFFER_SIZE];
|
||||
unsigned int unzippedBytes;
|
||||
unsigned int totalRead = 0;
|
||||
while (true) {
|
||||
unzippedBytes = gzread(inFileZ, unzipBuffer, BUFFER_SIZE);
|
||||
totalRead += unzippedBytes;
|
||||
if (unzippedBytes <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (totalRead > MAX_READ) {
|
||||
// Possible decompression bomb, stop processing.
|
||||
break;
|
||||
} else {
|
||||
// process buffer
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user