mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
fix: limit pad search during encrypted handshake (#3471)
This commit is contained in:
@@ -146,7 +146,6 @@ struct tr_handshake
|
||||
uint16_t ia_len = {};
|
||||
uint32_t crypto_select = {};
|
||||
uint32_t crypto_provide = {};
|
||||
tr_sha1_digest_t myReq1 = {};
|
||||
struct event* timeout_timer = nullptr;
|
||||
|
||||
std::optional<tr_peer_id_t> peer_id;
|
||||
@@ -481,34 +480,36 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
// A will be able to resynchronize on ENCRYPT(VC)"
|
||||
static ReadState readVC(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
{
|
||||
auto tmp = vc_t{};
|
||||
// find the end of PadB by looking for `ENCRYPT(VC)`
|
||||
auto needle = VC;
|
||||
auto filter = tr_message_stream_encryption::Filter{};
|
||||
filter.encryptInit(true, handshake->dh, *handshake->io->torrentHash());
|
||||
filter.encrypt(std::size(needle), std::data(needle));
|
||||
|
||||
/* note: this works w/o having to `unwind' the buffer if
|
||||
* we read too much, but it is pretty brute-force.
|
||||
* it would be nice to make this cleaner. */
|
||||
for (;;)
|
||||
for (size_t i = 0; i < PadB_MAXLEN; ++i)
|
||||
{
|
||||
if (evbuffer_get_length(inbuf) < std::size(tmp))
|
||||
if (evbuffer_get_length(inbuf) < std::size(needle))
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "not enough bytes... returning read_more");
|
||||
return READ_LATER;
|
||||
}
|
||||
|
||||
std::copy_n(reinterpret_cast<std::byte const*>(evbuffer_pullup(inbuf, std::size(tmp))), std::size(tmp), std::data(tmp));
|
||||
handshake->io->decryptInit(handshake->io->isIncoming(), handshake->dh, *handshake->io->torrentHash());
|
||||
handshake->io->decrypt(std::size(tmp), std::data(tmp));
|
||||
if (tmp == VC)
|
||||
auto const* peek = reinterpret_cast<std::byte const*>(evbuffer_pullup(inbuf, std::size(needle)));
|
||||
if (std::equal(std::begin(needle), std::end(needle), peek))
|
||||
{
|
||||
break;
|
||||
tr_logAddTraceHand(handshake, "got it!");
|
||||
// We already know it's a match; now we just need to
|
||||
// consume it from the read buffer.
|
||||
tr_peerIoReadBytes(handshake->io, inbuf, std::data(needle), std::size(needle));
|
||||
setState(handshake, AWAITING_CRYPTO_SELECT);
|
||||
return READ_NOW;
|
||||
}
|
||||
|
||||
evbuffer_drain(inbuf, 1);
|
||||
}
|
||||
|
||||
tr_logAddTraceHand(handshake, "got it!");
|
||||
evbuffer_drain(inbuf, std::size(VC));
|
||||
setState(handshake, AWAITING_CRYPTO_SELECT);
|
||||
return READ_NOW;
|
||||
tr_logAddTraceHand(handshake, "couldn't find ENCRYPT(VC)");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
static ReadState readCryptoSelect(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
@@ -719,16 +720,6 @@ static ReadState readYa(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
evbuffer_remove(inbuf, std::data(peer_public_key), std::size(peer_public_key));
|
||||
handshake->dh.setPeerPublicKey(peer_public_key);
|
||||
|
||||
if (auto const req1 = tr_sha1("req1"sv, handshake->dh.secret()); req1)
|
||||
{
|
||||
handshake->myReq1 = *req1;
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req1 hash after Ya");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
// send our public key to the peer
|
||||
tr_logAddTraceHand(handshake, "sending B->A: Diffie Hellman Yb, PadB");
|
||||
sendPublicKeyAndPad<PadB_MAXLEN>(handshake);
|
||||
@@ -739,27 +730,31 @@ static ReadState readYa(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
|
||||
static ReadState readPadA(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
{
|
||||
/* resynchronizing on HASH('req1', S) */
|
||||
struct evbuffer_ptr ptr = evbuffer_search(
|
||||
inbuf,
|
||||
reinterpret_cast<char const*>(std::data(handshake->myReq1)),
|
||||
std::size(handshake->myReq1),
|
||||
nullptr);
|
||||
// find the end of PadA by looking for HASH('req1', S)
|
||||
auto const needle = *tr_sha1("req1"sv, handshake->dh.secret());
|
||||
|
||||
if (ptr.pos != -1) /* match */
|
||||
for (size_t i = 0; i < PadA_MAXLEN; ++i)
|
||||
{
|
||||
evbuffer_drain(inbuf, ptr.pos);
|
||||
tr_logAddTraceHand(handshake, "found it... looking setting to awaiting_crypto_provide");
|
||||
setState(handshake, AWAITING_CRYPTO_PROVIDE);
|
||||
return READ_NOW;
|
||||
if (evbuffer_get_length(inbuf) < std::size(needle))
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "not enough bytes... returning read_more");
|
||||
return READ_LATER;
|
||||
}
|
||||
|
||||
auto const* peek = reinterpret_cast<std::byte const*>(evbuffer_pullup(inbuf, std::size(needle)));
|
||||
if (std::equal(std::begin(needle), std::end(needle), peek))
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "found it... looking setting to awaiting_crypto_provide");
|
||||
evbuffer_drain(inbuf, std::size(needle));
|
||||
setState(handshake, AWAITING_CRYPTO_PROVIDE);
|
||||
return READ_NOW;
|
||||
}
|
||||
|
||||
evbuffer_drain(inbuf, 1);
|
||||
}
|
||||
|
||||
if (size_t const len = evbuffer_get_length(inbuf); len > SHA_DIGEST_LENGTH)
|
||||
{
|
||||
evbuffer_drain(inbuf, len - SHA_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
return READ_LATER;
|
||||
tr_logAddTraceHand(handshake, "couldn't find HASH('req', S)");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
static ReadState readCryptoProvide(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
@@ -768,8 +763,7 @@ static ReadState readCryptoProvide(tr_handshake* handshake, struct evbuffer* inb
|
||||
|
||||
uint16_t padc_len = 0;
|
||||
uint32_t crypto_provide = 0;
|
||||
size_t const needlen = SHA_DIGEST_LENGTH + /* HASH('req1', s) */
|
||||
SHA_DIGEST_LENGTH + /* HASH('req2', SKEY) xor HASH('req3', S) */
|
||||
size_t const needlen = SHA_DIGEST_LENGTH + /* HASH('req2', SKEY) xor HASH('req3', S) */
|
||||
std::size(VC) + sizeof(crypto_provide) + sizeof(padc_len);
|
||||
|
||||
if (evbuffer_get_length(inbuf) < needlen)
|
||||
@@ -777,9 +771,6 @@ static ReadState readCryptoProvide(tr_handshake* handshake, struct evbuffer* inb
|
||||
return READ_LATER;
|
||||
}
|
||||
|
||||
/* TODO: confirm they sent HASH('req1',S) here? */
|
||||
evbuffer_drain(inbuf, SHA_DIGEST_LENGTH);
|
||||
|
||||
/* This next piece is HASH('req2', SKEY) xor HASH('req3', S) ...
|
||||
* we can get the first half of that (the obfuscatedTorrentHash)
|
||||
* by building the latter and xor'ing it with what the peer sent us */
|
||||
|
||||
@@ -38,11 +38,13 @@ using HandshakeTest = SessionTest;
|
||||
class MediatorMock final : public tr_handshake_mediator
|
||||
{
|
||||
public:
|
||||
MediatorMock(tr_session* session)
|
||||
explicit MediatorMock(tr_session* session)
|
||||
: session_{ session }
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~MediatorMock() = default;
|
||||
|
||||
[[nodiscard]] std::optional<torrent_info> torrentInfo(tr_sha1_digest_t const& info_hash) const override
|
||||
{
|
||||
if (auto const iter = torrents.find(info_hash); iter != std::end(torrents))
|
||||
@@ -137,17 +139,17 @@ void sendB64ToClient(evutil_socket_t sock, std::string_view b64)
|
||||
|
||||
auto constexpr ReservedBytesNoExtensions = std::array<uint8_t, 8>{ 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
auto constexpr PlaintextProtocolName = "\023BitTorrent protocol"sv;
|
||||
auto const default_peer_addr = *tr_address::fromString("127.0.0.1"sv);
|
||||
auto const default_peer_port = tr_port::fromHost(8080);
|
||||
auto const torrent_we_are_seeding = tr_handshake_mediator::torrent_info{ *tr_sha1("abcde"sv),
|
||||
tr_peerIdInit(),
|
||||
tr_torrent_id_t{ 100 },
|
||||
true /*is_done*/ };
|
||||
auto const ubuntu_torrent = tr_handshake_mediator::torrent_info{ *tr_sha1_from_string(
|
||||
"2c6b6858d61da9543d4231a71db4b1c9264b0685"sv),
|
||||
tr_peerIdInit(),
|
||||
tr_torrent_id_t{ 101 },
|
||||
false /*is_done*/ };
|
||||
auto const DefaultPeerAddr = *tr_address::fromString("127.0.0.1"sv);
|
||||
auto const DefaultPeerPort = tr_port::fromHost(8080);
|
||||
auto const TorrentWeAreSeeding = tr_handshake_mediator::torrent_info{ *tr_sha1("abcde"sv),
|
||||
tr_peerIdInit(),
|
||||
tr_torrent_id_t{ 100 },
|
||||
true /*is_done*/ };
|
||||
auto const UbuntuTorrent = tr_handshake_mediator::torrent_info{ *tr_sha1_from_string(
|
||||
"2c6b6858d61da9543d4231a71db4b1c9264b0685"sv),
|
||||
tr_peerIdInit(),
|
||||
tr_torrent_id_t{ 101 },
|
||||
false /*is_done*/ };
|
||||
|
||||
auto createIncomingIo(tr_session* session)
|
||||
{
|
||||
@@ -156,7 +158,7 @@ auto createIncomingIo(tr_session* session)
|
||||
auto const now = tr_time();
|
||||
auto const peer_socket = tr_peer_socket_tcp_create(sockpair[0]);
|
||||
auto* const
|
||||
io = tr_peerIoNewIncoming(session, &session->top_bandwidth_, &default_peer_addr, default_peer_port, now, peer_socket);
|
||||
io = tr_peerIoNewIncoming(session, &session->top_bandwidth_, &DefaultPeerAddr, DefaultPeerPort, now, peer_socket);
|
||||
return std::make_pair(io, sockpair[1]);
|
||||
}
|
||||
|
||||
@@ -169,8 +171,8 @@ auto createOutgoingIo(tr_session* session, tr_sha1_digest_t const& info_hash)
|
||||
auto* const io = tr_peerIoNew(
|
||||
session,
|
||||
&session->top_bandwidth_,
|
||||
&default_peer_addr,
|
||||
default_peer_port,
|
||||
&DefaultPeerAddr,
|
||||
DefaultPeerPort,
|
||||
now,
|
||||
&info_hash,
|
||||
false /*is_incoming*/,
|
||||
@@ -205,13 +207,13 @@ auto runHandshake(
|
||||
{
|
||||
auto result = std::optional<tr_handshake_result>{};
|
||||
|
||||
static auto const done_callback = [](auto const& resin)
|
||||
static auto const DoneCallback = [](auto const& resin)
|
||||
{
|
||||
*static_cast<std::optional<tr_handshake_result>*>(resin.userData) = resin;
|
||||
return true;
|
||||
};
|
||||
|
||||
tr_handshakeNew(mediator, io, encryption_mode, done_callback, &result);
|
||||
tr_handshakeNew(std::move(mediator), io, encryption_mode, DoneCallback, &result);
|
||||
|
||||
waitFor([&result]() { return result.has_value(); }, MaxWaitMsec);
|
||||
|
||||
@@ -222,7 +224,7 @@ TEST_F(HandshakeTest, incomingPlaintext)
|
||||
{
|
||||
auto const peer_id = makeRandomPeerId();
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(torrent_we_are_seeding.info_hash, torrent_we_are_seeding);
|
||||
mediator->torrents.emplace(TorrentWeAreSeeding.info_hash, TorrentWeAreSeeding);
|
||||
|
||||
// The simplest handshake there is. "The handshake starts with character
|
||||
// nineteen (decimal) followed by the string 'BitTorrent protocol'.
|
||||
@@ -235,7 +237,7 @@ TEST_F(HandshakeTest, incomingPlaintext)
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
sendToClient(sock, PlaintextProtocolName);
|
||||
sendToClient(sock, ReservedBytesNoExtensions);
|
||||
sendToClient(sock, torrent_we_are_seeding.info_hash);
|
||||
sendToClient(sock, TorrentWeAreSeeding.info_hash);
|
||||
sendToClient(sock, peer_id);
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
@@ -248,7 +250,7 @@ TEST_F(HandshakeTest, incomingPlaintext)
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(peer_id, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(torrent_we_are_seeding.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(TorrentWeAreSeeding.info_hash, *io->torrentHash());
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
@@ -259,7 +261,7 @@ TEST_F(HandshakeTest, incomingPlaintext)
|
||||
TEST_F(HandshakeTest, incomingPlaintextUnknownInfoHash)
|
||||
{
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(torrent_we_are_seeding.info_hash, torrent_we_are_seeding);
|
||||
mediator->torrents.emplace(TorrentWeAreSeeding.info_hash, TorrentWeAreSeeding);
|
||||
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
sendToClient(sock, PlaintextProtocolName);
|
||||
@@ -285,12 +287,12 @@ TEST_F(HandshakeTest, outgoingPlaintext)
|
||||
{
|
||||
auto const peer_id = makeRandomPeerId();
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(ubuntu_torrent.info_hash, torrent_we_are_seeding);
|
||||
mediator->torrents.emplace(UbuntuTorrent.info_hash, TorrentWeAreSeeding);
|
||||
|
||||
auto [io, sock] = createOutgoingIo(session_, ubuntu_torrent.info_hash);
|
||||
auto [io, sock] = createOutgoingIo(session_, UbuntuTorrent.info_hash);
|
||||
sendToClient(sock, PlaintextProtocolName);
|
||||
sendToClient(sock, ReservedBytesNoExtensions);
|
||||
sendToClient(sock, ubuntu_torrent.info_hash);
|
||||
sendToClient(sock, UbuntuTorrent.info_hash);
|
||||
sendToClient(sock, peer_id);
|
||||
|
||||
auto const res = runHandshake(mediator, io);
|
||||
@@ -303,8 +305,8 @@ TEST_F(HandshakeTest, outgoingPlaintext)
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(peer_id, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(ubuntu_torrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(ubuntu_torrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
EXPECT_EQ(UbuntuTorrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(UbuntuTorrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
@@ -315,7 +317,7 @@ TEST_F(HandshakeTest, incomingEncrypted)
|
||||
static auto constexpr ExpectedPeerId = makePeerId("-TR300Z-w4bd4mkebkbi"sv);
|
||||
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(ubuntu_torrent.info_hash, ubuntu_torrent);
|
||||
mediator->torrents.emplace(UbuntuTorrent.info_hash, UbuntuTorrent);
|
||||
mediator->setPrivateKeyFromBase64("0EYKCwBWQ4Dg9kX3c5xxjVtBDKw="sv);
|
||||
|
||||
auto [io, sock] = createIncomingIo(session_);
|
||||
@@ -343,8 +345,8 @@ TEST_F(HandshakeTest, incomingEncrypted)
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(ExpectedPeerId, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(ubuntu_torrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(ubuntu_torrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
EXPECT_EQ(UbuntuTorrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(UbuntuTorrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
@@ -389,10 +391,10 @@ TEST_F(HandshakeTest, outgoingEncrypted)
|
||||
static auto constexpr ExpectedPeerId = makePeerId("-qB4250-scysDI_JuVN3"sv);
|
||||
|
||||
auto mediator = std::make_shared<MediatorMock>(session_);
|
||||
mediator->torrents.emplace(ubuntu_torrent.info_hash, ubuntu_torrent);
|
||||
mediator->torrents.emplace(UbuntuTorrent.info_hash, UbuntuTorrent);
|
||||
mediator->setPrivateKeyFromBase64("0EYKCwBWQ4Dg9kX3c5xxjVtBDKw="sv);
|
||||
|
||||
auto [io, sock] = createOutgoingIo(session_, ubuntu_torrent.info_hash);
|
||||
auto [io, sock] = createOutgoingIo(session_, UbuntuTorrent.info_hash);
|
||||
|
||||
// Peer->Client data from a successful encrypted handshake recorded
|
||||
// in the wild for replay here
|
||||
@@ -422,8 +424,8 @@ TEST_F(HandshakeTest, outgoingEncrypted)
|
||||
EXPECT_TRUE(res->peer_id);
|
||||
EXPECT_EQ(ExpectedPeerId, res->peer_id);
|
||||
EXPECT_TRUE(io->torrentHash());
|
||||
EXPECT_EQ(ubuntu_torrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(ubuntu_torrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
EXPECT_EQ(UbuntuTorrent.info_hash, *io->torrentHash());
|
||||
EXPECT_EQ(tr_sha1_to_string(UbuntuTorrent.info_hash), tr_sha1_to_string(*io->torrentHash()));
|
||||
|
||||
tr_peerIoUnref(io);
|
||||
evutil_closesocket(sock);
|
||||
|
||||
Reference in New Issue
Block a user