blob: 8aec361fd3c7e47e0cae0f03400905bfbcd6f645 [file]
/** @file
fuzzing tscore base64 (ats_base64_encode / ats_base64_decode)
Treats the fuzz input as untrusted base64 and decodes it, then round-trips
arbitrary bytes through encode/decode. Every operation is cross-checked
against the scalar reference primitives, so any divergence of the public
(SIMD, when ENABLE_HIGHWAY_DISPATCH is on) path from the scalar path aborts.
Run under AddressSanitizer to catch any out-of-bounds access on the
untrusted-input decode path.
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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 <cstddef> // size_t, used by ink_base64.h
#include <tscore/ink_base64.h>
#include "ink_base64_scalar.h" // scalar reference (added to include path by CMake)
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#define kMaxInputLength 65536
namespace
{
[[noreturn]] void
fail(const char *what)
{
std::fprintf(stderr, "base64 fuzz mismatch: %s\n", what);
std::abort();
}
// Decode `in` two ways and require identical results (length, bytes, NUL).
void
check_decode(const char *in, size_t len)
{
const size_t cap = ats_base64_decode_dstlen(len);
std::vector<unsigned char> out_pub(cap + 1, 0xAB);
size_t n_pub = 0;
if (!ats_base64_decode(in, len, out_pub.data(), out_pub.size(), &n_pub)) {
fail("decode returned false with sufficient buffer");
}
const size_t valid = ts::base64::count_alphabet_prefix(in, len);
std::vector<unsigned char> out_ref(cap + 1, 0xCD);
size_t n_ref = 0;
ts::base64::decode_scalar(in, valid, out_ref.data(), &n_ref);
if (n_pub != n_ref || std::memcmp(out_pub.data(), out_ref.data(), n_ref) != 0 || out_pub[n_ref] != '\0') {
fail("decode parity");
}
}
} // namespace
extern "C" int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size > kMaxInputLength) {
return 0;
}
// 1. Decode the untrusted input directly (any byte values).
check_decode(reinterpret_cast<const char *>(data), size);
// 2. Encode the input bytes; cross-check encode, then decode back and
// confirm the original bytes are recovered.
const size_t ecap = ats_base64_encode_dstlen(size);
std::vector<char> enc_pub(ecap + 1, 1);
size_t ne_pub = 0;
if (!ats_base64_encode(reinterpret_cast<const char *>(data), size, enc_pub.data(), enc_pub.size(), &ne_pub)) {
fail("encode returned false with sufficient buffer");
}
std::vector<char> enc_ref(ecap + 1, 2);
size_t ne_ref = 0;
ts::base64::encode_scalar(data, size, enc_ref.data(), &ne_ref);
if (ne_pub != ne_ref || std::memcmp(enc_pub.data(), enc_ref.data(), ne_ref + 1) != 0) {
fail("encode parity");
}
// Encoded output is pure alphabet (+ padding); decoding it must recover the
// original bytes, and the two decode paths must agree.
check_decode(enc_pub.data(), ne_pub);
std::vector<unsigned char> back(ats_base64_decode_dstlen(ne_pub) + 1, 0);
size_t nb = 0;
ats_base64_decode(enc_pub.data(), ne_pub, back.data(), back.size(), &nb);
if (nb != size || std::memcmp(back.data(), data, size) != 0) {
fail("encode/decode round-trip");
}
return 0;
}