diff --git a/fuzzers/Jamfile b/fuzzers/Jamfile index 765b0550c..024ed199f 100644 --- a/fuzzers/Jamfile +++ b/fuzzers/Jamfile @@ -65,6 +65,7 @@ fuzzer upnp ; fuzzer dht_node ; fuzzer utp ; fuzzer resume_data ; +fuzzer peer_conn ; local LARGE_TARGETS = torrent_info @@ -77,6 +78,7 @@ local LARGE_TARGETS = file_storage_add_file sanitize_path upnp + peer_conn ; install stage : $(TARGETS) : EXE fuzzers ; diff --git a/fuzzers/src/peer_conn.cpp b/fuzzers/src/peer_conn.cpp new file mode 100644 index 000000000..7f5af9abf --- /dev/null +++ b/fuzzers/src/peer_conn.cpp @@ -0,0 +1,221 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include "libtorrent/session.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/random.hpp" + + +#if LIBTORRENT_VERSION_NUM >= 10300 +#include "libtorrent/io_context.hpp" +#else +#include "libtorrent/io_service.hpp" +#endif + +using namespace lt; + +std::unique_ptr g_ses; +sha1_hash g_info_hash; +int g_listen_port = 0; +#if LIBTORRENT_VERSION_NUM >= 10300 +io_context g_ios; +#else +io_service g_ios; +#endif + +//#define DEBUG_LOGGING 1 + +extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + // set up a session + settings_pack pack; + pack.set_int(settings_pack::piece_timeout, 1); + pack.set_int(settings_pack::request_timeout, 1); + pack.set_int(settings_pack::peer_timeout, 1); + pack.set_int(settings_pack::peer_connect_timeout, 1); + pack.set_int(settings_pack::inactivity_timeout, 1); + pack.set_int(settings_pack::handshake_timeout, 1); + +#ifdef DEBUG_LOGGING + pack.set_int(settings_pack::alert_mask, 0xffffff); +#else + pack.set_int(settings_pack::alert_mask, alert::connect_notification + | alert::error_notification + | alert::status_notification + | alert::peer_notification); +#endif + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + + // don't waste time making outbound connections + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_ip_notifier, false); + + // pick an available listen port and only listen on loopback + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:0"); + + g_ses = std::unique_ptr(new lt::session(pack)); + + // create a torrent + file_storage fs; + int const piece_size = 1024 * 1024; + std::int64_t const total_size = std::int64_t(piece_size) * 100; + fs.add_file("test_file", total_size); + + create_torrent t(fs, piece_size); + + for (piece_index_t i : fs.piece_range()) + t.set_hash(i, sha1_hash("abababababababababab")); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + + // remember the info-hash to give the fuzzer a chance to connect to it + g_info_hash = ti->info_hash(); + + // add the torrent to the session + add_torrent_params atp; + atp.ti = std::move(ti); + atp.save_path = "."; + + g_ses->add_torrent(std::move(atp)); + + // pull the alerts for the listen socket we ended up using + time_point const end_time = clock_type::now() + seconds(5); + bool started = false; + while (g_listen_port == 0 || !started) + { + std::vector alerts; + auto const now = clock_type::now(); + if (now > end_time) return -1; + + g_ses->wait_for_alert(end_time - now); + g_ses->pop_alerts(&alerts); + + for (auto const a : alerts) + { + std::cout << a->message() << '\n'; + if (auto la = alert_cast(a)) + { + if (la->socket_type == socket_type_t::tcp) + { + g_listen_port = la->port; + std::cout << "listening on " << g_listen_port << '\n'; + } + } + if (alert_cast(a)) + { + started = true; + } + } + } + + // we have to destruct the session before global destructors, such as the + // system error code category. The session objects rely on error_code during + // its destruction + std::atexit([]{ g_ses.reset(); }); + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ +#ifdef DEBUG_LOGGING + time_point const start_time = clock_type::now(); +#endif + // connect + tcp::socket s(g_ios); + error_code ec; + do { + ec.clear(); + error_code ignore; + s.connect(tcp::endpoint(make_address("127.0.0.1", ignore), g_listen_port), ec); + } while (ec == boost::system::errc::interrupted); + + // bittorrent handshake + + std::vector handshake(1 + 19 + 8 + 20 + 20 + size); + std::memcpy(handshake.data(), "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04", 28); + std::memcpy(handshake.data() + 28, g_info_hash.data(), 20); + lt::aux::random_bytes({handshake.data() + 48, 20}); + std::memcpy(handshake.data() + 68, data, size); + + // we're likely to fail to write entire (garbage) messages, as libtorrent may + // disconnect us half-way through. This may fail with broken_pipe for + // instance + error_code ignore; + boost::asio::write(s, boost::asio::buffer(handshake), ignore); + + s.close(); + + // wait for the alert saying the connection was closed + + time_point const end_time = clock_type::now() + seconds(3); + for (;;) + { + std::vector alerts; + auto const now = clock_type::now(); + if (now > end_time) return -1; + + g_ses->wait_for_alert(end_time - now); + g_ses->pop_alerts(&alerts); + + for (auto const a : alerts) + { +#ifdef DEBUG_LOGGING + std::cout << duration_cast(a->timestamp() - start_time).count() + << ": " << a->message() << '\n'; +#endif + if (alert_cast(a) + || alert_cast(a)) + { + goto done; + } + } + } +done: + + return 0; +} + diff --git a/fuzzers/tools/generate_initial_corpus.py b/fuzzers/tools/generate_initial_corpus.py index 90b51165c..656854d04 100644 --- a/fuzzers/tools/generate_initial_corpus.py +++ b/fuzzers/tools/generate_initial_corpus.py @@ -1,6 +1,8 @@ import os import shutil import hashlib +from random import shuffle +import struct corpus_dirs = [ 'torrent_info', 'upnp', 'gzip' 'base32decode', 'base32encode', @@ -8,7 +10,7 @@ corpus_dirs = [ 'dht_node', 'escape_path', 'escape_string', 'file_storage_add_file', 'gzip', 'http_parser', 'lazy_bdecode', 'parse_int', 'parse_magnet_uri', 'resume_data', 'sanitize_path', 'torrent_info', 'upnp', 'utf8_codepoint', 'utf8_wchar', 'utp', - 'verify_encoding', 'wchar_utf8'] + 'verify_encoding', 'wchar_utf8', 'peer_conn'] for p in corpus_dirs: try: @@ -36,3 +38,114 @@ for x in xml_tests: gzip_dir = '../test' for f in ['zeroes.gz', 'corrupt.gz', 'invalid1.gz']: shutil.copy(os.path.join(gzip_dir, f), os.path.join('corpus', 'gzip')) + +# generate peer protocol messages +messages = [] + + +def add_length(msg): + return struct.pack('i', len(msg)) + msg + + +# request +for i in range(101): + for j in range(-1, 1): + messages.append(add_length(struct.pack('Biii', 6, i, j, 0x4000))) + +# cancel +for i in range(101): + for j in range(-1, 1): + messages.append(add_length(struct.pack('Biii', 8, i, j, 0x4000))) + +# piece +for i in range(101): + messages.append(add_length(struct.pack('Bii', 7, i, 0) + ('a' * 0x4000))) + +# single-byte +for i in range(256): + messages.append(add_length(struct.pack('B', i))) + +# reject +for i in range(101): + messages.append(add_length(struct.pack('Biii', 16, i, 0, 0x4000))) + +# suggest +for i in range(101): + messages.append(add_length(struct.pack('Bi', 13, i))) + +# allow-fast +for i in range(101): + messages.append(add_length(struct.pack('Bi', 17, i))) + +# have +for i in range(101): + messages.append(add_length(struct.pack('Bi', 4, i))) + +# DHT-port +for i in range(101): + messages.append(add_length(struct.pack('BH', 9, i * 10))) + +# hash request +for i in range(-10, 200, 20): + for j in range(-1, 1): + for k in range(-1, 1): + for l in range(-1, 1): + for m in range(-1, 1): + messages.append(add_length(struct.pack('biiiii', 21, i, j, k, l, m))) + +# hash reject +for i in range(-10, 200, 20): + for j in range(-1, 1): + for k in range(-1, 1): + for l in range(-1, 1): + for m in range(-1, 1): + messages.append(add_length(struct.pack('biiiii', 23, i, j, k, l, m))) + +# hash +for i in range(-10, 200, 20): + for j in range(-1, 1): + messages.append(add_length(struct.pack('biiiii', 22, i, j, 0, 2, 0) + ('0' * 32 * 5))) + +# lt_dont_have +messages.append(add_length(struct.pack('BBi', 20, 7, -1))) +messages.append(add_length(struct.pack('BBi', 20, 7, 0))) +messages.append(add_length(struct.pack('BBi', 20, 7, 0x7fffffff))) + +# share mode +messages.append(add_length(struct.pack('BBB', 20, 8, 255))) +messages.append(add_length(struct.pack('BBB', 20, 8, 0))) +messages.append(add_length(struct.pack('BBB', 20, 8, 1))) + +# holepunch +for i in range(0, 2): + for j in range(0, 1): + messages.append(add_length(struct.pack('BBBBiH', 20, 4, i, j, 0, 0))) + messages.append(add_length(struct.pack('BBBBiiH', 20, 4, i, j, 0, 0, 0))) + +# upload only +for i in range(0, 1): + messages.append(add_length(struct.pack('BBB', 20, 3, i))) + +# bitfields +bitfield_len = (100 + 7)/8 + +for i in range(256): + messages.append(add_length(struct.pack('B', 5) + (chr(i) * bitfield_len))) + +# extended handshake +ext_handshake = 'd1:md11:ut_metadatai1e11:lt_donthavei2eee' +for i in range(256): + messages.append(add_length(struct.pack('BB', 20, 0) + ext_handshake)) + +mixes = [] + +for i in range(200): + shuffle(messages) + mixes.append(''.join(messages[1:20])) + +messages += mixes + +for m in messages: + f = open('corpus/peer_conn/%s' % hashlib.sha1(m).hexdigest(), 'w+') + f.write(m) + f.close()