Skip to content

Commit fa691f8

Browse files
authored
Harden Wasm bytecode parsing. (#304)
Reported by Chris Ertl from Google Security. Signed-off-by: Piotr Sikora <[email protected]>
1 parent 33b2e75 commit fa691f8

12 files changed

+136
-27
lines changed

.bazelrc

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,49 @@ build:clang --action_env=BAZEL_COMPILER=clang
88
build:clang --action_env=CC=clang
99
build:clang --action_env=CXX=clang++
1010

11+
# Common flags for Clang sanitizers.
12+
build:clang-xsan --config=clang
13+
build:clang-xsan --copt -O1
14+
build:clang-xsan --copt -fno-omit-frame-pointer
15+
build:clang-xsan --copt -fno-optimize-sibling-calls
16+
build:clang-xsan --copt -fno-sanitize-recover=all
17+
build:clang-xsan --linkopt -fsanitize-link-c++-runtime
18+
build:clang-xsan --linkopt -fuse-ld=lld
19+
build:clang-xsan --linkopt -rtlib=compiler-rt
20+
build:clang-xsan --linkopt --unwindlib=libgcc
21+
1122
# Use Clang compiler with Address and Undefined Behavior Sanitizers.
12-
build:clang-asan --config=clang
23+
build:clang-asan --config=clang-xsan
1324
build:clang-asan --copt -DADDRESS_SANITIZER=1
1425
build:clang-asan --copt -DUNDEFINED_SANITIZER=1
15-
build:clang-asan --copt -O1
16-
build:clang-asan --copt -fno-omit-frame-pointer
17-
build:clang-asan --copt -fno-optimize-sibling-calls
1826
build:clang-asan --copt -fsanitize=address,undefined
1927
build:clang-asan --copt -fsanitize-address-use-after-scope
2028
build:clang-asan --linkopt -fsanitize=address,undefined
2129
build:clang-asan --linkopt -fsanitize-address-use-after-scope
22-
build:clang-asan --linkopt -fsanitize-link-c++-runtime
23-
build:clang-asan --linkopt -fuse-ld=lld
2430
build:clang-asan --test_env=ASAN_OPTIONS=check_initialization_order=1:detect_stack_use_after_return=1:strict_init_order=1:strict_string_checks=1
25-
build:clang-asan --test_env=UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
31+
build:clang-asan --test_env=UBSAN_OPTIONS=print_stacktrace=1
2632
build:clang-asan --test_env=ASAN_SYMBOLIZER_PATH
2733

2834
# Use Clang compiler with Address and Undefined Behavior Sanitizers (strict version).
2935
build:clang-asan-strict --config=clang-asan
30-
build:clang-asan-strict --copt -fsanitize=integer
31-
build:clang-asan-strict --linkopt -fsanitize=integer
36+
build:clang-asan-strict --copt -fsanitize=integer,local-bounds,nullability
37+
build:clang-asan-strict --linkopt -fsanitize=integer,local-bounds,nullability
38+
39+
# Use Honggfuzz with Address and Undefined Behavior Sanitizers (strict version).
40+
build:clang-asan-honggfuzz --config=clang-asan-strict
41+
build:clang-asan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:honggfuzz
42+
build:clang-asan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz
43+
44+
# Use LibFuzzer with Address and Undefined Behavior Sanitizers (strict version).
45+
build:clang-asan-libfuzzer --config=clang-asan-strict
46+
build:clang-asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
47+
build:clang-asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
3248

3349
# Use Clang compiler with Thread Sanitizer.
34-
build:clang-tsan --config=clang
50+
build:clang-tsan --config=clang-xsan
3551
build:clang-tsan --copt -DTHREAD_SANITIZER=1
36-
build:clang-tsan --copt -O1
37-
build:clang-tsan --copt -fno-omit-frame-pointer
38-
build:clang-tsan --copt -fno-optimize-sibling-calls
3952
build:clang-tsan --copt -fsanitize=thread
4053
build:clang-tsan --linkopt -fsanitize=thread
41-
build:clang-tsan --linkopt -fuse-ld=lld
4254

4355
# Use Clang-Tidy tool.
4456
build:clang-tidy --aspects @bazel_clang_tidy//clang_tidy:clang_tidy.bzl%clang_tidy_aspect

.github/workflows/test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ jobs:
125125
os: windows-2019
126126
arch: x86_64
127127
action: test
128+
targets: -//test/fuzz/...
128129
- name: 'V8 on Linux/x86_64'
129130
engine: 'v8'
130131
repo: 'v8'
@@ -155,6 +156,7 @@ jobs:
155156
os: ubuntu-20.04
156157
arch: aarch64
157158
action: test
159+
targets: -//test/fuzz/...
158160
flags: --config=zig-cc-linux-aarch64 --@v8//bazel/config:v8_target_cpu=arm64
159161
deps: qemu-user-static libc6-arm64-cross
160162
cache: true
@@ -228,6 +230,7 @@ jobs:
228230
os: windows-2019
229231
arch: x86_64
230232
action: test
233+
targets: -//test/fuzz/...
231234
- name: 'WAVM on Linux/x86_64'
232235
engine: 'wavm'
233236
repo: 'com_github_wavm_wavm'
@@ -280,14 +283,15 @@ jobs:
280283
done
281284
282285
- name: Bazel build/test
286+
shell: bash
283287
run: >
284288
${{ matrix.run_under }}
285289
bazel ${{ matrix.action }}
286290
--verbose_failures
287291
--test_output=errors
288292
--define engine=${{ matrix.engine }}
289293
${{ matrix.flags }}
290-
//test/...
294+
-- //test/... ${{ matrix.targets }}
291295
292296
- name: Bazel build/test (signed Wasm module)
293297
if: ${{ matrix.engine != 'null' && !startsWith(matrix.os, 'windows') }}

bazel/dependencies.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
1717
load("@proxy_wasm_cpp_host//bazel/cargo/wasmsign:crates.bzl", "wasmsign_fetch_remote_crates")
1818
load("@proxy_wasm_cpp_host//bazel/cargo/wasmtime:crates.bzl", "wasmtime_fetch_remote_crates")
1919
load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
20+
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
21+
load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
2022
load("@rules_python//python:pip.bzl", "pip_install")
2123
load("@rules_rust//rust:repositories.bzl", "rust_repositories", "rust_repository_set")
2224

@@ -25,6 +27,9 @@ def proxy_wasm_cpp_host_dependencies():
2527

2628
rules_foreign_cc_dependencies()
2729

30+
rules_fuzzing_dependencies()
31+
rules_fuzzing_init()
32+
2833
rust_repositories()
2934
rust_repository_set(
3035
name = "rust_linux_x86_64",

bazel/repositories.bzl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ def proxy_wasm_cpp_host_repositories():
5555
url = "https://github.com/bazelbuild/rules_foreign_cc/archive/0.7.1.tar.gz",
5656
)
5757

58+
maybe(
59+
http_archive,
60+
name = "rules_fuzzing",
61+
sha256 = "23bb074064c6f488d12044934ab1b0631e8e6898d5cf2f6bde087adb01111573",
62+
strip_prefix = "rules_fuzzing-0.3.1",
63+
url = "https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.1.zip",
64+
)
65+
5866
maybe(
5967
http_archive,
6068
name = "rules_python",

src/bytecode_util.cc

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,18 @@ bool BytecodeUtil::getAbiVersion(std::string_view bytecode, proxy_wasm::AbiVersi
4848
return false;
4949
}
5050
if (section_type == 7 /* export section */) {
51+
const char *section_end = pos + section_len;
5152
uint32_t export_vector_size = 0;
52-
if (!parseVarint(pos, end, export_vector_size) || pos + export_vector_size > end) {
53+
if (!parseVarint(pos, section_end, export_vector_size) ||
54+
pos + export_vector_size > section_end) {
5355
return false;
5456
}
5557
// Search thourgh exports.
5658
for (uint32_t i = 0; i < export_vector_size; i++) {
5759
// Parse name of the export.
5860
uint32_t export_name_size = 0;
59-
if (!parseVarint(pos, end, export_name_size) || pos + export_name_size > end) {
61+
if (!parseVarint(pos, section_end, export_name_size) ||
62+
pos + export_name_size > section_end) {
6063
return false;
6164
}
6265
const auto *const name_begin = pos;
@@ -65,7 +68,7 @@ bool BytecodeUtil::getAbiVersion(std::string_view bytecode, proxy_wasm::AbiVersi
6568
return false;
6669
}
6770
// Check if it is a function type export
68-
if (*pos++ == 0x00) {
71+
if (*pos++ == 0x00 /* function */) {
6972
const std::string export_name = {name_begin, export_name_size};
7073
// Check the name of the function.
7174
if (export_name == "proxy_abi_version_0_1_0") {
@@ -114,24 +117,25 @@ bool BytecodeUtil::getCustomSection(std::string_view bytecode, std::string_view
114117
}
115118
if (section_type == 0) {
116119
// Custom section.
117-
const auto *const section_data_start = pos;
120+
const char *section_end = pos + section_len;
118121
uint32_t section_name_len = 0;
119-
if (!BytecodeUtil::parseVarint(pos, end, section_name_len) || pos + section_name_len > end) {
122+
if (!BytecodeUtil::parseVarint(pos, section_end, section_name_len) ||
123+
pos + section_name_len > section_end) {
120124
return false;
121125
}
122126
if (section_name_len == name.size() && ::memcmp(pos, name.data(), section_name_len) == 0) {
123127
pos += section_name_len;
124-
ret = {pos, static_cast<size_t>(section_data_start + section_len - pos)};
128+
ret = {pos, static_cast<size_t>(section_end - pos)};
125129
return true;
126130
}
127-
pos = section_data_start + section_len;
131+
pos = section_end;
128132
} else {
129133
// Skip other sections.
130134
pos += section_len;
131135
}
132136
}
133137
return true;
134-
};
138+
}
135139

136140
bool BytecodeUtil::getFunctionNameIndex(std::string_view bytecode,
137141
std::unordered_map<uint32_t, std::string> &ret) {
@@ -242,16 +246,32 @@ bool BytecodeUtil::getStrippedSource(std::string_view bytecode, std::string &ret
242246

243247
bool BytecodeUtil::parseVarint(const char *&pos, const char *end, uint32_t &ret) {
244248
uint32_t shift = 0;
249+
uint32_t total = 0;
250+
uint32_t v;
245251
char b;
246-
do {
252+
while (pos < end) {
247253
if (pos + 1 > end) {
254+
// overread
248255
return false;
249256
}
250257
b = *pos++;
251-
ret += (b & 0x7f) << shift;
258+
v = (b & 0x7f);
259+
if (shift == 28 && v > 3) {
260+
// overflow
261+
return false;
262+
}
263+
total += v << shift;
264+
if ((b & 0x80) == 0) {
265+
ret = total;
266+
return true;
267+
}
252268
shift += 7;
253-
} while ((b & 0x80) != 0);
254-
return ret != static_cast<uint32_t>(-1);
269+
if (shift > 28) {
270+
// overflow
271+
return false;
272+
}
273+
}
274+
return false;
255275
}
256276

257277
} // namespace proxy_wasm

test/fuzz/BUILD

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
2+
3+
licenses(["notice"]) # Apache 2
4+
5+
package(default_visibility = ["//visibility:public"])
6+
7+
filegroup(
8+
name = "corpus_bytecode",
9+
srcs = glob(["corpus_bytecode/**"]),
10+
)
11+
12+
cc_fuzz_test(
13+
name = "bytecode_util_fuzzer",
14+
srcs = ["bytecode_util_fuzzer.cc"],
15+
corpus = [":corpus_bytecode"],
16+
deps = [
17+
"//:lib",
18+
],
19+
)

test/fuzz/bytecode_util_fuzzer.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "include/proxy-wasm/bytecode_util.h"
16+
17+
#include <string>
18+
19+
namespace proxy_wasm {
20+
namespace {
21+
22+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
23+
auto bytecode = std::string_view(reinterpret_cast<const char *>(data), size);
24+
25+
AbiVersion version;
26+
BytecodeUtil::getAbiVersion(bytecode, version);
27+
28+
std::string_view custom_section;
29+
BytecodeUtil::getCustomSection(bytecode, "precompiled", custom_section);
30+
31+
std::string stripped_source;
32+
BytecodeUtil::getStrippedSource(bytecode, stripped_source);
33+
34+
std::unordered_map<uint32_t, std::string> function_names;
35+
BytecodeUtil::getFunctionNameIndex(bytecode, function_names);
36+
37+
return 0;
38+
}
39+
40+
} // namespace
41+
} // namespace proxy_wasm
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)