Skip to content

Commit 92bef16

Browse files
committed
Harden Wasm bytecode parsing.
Reported by Chris Ertl from Google Security. Signed-off-by: Piotr Sikora <[email protected]>
1 parent c3c1a8f commit 92bef16

11 files changed

+126
-26
lines changed

.bazelrc

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,48 @@ 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+
1121
# Use Clang compiler with Address and Undefined Behavior Sanitizers.
12-
build:clang-asan --config=clang
22+
build:clang-asan --config=clang-xsan
1323
build:clang-asan --copt -DADDRESS_SANITIZER=1
1424
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
1825
build:clang-asan --copt -fsanitize=address,undefined
1926
build:clang-asan --copt -fsanitize-address-use-after-scope
2027
build:clang-asan --linkopt -fsanitize=address,undefined
2128
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
2429
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
30+
build:clang-asan --test_env=UBSAN_OPTIONS=print_stacktrace=1
2631
build:clang-asan --test_env=ASAN_SYMBOLIZER_PATH
2732

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

3348
# Use Clang compiler with Thread Sanitizer.
34-
build:clang-tsan --config=clang
49+
build:clang-tsan --config=clang-xsan
3550
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
3951
build:clang-tsan --copt -fsanitize=thread
4052
build:clang-tsan --linkopt -fsanitize=thread
41-
build:clang-tsan --linkopt -fuse-ld=lld
4253

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

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/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
load("@proxy_wasm_cpp_host//bazel:select.bzl", "proxy_wasm_select_engine_null")
22
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
3+
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
34

45
licenses(["notice"]) # Apache 2
56

@@ -16,6 +17,20 @@ cc_test(
1617
],
1718
)
1819

20+
filegroup(
21+
name = "corpus_bytecode",
22+
srcs = glob(["corpus_bytecode/**"]),
23+
)
24+
25+
cc_fuzz_test(
26+
name = "bytecode_util_fuzzer",
27+
srcs = ["bytecode_util_fuzzer.cc"],
28+
corpus = [":corpus_bytecode"],
29+
deps = [
30+
"//:lib",
31+
],
32+
)
33+
1934
cc_test(
2035
name = "bytecode_util_test",
2136
srcs = ["bytecode_util_test.cc"],

test/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)