diff --git a/src/test/fuzz/asmap.cpp b/src/test/fuzz/asmap.cpp new file mode 100644 index 00000000000..ea56277eacf --- /dev/null +++ b/src/test/fuzz/asmap.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include + +#include +#include + +//! asmap code that consumes nothing +static const std::vector IPV6_PREFIX_ASMAP = {}; + +//! asmap code that consumes the 96 prefix bits of ::ffff:0/96 (IPv4-in-IPv6 map) +static const std::vector IPV4_PREFIX_ASMAP = { + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, // Match 0x00 + true, true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, // Match 0xFF + true, true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true // Match 0xFF +}; + +void test_one_input(const std::vector& buffer) +{ + // Encoding: [7 bits: asmap size] [1 bit: ipv6?] [3-130 bytes: asmap] [4 or 16 bytes: addr] + if (buffer.size() < 1 + 3 + 4) return; + int asmap_size = 3 + (buffer[0] & 127); + bool ipv6 = buffer[0] & 128; + int addr_size = ipv6 ? 16 : 4; + if (buffer.size() < size_t(1 + asmap_size + addr_size)) return; + std::vector asmap = ipv6 ? IPV6_PREFIX_ASMAP : IPV4_PREFIX_ASMAP; + asmap.reserve(asmap.size() + 8 * asmap_size); + for (int i = 0; i < asmap_size; ++i) { + for (int j = 0; j < 8; ++j) { + asmap.push_back((buffer[1 + i] >> j) & 1); + } + } + if (!SanityCheckASMap(asmap)) return; + CNetAddr net_addr; + net_addr.SetRaw(ipv6 ? NET_IPV6 : NET_IPV4, buffer.data() + 1 + asmap_size); + (void)net_addr.GetMappedAS(asmap); +} diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp index 5c3652f89b0..f24a382d6e8 100644 --- a/src/util/asmap.cpp +++ b/src/util/asmap.cpp @@ -116,7 +116,7 @@ uint32_t Interpret(const std::vector &asmap, const std::vector &ip) break; // Instruction straddles EOF } } - // Reached EOF without RETURN, or aborted (see any of the breaks above). + assert(false); // Reached EOF without RETURN, or aborted (see any of the breaks above) - should have been caught by SanityCheckASMap below return 0; // 0 is not a valid ASN } @@ -126,11 +126,14 @@ bool SanityCheckASMap(const std::vector& asmap, int bits) std::vector::const_iterator pos = begin; std::vector> jumps; // All future positions we may jump to (bit offset in asmap -> bits to consume left) jumps.reserve(bits); + Instruction prevopcode = Instruction::JUMP; + bool had_incomplete_match = false; while (pos != endpos) { uint32_t offset = pos - begin; if (!jumps.empty() && offset >= jumps.back().first) return false; // There was a jump into the middle of the previous instruction Instruction opcode = DecodeType(pos, endpos); if (opcode == Instruction::RETURN) { + if (prevopcode == Instruction::DEFAULT) return false; // There should not be any RETURN immediately after a DEFAULT (could be combined into just RETURN) uint32_t asn = DecodeASN(pos, endpos); if (asn == INVALID) return false; // ASN straddles EOF if (jumps.empty()) { @@ -147,6 +150,7 @@ bool SanityCheckASMap(const std::vector& asmap, int bits) if (offset != jumps.back().first) return false; // Unreachable code bits = jumps.back().second; // Restore the number of bits we would have had left after this jump jumps.pop_back(); + prevopcode = Instruction::JUMP; } } else if (opcode == Instruction::JUMP) { uint32_t jump = DecodeJump(pos, endpos); @@ -157,15 +161,22 @@ bool SanityCheckASMap(const std::vector& asmap, int bits) uint32_t jump_offset = pos - begin + jump; if (!jumps.empty() && jump_offset >= jumps.back().first) return false; // Intersecting jumps jumps.emplace_back(jump_offset, bits); + prevopcode = Instruction::JUMP; } else if (opcode == Instruction::MATCH) { uint32_t match = DecodeMatch(pos, endpos); if (match == INVALID) return false; // Match bits straddle EOF int matchlen = CountBits(match) - 1; + if (prevopcode != Instruction::MATCH) had_incomplete_match = false; + if (matchlen < 8 && had_incomplete_match) return false; // Within a sequence of matches only at most one should be incomplete + had_incomplete_match = (matchlen < 8); if (bits < matchlen) return false; // Consuming bits past the end of the input bits -= matchlen; + prevopcode = Instruction::MATCH; } else if (opcode == Instruction::DEFAULT) { + if (prevopcode == Instruction::DEFAULT) return false; // There should not be two successive DEFAULTs (they could be combined into one) uint32_t asn = DecodeASN(pos, endpos); if (asn == INVALID) return false; // ASN straddles EOF + prevopcode = Instruction::DEFAULT; } else { return false; // Instruction straddles EOF }