Skip to content

Commit c75f55d

Browse files
authored
Merge branch 'main' into criemen/bzlmod-upgrades
2 parents f76a190 + b73b868 commit c75f55d

36 files changed

+1193
-6
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* https://github.com/google/brotli
3+
*/
4+
5+
import cpp
6+
import DecompressionBomb
7+
8+
/**
9+
* The `BrotliDecoderDecompress` function is used in flow sink.
10+
* See https://www.brotli.org/decode.html.
11+
*/
12+
class BrotliDecoderDecompressFunction extends DecompressionFunction {
13+
BrotliDecoderDecompressFunction() { this.hasGlobalName("BrotliDecoderDecompress") }
14+
15+
override int getArchiveParameterIndex() { result = 1 }
16+
}
17+
18+
/**
19+
* The `BrotliDecoderDecompressStream` function is used in flow sink.
20+
* See https://www.brotli.org/decode.html.
21+
*/
22+
class BrotliDecoderDecompressStreamFunction extends DecompressionFunction {
23+
BrotliDecoderDecompressStreamFunction() { this.hasGlobalName("BrotliDecoderDecompressStream") }
24+
25+
override int getArchiveParameterIndex() { result = 2 }
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import cpp
2+
import semmle.code.cpp.ir.dataflow.TaintTracking
3+
import MiniZip
4+
import ZlibGzopen
5+
import ZlibInflator
6+
import ZlibUncompress
7+
import LibArchive
8+
import ZSTD
9+
import Brotli
10+
11+
/**
12+
* The Decompression Sink instances, extend this class to define new decompression sinks.
13+
*/
14+
abstract class DecompressionFunction extends Function {
15+
abstract int getArchiveParameterIndex();
16+
}
17+
18+
/**
19+
* The Decompression Flow Steps, extend this class to define new decompression sinks.
20+
*/
21+
abstract class DecompressionFlowStep extends string {
22+
bindingset[this]
23+
DecompressionFlowStep() { any() }
24+
25+
abstract predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2);
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Extracting Compressed files with any compression algorithm like gzip can cause denial of service attacks.</p>
7+
<p>Attackers can compress a huge file consisting of repeated similiar bytes into a small compressed file.</p>
8+
</overview>
9+
<recommendation>
10+
11+
<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>
12+
13+
</recommendation>
14+
<example>
15+
16+
<p>
17+
Reading an uncompressed Gzip file within a loop and check for a threshold size in each cycle.
18+
</p>
19+
<sample src="example_good.cpp"/>
20+
21+
<p>
22+
The following example is unsafe, as we do not check the uncompressed size.
23+
</p>
24+
<sample src="example_bad.cpp" />
25+
26+
</example>
27+
28+
<references>
29+
30+
<li>
31+
<a href="https://zlib.net/manual.html">Zlib documentation</a>
32+
</li>
33+
34+
<li>
35+
<a href="https://www.bamsoftware.com/hacks/zipbomb/">An explanation of the attack</a>
36+
</li>
37+
38+
</references>
39+
</qhelp>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @name User-controlled file decompression
3+
* @description User-controlled data that flows into decompression library APIs without checking the compression rate is dangerous
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision low
7+
* @id cpp/data-decompression-bomb
8+
* @tags security
9+
* experimental
10+
* external/cwe/cwe-409
11+
*/
12+
13+
import cpp
14+
import semmle.code.cpp.security.FlowSources
15+
import DecompressionBomb
16+
17+
predicate isSink(FunctionCall fc, DataFlow::Node sink) {
18+
exists(DecompressionFunction f | fc.getTarget() = f |
19+
fc.getArgument(f.getArchiveParameterIndex()) = [sink.asExpr(), sink.asIndirectExpr()]
20+
)
21+
}
22+
23+
module DecompressionTaintConfig implements DataFlow::ConfigSig {
24+
predicate isSource(DataFlow::Node source) { source instanceof FlowSource }
25+
26+
predicate isSink(DataFlow::Node sink) { isSink(_, sink) }
27+
28+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
29+
any(DecompressionFlowStep s).isAdditionalFlowStep(node1, node2)
30+
}
31+
}
32+
33+
module DecompressionTaint = TaintTracking::Global<DecompressionTaintConfig>;
34+
35+
import DecompressionTaint::PathGraph
36+
37+
from DecompressionTaint::PathNode source, DecompressionTaint::PathNode sink, FunctionCall fc
38+
where DecompressionTaint::flowPath(source, sink) and isSink(fc, sink.getNode())
39+
select sink.getNode(), source, sink, "The decompression output of $@ is not limited", fc,
40+
fc.getTarget().getName()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* https://github.com/libarchive/libarchive/wiki
3+
*/
4+
5+
import cpp
6+
import DecompressionBomb
7+
8+
/**
9+
* The `archive_read_data*` functions are used in flow sink.
10+
* See https://github.com/libarchive/libarchive/wiki/Examples.
11+
*/
12+
class Archive_read_data_block extends DecompressionFunction {
13+
Archive_read_data_block() {
14+
this.hasGlobalName(["archive_read_data_block", "archive_read_data", "archive_read_data_into_fd"])
15+
}
16+
17+
override int getArchiveParameterIndex() { result = 0 }
18+
}
19+
20+
/**
21+
* The `archive_read_open_filename` function as a flow step.
22+
*/
23+
class ReadOpenFunctionStep extends DecompressionFlowStep {
24+
ReadOpenFunctionStep() { this = "ReadOpenFunction" }
25+
26+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
27+
exists(FunctionCall fc | fc.getTarget().hasGlobalName("archive_read_open_filename") |
28+
node1.asIndirectExpr() = fc.getArgument(1) and
29+
node2.asIndirectExpr() = fc.getArgument(0)
30+
)
31+
}
32+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* https://github.com/zlib-ng/minizip-ng
3+
*/
4+
5+
import cpp
6+
import DecompressionBomb
7+
8+
/**
9+
* The `mz_zip_entry` function is used in flow sink.
10+
* See https://github.com/zlib-ng/minizip-ng/blob/master/doc/mz_zip.md.
11+
*/
12+
class Mz_zip_entry extends DecompressionFunction {
13+
Mz_zip_entry() { this.hasGlobalName("mz_zip_entry_read") }
14+
15+
override int getArchiveParameterIndex() { result = 1 }
16+
}
17+
18+
/**
19+
* The `mz_zip_reader_entry_*` and `mz_zip_reader_save_all` functions are used in flow sink.
20+
* See https://github.com/zlib-ng/minizip-ng/blob/master/doc/mz_zip_rw.md.
21+
*/
22+
class Mz_zip_reader_entry extends DecompressionFunction {
23+
Mz_zip_reader_entry() {
24+
this.hasGlobalName([
25+
"mz_zip_reader_entry_save", "mz_zip_reader_entry_read", "mz_zip_reader_entry_save_process",
26+
"mz_zip_reader_entry_save_file", "mz_zip_reader_entry_save_buffer", "mz_zip_reader_save_all"
27+
])
28+
}
29+
30+
override int getArchiveParameterIndex() { result = 0 }
31+
}
32+
33+
/**
34+
* The `UnzOpen*` functions are used in flow sink.
35+
*/
36+
class UnzOpenFunction extends DecompressionFunction {
37+
UnzOpenFunction() { this.hasGlobalName(["UnzOpen", "unzOpen64", "unzOpen2", "unzOpen2_64"]) }
38+
39+
override int getArchiveParameterIndex() { result = 0 }
40+
}
41+
42+
/**
43+
* The `mz_zip_reader_open_file` and `mz_zip_reader_open_file_in_memory` functions as a flow step.
44+
*/
45+
class ReaderOpenFunctionStep extends DecompressionFlowStep {
46+
ReaderOpenFunctionStep() { this = "ReaderOpenFunctionStep" }
47+
48+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
49+
exists(FunctionCall fc |
50+
fc.getTarget().hasGlobalName(["mz_zip_reader_open_file_in_memory", "mz_zip_reader_open_file"])
51+
|
52+
node1.asIndirectExpr() = fc.getArgument(1) and
53+
node2.asIndirectExpr() = fc.getArgument(0)
54+
)
55+
}
56+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* https://github.com/facebook/zstd/blob/dev/examples/streaming_decompression.c
3+
*/
4+
5+
import cpp
6+
import DecompressionBomb
7+
8+
/**
9+
* The `ZSTD_decompress` function is used in flow sink.
10+
*/
11+
class ZstdDecompressFunction extends DecompressionFunction {
12+
ZstdDecompressFunction() { this.hasGlobalName("ZSTD_decompress") }
13+
14+
override int getArchiveParameterIndex() { result = 2 }
15+
}
16+
17+
/**
18+
* The `ZSTD_decompressDCtx` function is used in flow sink.
19+
*/
20+
class ZstdDecompressDctxFunction extends DecompressionFunction {
21+
ZstdDecompressDctxFunction() { this.hasGlobalName("ZSTD_decompressDCtx") }
22+
23+
override int getArchiveParameterIndex() { result = 3 }
24+
}
25+
26+
/**
27+
* The `ZSTD_decompressStream` function is used in flow sink.
28+
*/
29+
class ZstdDecompressStreamFunction extends DecompressionFunction {
30+
ZstdDecompressStreamFunction() { this.hasGlobalName("ZSTD_decompressStream") }
31+
32+
override int getArchiveParameterIndex() { result = 2 }
33+
}
34+
35+
/**
36+
* The `ZSTD_decompress_usingDDict` function is used in flow sink.
37+
*/
38+
class ZstdDecompressUsingDdictFunction extends DecompressionFunction {
39+
ZstdDecompressUsingDdictFunction() { this.hasGlobalName("ZSTD_decompress_usingDDict") }
40+
41+
override int getArchiveParameterIndex() { result = 3 }
42+
}
43+
44+
/**
45+
* The `fopen_orDie` function as a flow step.
46+
*/
47+
class FopenOrDieFunctionStep extends DecompressionFlowStep {
48+
FopenOrDieFunctionStep() { this = "FopenOrDieFunctionStep" }
49+
50+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
51+
exists(FunctionCall fc | fc.getTarget().hasGlobalName("fopen_orDie") |
52+
node1.asIndirectExpr() = fc.getArgument(0) and
53+
node2.asExpr() = fc
54+
)
55+
}
56+
}
57+
58+
/**
59+
* The `fread_orDie` function as a flow step.
60+
*/
61+
class FreadOrDieFunctionStep extends DecompressionFlowStep {
62+
FreadOrDieFunctionStep() { this = "FreadOrDieFunctionStep" }
63+
64+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
65+
exists(FunctionCall fc | fc.getTarget().hasGlobalName("fread_orDie") |
66+
node1.asExpr() = fc.getArgument(2) and
67+
node2.asIndirectExpr() = fc.getArgument(0)
68+
)
69+
}
70+
}
71+
72+
/**
73+
* The `src` member of a `ZSTD_inBuffer` variable is used in a flow steps.
74+
*/
75+
class SrcMember extends DecompressionFlowStep {
76+
SrcMember() { this = "SrcMember" }
77+
78+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
79+
exists(VariableAccess inBufferAccess, Field srcField, ClassAggregateLiteral c |
80+
inBufferAccess.getType().hasName("ZSTD_inBuffer") and
81+
srcField.hasName("src")
82+
|
83+
node2.asExpr() = inBufferAccess and
84+
inBufferAccess.getTarget().getInitializer().getExpr() = c and
85+
node1.asIndirectExpr() = c.getFieldExpr(srcField, _)
86+
)
87+
}
88+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* https://www.zlib.net/
3+
*/
4+
5+
import cpp
6+
import DecompressionBomb
7+
8+
/**
9+
* The `gzfread` function is used in flow sink.
10+
*
11+
* `gzfread(voidp buf, z_size_t size, z_size_t nitems, gzFile file)`
12+
*/
13+
class GzFreadFunction extends DecompressionFunction {
14+
GzFreadFunction() { this.hasGlobalName("gzfread") }
15+
16+
override int getArchiveParameterIndex() { result = 3 }
17+
}
18+
19+
/**
20+
* The `gzgets` function is used in flow sink.
21+
*
22+
* `gzgets(gzFile file, char *buf, int len)`
23+
*/
24+
class GzGetsFunction extends DecompressionFunction {
25+
GzGetsFunction() { this.hasGlobalName("gzgets") }
26+
27+
override int getArchiveParameterIndex() { result = 0 }
28+
}
29+
30+
/**
31+
* The `gzread` function is used in flow sink.
32+
*
33+
* `gzread(gzFile file, voidp buf, unsigned len)`
34+
*/
35+
class GzReadFunction extends DecompressionFunction {
36+
GzReadFunction() { this.hasGlobalName("gzread") }
37+
38+
override int getArchiveParameterIndex() { result = 0 }
39+
}
40+
41+
/**
42+
* The `gzdopen` function is used in flow steps.
43+
*
44+
* `gzdopen(int fd, const char *mode)`
45+
*/
46+
class GzdopenFunctionStep extends DecompressionFlowStep {
47+
GzdopenFunctionStep() { this = "GzdopenFunctionStep" }
48+
49+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
50+
exists(FunctionCall fc | fc.getTarget().hasGlobalName("gzdopen") |
51+
node1.asExpr() = fc.getArgument(0) and
52+
node2.asExpr() = fc
53+
)
54+
}
55+
}
56+
57+
/**
58+
* The `gzopen` function is used in flow steps.
59+
*
60+
* `gzopen(const char *path, const char *mode)`
61+
*/
62+
class GzopenFunctionStep extends DecompressionFlowStep {
63+
GzopenFunctionStep() { this = "GzopenFunctionStep" }
64+
65+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
66+
exists(FunctionCall fc | fc.getTarget().hasGlobalName("gzopen") |
67+
node1.asIndirectExpr() = fc.getArgument(0) and
68+
node2.asExpr() = fc
69+
)
70+
}
71+
}

0 commit comments

Comments
 (0)