add fuzzer for peer connection
This commit is contained in:
parent
0cf2924230
commit
5c6f027872
|
@ -65,6 +65,7 @@ fuzzer upnp ;
|
||||||
fuzzer dht_node ;
|
fuzzer dht_node ;
|
||||||
fuzzer utp ;
|
fuzzer utp ;
|
||||||
fuzzer resume_data ;
|
fuzzer resume_data ;
|
||||||
|
fuzzer peer_conn ;
|
||||||
|
|
||||||
local LARGE_TARGETS =
|
local LARGE_TARGETS =
|
||||||
torrent_info
|
torrent_info
|
||||||
|
@ -77,6 +78,7 @@ local LARGE_TARGETS =
|
||||||
file_storage_add_file
|
file_storage_add_file
|
||||||
sanitize_path
|
sanitize_path
|
||||||
upnp
|
upnp
|
||||||
|
peer_conn
|
||||||
;
|
;
|
||||||
|
|
||||||
install stage : $(TARGETS) : <install-type>EXE <location>fuzzers ;
|
install stage : $(TARGETS) : <install-type>EXE <location>fuzzers ;
|
||||||
|
|
|
@ -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 <memory>
|
||||||
|
#include <iostream>
|
||||||
|
#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<session> 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<session>(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<char> buf;
|
||||||
|
bencode(std::back_inserter(buf), t.generate());
|
||||||
|
auto ti = std::make_shared<torrent_info>(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<alert*> 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<listen_succeeded_alert>(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<torrent_resumed_alert>(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<char> 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<alert*> 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<milliseconds>(a->timestamp() - start_time).count()
|
||||||
|
<< ": " << a->message() << '\n';
|
||||||
|
#endif
|
||||||
|
if (alert_cast<peer_error_alert>(a)
|
||||||
|
|| alert_cast<peer_disconnected_alert>(a))
|
||||||
|
{
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from random import shuffle
|
||||||
|
import struct
|
||||||
|
|
||||||
corpus_dirs = [
|
corpus_dirs = [
|
||||||
'torrent_info', 'upnp', 'gzip' 'base32decode', 'base32encode',
|
'torrent_info', 'upnp', 'gzip' 'base32decode', 'base32encode',
|
||||||
|
@ -8,7 +10,7 @@ corpus_dirs = [
|
||||||
'dht_node', 'escape_path', 'escape_string', 'file_storage_add_file', 'gzip',
|
'dht_node', 'escape_path', 'escape_string', 'file_storage_add_file', 'gzip',
|
||||||
'http_parser', 'lazy_bdecode', 'parse_int', 'parse_magnet_uri', 'resume_data',
|
'http_parser', 'lazy_bdecode', 'parse_int', 'parse_magnet_uri', 'resume_data',
|
||||||
'sanitize_path', 'torrent_info', 'upnp', 'utf8_codepoint', 'utf8_wchar', 'utp',
|
'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:
|
for p in corpus_dirs:
|
||||||
try:
|
try:
|
||||||
|
@ -36,3 +38,114 @@ for x in xml_tests:
|
||||||
gzip_dir = '../test'
|
gzip_dir = '../test'
|
||||||
for f in ['zeroes.gz', 'corrupt.gz', 'invalid1.gz']:
|
for f in ['zeroes.gz', 'corrupt.gz', 'invalid1.gz']:
|
||||||
shutil.copy(os.path.join(gzip_dir, f), os.path.join('corpus', 'gzip'))
|
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()
|
||||||
|
|
Loading…
Reference in New Issue