blob: f0e8d146a859ea823568701dbd627a01b7737fdc [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <iomanip>
#include <iostream>
#include <limits>
#include <utility>
#include <vector>
#include <fuzzer/FuzzedDataProvider.h>
#include <test/conformance/cpp/libfuzzer_decode_encode.h>
namespace {
// Caller must ensure that `*size >= sizeof(T)`. Using `out` parameter ensures match with implicit
// template specialization.
template <typename T>
void FirstAs(const uint8_t** data, size_t* size, T* out) {
assert(*size >= sizeof(T));
// Use byte-by-byte copy strategy to avoid "load of misaligned address".
uint8_t* out_bytes = (uint8_t*)(out);
memcpy(out_bytes, *data, sizeof(T));
*data += sizeof(T);
*size -= sizeof(T);
}
// Caller must ensure that `*size >= sizeof(T)`. Using `out` parameter ensures match with implicit
// template specialization.
template <typename T>
void LastAs(const uint8_t** data, size_t* size, T* out) {
assert(*size >= sizeof(T));
// Use byte-by-byte copy strategy to avoid "load of misaligned address".
uint8_t* out_bytes = (uint8_t*)(out);
memcpy(out_bytes, *data + *size - sizeof(T), sizeof(T));
*size -= sizeof(T);
}
constexpr uint64_t kMaxHandles = 2 * ZX_CHANNEL_MAX_MSG_HANDLES;
using DecoderEncoderForType = ::fidl::fuzzing::DecoderEncoderForType;
using DecoderEncoderProgress = ::fidl::fuzzing::DecoderEncoderProgress;
using DecoderEncoderStatus = ::fidl::fuzzing::DecoderEncoderStatus;
class DecoderEncoderInput {
public:
DecoderEncoderInput(const uint8_t* const bytes, const size_t size) : bytes_(bytes), size_(size) {}
const uint8_t* data() const { return bytes_; }
size_t size() const { return size_; }
private:
const uint8_t* const bytes_;
const size_t size_;
};
std::string_view DecoderEncoderProgressString(DecoderEncoderProgress progress) {
switch (progress) {
case DecoderEncoderProgress::NoProgress:
return "NoProgress";
case DecoderEncoderProgress::InitializedForDecoding:
return "InitializedForDecoding";
case DecoderEncoderProgress::FirstDecodeSuccess:
return "FirstDecodeSuccess";
case DecoderEncoderProgress::FirstEncodeSuccess:
return "FirstEncodeSuccess";
case DecoderEncoderProgress::FirstEncodeVerified:
return "FirstEncodeVerified";
case DecoderEncoderProgress::SecondDecodeSuccess:
return "SecondDecodeSuccess";
case DecoderEncoderProgress::SecondEncodeSuccess:
return "SecondEncodeSuccess";
}
__builtin_unreachable();
}
// Prints the contents of `first` to `stderr`, highlighting the bytes that differ from `second`.
template <typename T1, typename T2>
void ReportFirstByteArray(const T1& first, const char* const first_label, const T2& second,
const char* const second_label) {
std::cerr << std::endl << first_label << " (diff'd against " << second_label << "):" << std::endl;
if (first.size() == 0) {
std::cerr << "<empty byte array>";
} else {
for (size_t i = 0; i < first.size(); i++) {
if (i != 0 && i % 8 == 0)
std::cerr << std::endl;
const uint8_t* const first_data = first.data();
const uint8_t* const second_data = second.data();
const uint32_t uint_value = first_data[i];
const bool is_diff = i >= second.size() || first_data[i] != second_data[i];
std::cerr << (is_diff ? "[" : " ");
std::cerr << "0x" << std::hex << std::setfill('0') << std::setw(2) << uint_value;
std::cerr << (is_diff ? "]" : " ");
}
}
std::cerr << std::endl;
}
#define REPORT_BYTE_ARRAY_DIFF(first, second) \
{ \
ReportFirstByteArray(first, #first, second, #second); \
ReportFirstByteArray(second, #second, first, #first); \
}
void ReportTestCase(const DecoderEncoderInput& input,
const DecoderEncoderForType& decoder_encoder_for_type,
const DecoderEncoderStatus& status) {
std::cerr << std::endl
<< "FIDL wire type:" << std::endl
<< decoder_encoder_for_type.fidl_type_name << std::endl
<< "flexible envelope? " << std::boolalpha
<< decoder_encoder_for_type.has_flexible_envelope << std::endl;
std::cerr << std::endl
<< "Decode/encode progress:" << std::endl
<< DecoderEncoderProgressString(status.progress) << std::endl;
std::cerr << std::endl << "Decode/encode status:" << std::endl << status.status << std::endl;
const std::vector<uint8_t>& first_encoded_bytes = status.first_encoded_bytes;
REPORT_BYTE_ARRAY_DIFF(input, first_encoded_bytes);
const std::vector<uint8_t>& second_encoded_bytes = status.second_encoded_bytes;
REPORT_BYTE_ARRAY_DIFF(first_encoded_bytes, second_encoded_bytes);
// TODO(fxbug.dev/72895): Report second handle data.
}
#define ASSERT_TEST_CASE(cond, input, decoder_encoder_for_type, status) \
{ \
if (!(cond)) { \
std::cerr << "TEST CASE ASSERTION FAILED: " << #cond << std::endl; \
ReportTestCase(input, decoder_encoder_for_type, status); \
} \
assert(cond); \
}
// If decoder/encoder progressed to a second round-trip, then check that it completed the round-trip
// successfully, and the re-encoded data from both round-trips match.
void CheckDecoderEncoderDoubleRoundTrip(const DecoderEncoderInput& input,
const DecoderEncoderForType& decoder_encoder_for_type,
const DecoderEncoderStatus& status) {
// No symmetry verification unless first decode/encode round-trip succeeded and verified. This is
// because unexpected data in a flexible envelope may be accepted on decode, but invalid to
// re-encode.
if (status.progress < DecoderEncoderProgress::FirstEncodeVerified)
return;
// If no early return above, then second decode-encode round-trip should have succeeded and data
// should match.
#define ASSERT_LOCAL(cond) ASSERT_TEST_CASE(cond, input, decoder_encoder_for_type, status)
ASSERT_LOCAL(status.progress >= DecoderEncoderProgress::SecondEncodeSuccess);
ASSERT_LOCAL(status.first_encoded_bytes.size() == status.second_encoded_bytes.size());
ASSERT_LOCAL(memcmp(status.first_encoded_bytes.data(), status.second_encoded_bytes.data(),
status.first_encoded_bytes.size()) == 0);
#undef ASSERT_LOCAL
// TODO(fxbug.dev/72895): Check handle koids.
}
// If initial decoding succeeded, then check that a decode/encode round-trip succeeded, and
// re-encoded the same data.
void CheckDecoderEncoderRoundTrip(const DecoderEncoderInput& input,
const DecoderEncoderForType& decoder_encoder_for_type,
const DecoderEncoderStatus& status) {
// No symmetry verification unless initial decode succeeded.
if (status.progress < DecoderEncoderProgress::FirstDecodeSuccess)
return;
// If no early return above, then first decode-encode round-trip should have succeeded and
// verified, and data should match.
#define ASSERT_LOCAL(cond) ASSERT_TEST_CASE(cond, input, decoder_encoder_for_type, status)
ASSERT_LOCAL(status.progress >= DecoderEncoderProgress::FirstEncodeVerified);
ASSERT_LOCAL(input.size() == status.first_encoded_bytes.size());
ASSERT_LOCAL(memcmp(input.data(), status.first_encoded_bytes.data(), input.size()) == 0);
#undef ASSERT_LOCAL
// TODO(fxbug.dev/72895): Check handle koids.
}
#undef ASSERT_TEST_CASE
void CheckDecoderEncoderResult(const DecoderEncoderInput& input,
const DecoderEncoderForType& decoder_encoder_for_type,
const DecoderEncoderStatus& status) {
if (decoder_encoder_for_type.has_flexible_envelope) {
// Data with flexible envelopes can only perform symmetry checks on a "double round-trip"
// because unexpected data in a flexible envelope may be accepted on decode, but invalid to
// re-encode. Only after the re-encode succeeds and is verified can a symmetry check on a second
// round-trip be performed (i.e., ensure both re-encodings match).
CheckDecoderEncoderDoubleRoundTrip(input, decoder_encoder_for_type, status);
} else {
// No flexible envelope: Just check check single round-trip: successful decode implies
// successful re-encode of the same data.
CheckDecoderEncoderRoundTrip(input, decoder_encoder_for_type, status);
}
}
} // namespace
// This assertion guards `static_cast<uint32_t>(handle_infos.size())` below, where
// `handle_infos.size() = num_handles` and `num_handles <= kMaxHandles`.
static_assert(kMaxHandles <= std::numeric_limits<uint32_t>::max());
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
const uint8_t* remaining_data = data;
size_t remaining_size = size;
// Follow libfuzzer best practice: Length encodings drawn from tail.
uint64_t num_handles;
if (remaining_size < sizeof(num_handles))
return 0;
LastAs(&remaining_data, &remaining_size, &num_handles);
// Test oversized, but not ludicrously sized, collection of handles.
num_handles %= kMaxHandles + 1;
// Size check: Handles.
// 1. Handle type.
//
// TODO(markdittmer): Use interesting handle rights and values. This may require a change in
// corpus data format.
if (remaining_size < num_handles * sizeof(zx_obj_type_t))
return 0;
std::vector<zx_handle_t> handles;
std::vector<fidl_channel_handle_metadata_t> handle_metadata;
for (uint64_t i = 0; i < num_handles; i++) {
zx_obj_type_t obj_type;
// Consume data: Handles.
// Note: Data (non-length-encodings) drawn from head.
// 1. Handle type.
FirstAs(&remaining_data, &remaining_size, &obj_type);
// TODO(markdittmer): Use interesting handle rights and values. This may require a change in
// corpus data format.
handles.push_back(ZX_HANDLE_INVALID);
handle_metadata.push_back(fidl_channel_handle_metadata_t{
.obj_type = obj_type,
.rights = 0,
});
}
// Remaining data goes into `message`, and `message.size()` later cast as `uint32_t`.
if (remaining_size > std::numeric_limits<uint32_t>::max())
return 0;
const DecoderEncoderInput decoder_encoder_input(remaining_data, remaining_size);
for (auto decoder_encoder_for_type : fuzzing::test_conformance_decoder_encoders) {
// Decode/encode require non-const data pointer: Copy remaining data into (non-const) vector.
std::vector<uint8_t> message(decoder_encoder_input.data(),
decoder_encoder_input.data() + decoder_encoder_input.size());
// Result is unused on builds with assertions disabled.
[[maybe_unused]] auto decode_encode_status = decoder_encoder_for_type.decoder_encoder(
message.data(), static_cast<uint32_t>(message.size()), handles.data(),
handle_metadata.data(), static_cast<uint32_t>(handles.size()));
CheckDecoderEncoderResult(decoder_encoder_input, decoder_encoder_for_type,
decode_encode_status);
}
return 0;
}