diff --git a/BUILD b/BUILD index 67d8d89df..46edf1d1c 100644 --- a/BUILD +++ b/BUILD @@ -52,6 +52,7 @@ cc_library( "src/bytecode_util.cc", "src/context.cc", "src/exports.cc", + "src/pairs_util.cc", "src/shared_data.cc", "src/shared_data.h", "src/shared_queue.cc", @@ -62,6 +63,7 @@ cc_library( ], hdrs = [ "include/proxy-wasm/bytecode_util.h", + "include/proxy-wasm/pairs_util.h", "include/proxy-wasm/signature_util.h", ], linkopts = select({ diff --git a/include/proxy-wasm/exports.h b/include/proxy-wasm/exports.h index 325d28ff3..376a4d3b6 100644 --- a/include/proxy-wasm/exports.h +++ b/include/proxy-wasm/exports.h @@ -62,36 +62,6 @@ struct RegisterForeignFunction { namespace exports { -template size_t pairsSize(const Pairs &result) { - size_t size = 4; // number of headers - for (auto &p : result) { - size += 8; // size of key, size of value - size += p.first.size() + 1; // null terminated key - size += p.second.size() + 1; // null terminated value - } - return size; -} - -template void marshalPairs(const Pairs &result, char *buffer) { - char *b = buffer; - *reinterpret_cast(b) = htowasm(result.size()); - b += sizeof(uint32_t); - for (auto &p : result) { - *reinterpret_cast(b) = htowasm(p.first.size()); - b += sizeof(uint32_t); - *reinterpret_cast(b) = htowasm(p.second.size()); - b += sizeof(uint32_t); - } - for (auto &p : result) { - memcpy(b, p.first.data(), p.first.size()); - b += p.first.size(); - *b++ = 0; - memcpy(b, p.second.data(), p.second.size()); - b += p.second.size(); - *b++ = 0; - } -} - // ABI functions exported from host to wasm. Word get_configuration(Word value_ptr_ptr, Word value_size_ptr); diff --git a/include/proxy-wasm/limits.h b/include/proxy-wasm/limits.h index d02d4c5e3..9b9ac1cd3 100644 --- a/include/proxy-wasm/limits.h +++ b/include/proxy-wasm/limits.h @@ -30,3 +30,13 @@ #ifndef PROXY_WASM_HOST_WASI_RANDOM_GET_MAX_SIZE_BYTES #define PROXY_WASM_HOST_WASI_RANDOM_GET_MAX_SIZE_BYTES (64 * 1024) #endif + +// Maximum allowed size of Pairs buffer to deserialize. +#ifndef PROXY_WASM_HOST_PAIRS_MAX_BYTES +#define PROXY_WASM_HOST_PAIRS_MAX_BYTES (1024 * 1024) +#endif + +// Maximum allowed number of pairs in a Pairs buffer to deserialize. +#ifndef PROXY_WASM_HOST_PAIRS_MAX_COUNT +#define PROXY_WASM_HOST_PAIRS_MAX_COUNT 1024 +#endif diff --git a/include/proxy-wasm/pairs_util.h b/include/proxy-wasm/pairs_util.h new file mode 100644 index 000000000..54df2fd88 --- /dev/null +++ b/include/proxy-wasm/pairs_util.h @@ -0,0 +1,63 @@ +// Copyright 2016-2019 Envoy Project Authors +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +namespace proxy_wasm { + +using Pairs = std::vector>; +using StringPairs = std::vector>; + +class PairsUtil { +public: + /** + * pairsSize returns the buffer size required to serialize Pairs. + * @param pairs Pairs to serialize. + * @return size of the output buffer. + */ + static size_t pairsSize(const Pairs &pairs); + + static size_t pairsSize(const StringPairs &stringpairs) { + Pairs views(stringpairs.begin(), stringpairs.end()); + return pairsSize(views); + } + + /** + * marshalPairs serializes Pairs to output buffer. + * @param pairs Pairs to serialize. + * @param buffer output buffer. + * @param size size of the output buffer. + * @return indicates whether serialization succeeded or not. + */ + static bool marshalPairs(const Pairs &pairs, char *buffer, size_t size); + + static bool marshalPairs(const StringPairs &stringpairs, char *buffer, size_t size) { + Pairs views(stringpairs.begin(), stringpairs.end()); + return marshalPairs(views, buffer, size); + } + + /** + * toPairs deserializes input buffer to Pairs. + * @param buffer serialized input buffer. + * @return deserialized Pairs or an empty instance in case of deserialization failure. + */ + static Pairs toPairs(std::string_view buffer); +}; + +} // namespace proxy_wasm diff --git a/src/exports.cc b/src/exports.cc index 837f9e1e0..b06c40437 100644 --- a/src/exports.cc +++ b/src/exports.cc @@ -14,6 +14,7 @@ // limitations under the License. // #include "include/proxy-wasm/limits.h" +#include "include/proxy-wasm/pairs_util.h" #include "include/proxy-wasm/wasm.h" #include @@ -58,55 +59,6 @@ RegisterForeignFunction::RegisterForeignFunction(const std::string &name, WasmFo namespace exports { -namespace { - -Pairs toPairs(std::string_view buffer) { - Pairs result; - const char *b = buffer.data(); - if (buffer.size() < sizeof(uint32_t)) { - return {}; - } - auto size = wasmtoh(*reinterpret_cast(b)); - b += sizeof(uint32_t); - if (sizeof(uint32_t) + size * 2 * sizeof(uint32_t) > buffer.size()) { - return {}; - } - result.resize(size); - for (uint32_t i = 0; i < size; i++) { - result[i].first = std::string_view(nullptr, wasmtoh(*reinterpret_cast(b))); - b += sizeof(uint32_t); - result[i].second = std::string_view(nullptr, wasmtoh(*reinterpret_cast(b))); - b += sizeof(uint32_t); - } - for (auto &p : result) { - p.first = std::string_view(b, p.first.size()); - b += p.first.size() + 1; - p.second = std::string_view(b, p.second.size()); - b += p.second.size() + 1; - } - return result; -} - -template -bool getPairs(ContextBase *context, const Pairs &result, uint64_t ptr_ptr, uint64_t size_ptr) { - if (result.empty()) { - return context->wasm()->copyToPointerSize("", ptr_ptr, size_ptr); - } - uint64_t size = pairsSize(result); - uint64_t ptr = 0; - char *buffer = static_cast(context->wasm()->allocMemory(size, &ptr)); - marshalPairs(result, buffer); - if (!context->wasmVm()->setWord(ptr_ptr, Word(ptr))) { - return false; - } - if (!context->wasmVm()->setWord(size_ptr, Word(size))) { - return false; - } - return true; -} - -} // namespace - // General ABI. Word set_property(Word key_ptr, Word key_size, Word value_ptr, Word value_size) { @@ -200,7 +152,7 @@ Word send_local_response(Word response_code, Word response_code_details_ptr, if (!details || !body || !additional_response_header_pairs) { return WasmResult::InvalidMemoryAccess; } - auto additional_headers = toPairs(additional_response_header_pairs.value()); + auto additional_headers = PairsUtil::toPairs(additional_response_header_pairs.value()); context->sendLocalResponse(response_code, body.value(), std::move(additional_headers), grpc_status, details.value()); context->wasm()->stopNextIteration(true); @@ -435,7 +387,25 @@ Word get_header_map_pairs(Word type, Word ptr_ptr, Word size_ptr) { if (result != WasmResult::Ok) { return result; } - if (!getPairs(context, pairs, ptr_ptr, size_ptr)) { + if (pairs.empty()) { + if (!context->wasm()->copyToPointerSize("", ptr_ptr, size_ptr)) { + return WasmResult::InvalidMemoryAccess; + } + return WasmResult::Ok; + } + uint64_t size = PairsUtil::pairsSize(pairs); + uint64_t ptr = 0; + char *buffer = static_cast(context->wasm()->allocMemory(size, &ptr)); + if (buffer == nullptr) { + return WasmResult::InvalidMemoryAccess; + } + if (!PairsUtil::marshalPairs(pairs, buffer, size)) { + return WasmResult::InvalidMemoryAccess; + } + if (!context->wasmVm()->setWord(ptr_ptr, Word(ptr))) { + return WasmResult::InvalidMemoryAccess; + } + if (!context->wasmVm()->setWord(size_ptr, Word(size))) { return WasmResult::InvalidMemoryAccess; } return WasmResult::Ok; @@ -451,7 +421,7 @@ Word set_header_map_pairs(Word type, Word ptr, Word size) { return WasmResult::InvalidMemoryAccess; } return context->setHeaderMapPairs(static_cast(type.u64_), - toPairs(data.value())); + PairsUtil::toPairs(data.value())); } Word get_header_map_size(Word type, Word result_ptr) { @@ -549,8 +519,8 @@ Word http_call(Word uri_ptr, Word uri_size, Word header_pairs_ptr, Word header_p if (!uri || !body || !header_pairs || !trailer_pairs) { return WasmResult::InvalidMemoryAccess; } - auto headers = toPairs(header_pairs.value()); - auto trailers = toPairs(trailer_pairs.value()); + auto headers = PairsUtil::toPairs(header_pairs.value()); + auto trailers = PairsUtil::toPairs(trailer_pairs.value()); uint32_t token = 0; // NB: try to write the token to verify the memory before starting the async // operation. @@ -619,7 +589,7 @@ Word grpc_call(Word service_ptr, Word service_size, Word service_name_ptr, Word return WasmResult::InvalidMemoryAccess; } uint32_t token = 0; - auto initial_metadata = toPairs(initial_metadata_pairs.value()); + auto initial_metadata = PairsUtil::toPairs(initial_metadata_pairs.value()); auto result = context->grpcCall(service.value(), service_name.value(), method_name.value(), initial_metadata, request.value(), std::chrono::milliseconds(timeout_milliseconds), &token); @@ -645,7 +615,7 @@ Word grpc_stream(Word service_ptr, Word service_size, Word service_name_ptr, Wor return WasmResult::InvalidMemoryAccess; } uint32_t token = 0; - auto initial_metadata = toPairs(initial_metadata_pairs.value()); + auto initial_metadata = PairsUtil::toPairs(initial_metadata_pairs.value()); auto result = context->grpcStream(service.value(), service_name.value(), method_name.value(), initial_metadata, &token); if (result != WasmResult::Ok) { diff --git a/src/pairs_util.cc b/src/pairs_util.cc new file mode 100644 index 000000000..d49259216 --- /dev/null +++ b/src/pairs_util.cc @@ -0,0 +1,171 @@ +// Copyright 2016-2019 Envoy Project Authors +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "include/proxy-wasm/pairs_util.h" + +#include +#include +#include + +#include "include/proxy-wasm/limits.h" +#include "include/proxy-wasm/word.h" + +namespace proxy_wasm { + +using Sizes = std::vector>; + +size_t PairsUtil::pairsSize(const Pairs &pairs) { + size_t size = sizeof(uint32_t); // number of headers + for (const auto &p : pairs) { + size += 2 * sizeof(uint32_t); // size of name, size of value + size += p.first.size() + 1; // NULL-terminated name + size += p.second.size() + 1; // NULL-terminated value + } + return size; +} + +bool PairsUtil::marshalPairs(const Pairs &pairs, char *buffer, size_t size) { + if (buffer == nullptr) { + return false; + } + + char *pos = buffer; + const char *end = buffer + size; + + // Write number of pairs. + uint32_t num_pairs = htowasm(pairs.size()); + if (pos + sizeof(uint32_t) > end) { + return false; + } + ::memcpy(pos, &num_pairs, sizeof(uint32_t)); + pos += sizeof(uint32_t); + + for (const auto &p : pairs) { + // Write name length. + uint32_t name_len = htowasm(p.first.size()); + if (pos + sizeof(uint32_t) > end) { + return false; + } + ::memcpy(pos, &name_len, sizeof(uint32_t)); + pos += sizeof(uint32_t); + + // Write value length. + uint32_t value_len = htowasm(p.second.size()); + if (pos + sizeof(uint32_t) > end) { + return false; + } + ::memcpy(pos, &value_len, sizeof(uint32_t)); + pos += sizeof(uint32_t); + } + + for (const auto &p : pairs) { + // Write name. + if (pos + p.first.size() + 1 > end) { + return false; + } + ::memcpy(pos, p.first.data(), p.first.size()); + pos += p.first.size(); + *pos++ = '\0'; // NULL-terminated string. + + // Write value. + if (pos + p.second.size() + 1 > end) { + return false; + } + ::memcpy(pos, p.second.data(), p.second.size()); + pos += p.second.size(); + *pos++ = '\0'; // NULL-terminated string. + } + + return pos == end; +} + +Pairs PairsUtil::toPairs(std::string_view buffer) { + if (buffer.data() == nullptr || buffer.size() > PROXY_WASM_HOST_PAIRS_MAX_BYTES) { + return {}; + } + + const char *pos = buffer.data(); + const char *end = buffer.data() + buffer.size(); + + // Read number of pairs. + if (pos + sizeof(uint32_t) > end) { + return {}; + } + uint32_t num_pairs = wasmtoh(*reinterpret_cast(pos)); + pos += sizeof(uint32_t); + + // Check if we're not going to exceed the limit. + if (num_pairs > PROXY_WASM_HOST_PAIRS_MAX_COUNT) { + return {}; + } + if (pos + num_pairs * 2 * sizeof(uint32_t) > end) { + return {}; + } + + Sizes sizes; + sizes.resize(num_pairs); + + for (auto &s : sizes) { + // Read name length. + if (pos + sizeof(uint32_t) > end) { + return {}; + } + s.first = wasmtoh(*reinterpret_cast(pos)); + pos += sizeof(uint32_t); + + // Read value length. + if (pos + sizeof(uint32_t) > end) { + return {}; + } + s.second = wasmtoh(*reinterpret_cast(pos)); + pos += sizeof(uint32_t); + } + + Pairs pairs; + pairs.resize(num_pairs); + + for (uint32_t i = 0; i < num_pairs; i++) { + auto &s = sizes[i]; + auto &p = pairs[i]; + + // Don't overread. + if (pos + s.first + 1 > end) { + return {}; + } + p.first = std::string_view(pos, s.first); + pos += s.first; + if (*pos++ != '\0') { // NULL-terminated string. + return {}; + } + + // Don't overread. + if (pos + s.second + 1 > end) { + return {}; + } + p.second = std::string_view(pos, s.second); + pos += s.second; + if (*pos++ != '\0') { // NULL-terminated string. + return {}; + } + } + + if (pos != end) { + return {}; + } + + return pairs; +} + +} // namespace proxy_wasm diff --git a/test/fuzz/BUILD b/test/fuzz/BUILD index 9e9ca0c78..4c069bcf1 100644 --- a/test/fuzz/BUILD +++ b/test/fuzz/BUILD @@ -17,3 +17,17 @@ cc_fuzz_test( "//:lib", ], ) + +filegroup( + name = "corpus_pairs", + srcs = glob(["corpus_pairs/**"]), +) + +cc_fuzz_test( + name = "pairs_util_fuzzer", + srcs = ["pairs_util_fuzzer.cc"], + corpus = [":corpus_pairs"], + deps = [ + "//:lib", + ], +) diff --git a/test/fuzz/corpus_pairs/crash-0bf4371e72f7ae83f9c5bf3e2485531cdcaaaa04 b/test/fuzz/corpus_pairs/crash-0bf4371e72f7ae83f9c5bf3e2485531cdcaaaa04 new file mode 100644 index 000000000..7be804a26 Binary files /dev/null and b/test/fuzz/corpus_pairs/crash-0bf4371e72f7ae83f9c5bf3e2485531cdcaaaa04 differ diff --git a/test/fuzz/pairs_util_fuzzer.cc b/test/fuzz/pairs_util_fuzzer.cc new file mode 100644 index 000000000..aaef0d099 --- /dev/null +++ b/test/fuzz/pairs_util_fuzzer.cc @@ -0,0 +1,46 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "include/proxy-wasm/pairs_util.h" + +#include + +namespace proxy_wasm { +namespace { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + auto input = std::string_view(reinterpret_cast(data), size); + + auto pairs = PairsUtil::toPairs(input); + + if (!pairs.empty()) { + // Verify that non-empty Pairs serializes back to the same bytes. + auto new_size = PairsUtil::pairsSize(pairs); + if (new_size != size) { + __builtin_trap(); + } + std::vector new_data(new_size); + if (!PairsUtil::marshalPairs(pairs, new_data.data(), new_data.size())) { + __builtin_trap(); + } + if (::memcmp(new_data.data(), data, size) != 0) { + __builtin_trap(); + } + } + + return 0; +} + +} // namespace +} // namespace proxy_wasm