Skip to content

Commit 437b671

Browse files
author
Paolo Tranquilli
committed
Merge branch 'rust-experiment' into aibaars/rust-experiment
Also fixed conflicts and applied linting (can be done via `rust/lint.py` or `pre-commit` configuration).
2 parents 42b1112 + 38c25f9 commit 437b671

82 files changed

Lines changed: 1969 additions & 150 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/rust.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: "Rust"
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "rust/**"
7+
- "misc/bazel/**"
8+
- "misc/codegen/**"
9+
- "shared/**"
10+
- "MODULE.bazel"
11+
- .github/workflows/rust.yml
12+
- .github/actions/**
13+
- codeql-workspace.yml
14+
- "!**/*.md"
15+
- "!**/*.qhelp"
16+
branches:
17+
- rust-experiment
18+
- main
19+
- rc/*
20+
- codeql-cli-*
21+
22+
permissions:
23+
contents: read
24+
25+
jobs:
26+
rust-check:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@v4
31+
- name: Format
32+
working-directory: rust/extractor
33+
shell: bash
34+
run: |
35+
cargo fmt --check
36+
- name: Compilation
37+
working-directory: rust/extractor
38+
shell: bash
39+
run: cargo check
40+
- name: Clippy
41+
working-directory: rust/extractor
42+
shell: bash
43+
run: |
44+
cargo clippy --fix
45+
git diff --exit-code
46+
- name: Code generation
47+
shell: bash
48+
run: |
49+
bazel run //rust/codegen
50+
git add .
51+
git diff --exit-code HEAD

.pre-commit-config.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ repos:
5858

5959
- id: swift-codegen
6060
name: Run Swift checked in code generation
61-
files: ^swift/(schema.py$|codegen/|.*/generated/|ql/lib/(swift\.dbscheme$|codeql/swift/elements)|ql/\.generated.list)
61+
files: ^misc/codegen/|^swift/(schema.py$|codegen/|.*/generated/|ql/lib/(swift\.dbscheme$|codeql/swift/elements)|ql/\.generated.list)
6262
language: system
6363
entry: bazel run //swift/codegen -- --quiet
6464
pass_filenames: false
@@ -69,3 +69,17 @@ repos:
6969
language: system
7070
entry: bazel test //misc/codegen/test
7171
pass_filenames: false
72+
73+
- id: rust-codegen
74+
name: Run Rust checked in code generation
75+
files: ^misc/codegen/|^rust/(schema.py$|codegen/|.*/generated/|ql/lib/(rust\.dbscheme$|codeql/rust/elements)|\.generated.list)
76+
language: system
77+
entry: bazel run //rust/codegen -- --quiet
78+
pass_filenames: false
79+
80+
- id: rust-lint
81+
name: Run fmt and clippy on Rust code
82+
files: ^rust/extractor/(.*rs|Cargo.toml)$
83+
language: system
84+
entry: python3 rust/lint.py
85+
pass_filenames: false

MODULE.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ local_path_override(
1616

1717
bazel_dep(name = "platforms", version = "0.0.10")
1818
bazel_dep(name = "rules_go", version = "0.50.0")
19-
bazel_dep(name = "rules_pkg", version = "0.10.1")
19+
bazel_dep(name = "rules_pkg", version = "1.0.1")
2020
bazel_dep(name = "rules_nodejs", version = "6.2.0-codeql.1")
21-
bazel_dep(name = "rules_python", version = "0.32.2")
21+
bazel_dep(name = "rules_python", version = "0.35.0")
2222
bazel_dep(name = "bazel_skylib", version = "1.6.1")
2323
bazel_dep(name = "abseil-cpp", version = "20240116.0", repo_name = "absl")
2424
bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json")
@@ -27,7 +27,7 @@ bazel_dep(name = "rules_kotlin", version = "1.9.4-codeql.1")
2727
bazel_dep(name = "gazelle", version = "0.38.0")
2828
bazel_dep(name = "rules_dotnet", version = "0.15.1")
2929
bazel_dep(name = "googletest", version = "1.14.0.bcr.1")
30-
bazel_dep(name = "rules_rust", version = "0.49.1")
30+
bazel_dep(name = "rules_rust", version = "0.49.3")
3131

3232
bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True)
3333

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+
}

0 commit comments

Comments
 (0)