forked from Green-Sky/tomato
Squashed 'external/toxcore/c-toxcore/' changes from 1828c5356..c9cdae001
c9cdae001 fix(toxav): remove extra copy of video frame on encode 4f6d4546b test: Improve the fake network library. a2581e700 refactor(toxcore): generate `Friend_Request` and `Dht_Nodes_Response` 2aaa11770 refactor(toxcore): use Tox_Memory in generated events 5c367452b test(toxcore): fix incorrect mutex in tox_scenario_get_time 8f92e710f perf: Add a timed limit of number of cookie requests. 695b6417a test: Add some more simulated network support. 815ae9ce9 test(toxcore): fix thread-safety in scenario framework 6d85c754e test(toxcore): add unit tests for net_crypto 9c22e79cc test(support): add SimulatedEnvironment for deterministic testing f34fcb195 chore: Update windows Dockerfile to debian stable (trixie). ece0e8980 fix(group_moderation): allow validating unsorted sanction list signatures a4fa754d7 refactor: rename struct Packet to struct Net_Packet d6f330f85 cleanup: Fix some warnings from coverity. e206bffa2 fix(group_chats): fix sync packets reverting topics 0e4715598 test: Add new scenario testing framework. 668291f44 refactor(toxcore): decouple Network_Funcs from sockaddr via IP_Port fc4396cef fix: potential division by zero in toxav and unsafe hex parsing 8e8b352ab refactor: Add nullable annotations to struct members. 7740bb421 refactor: decouple net_crypto from DHT 1936d4296 test: add benchmark for toxav audio and video 46bfdc2df fix: correct printf format specifiers for unsigned integers REVERT: 1828c5356 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: c9cdae001341e701fca980c9bb9febfeb95d2902
This commit is contained in:
579
toxcore/onion_client_test.cc
Normal file
579
toxcore/onion_client_test.cc
Normal file
@@ -0,0 +1,579 @@
|
||||
#include "onion_client.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "../testing/support/public/simulated_environment.hh"
|
||||
#include "DHT_test_util.hh"
|
||||
#include "crypto_core.h"
|
||||
#include "logger.h"
|
||||
#include "mono_time.h"
|
||||
#include "net_crypto.h"
|
||||
#include "net_profile.h"
|
||||
#include "network.h"
|
||||
#include "onion.h"
|
||||
#include "onion_announce.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace tox::test;
|
||||
|
||||
// --- Helper Class ---
|
||||
|
||||
template <typename DHTWrapper>
|
||||
class OnionTestNode {
|
||||
public:
|
||||
OnionTestNode(SimulatedEnvironment &env, uint16_t port)
|
||||
: dht_wrapper_(env, port)
|
||||
, net_profile_(netprof_new(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory),
|
||||
[mem = &dht_wrapper_.node().c_memory](Net_Profile *p) { netprof_kill(mem, p); })
|
||||
, net_crypto_(nullptr, [](Net_Crypto *c) { kill_net_crypto(c); })
|
||||
, onion_client_(nullptr, [](Onion_Client *c) { kill_onion_client(c); })
|
||||
{
|
||||
// Setup NetCrypto
|
||||
TCP_Proxy_Info proxy_info = {{0}, TCP_PROXY_NONE};
|
||||
net_crypto_.reset(new_net_crypto(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory,
|
||||
&dht_wrapper_.node().c_random, &dht_wrapper_.node().c_network, dht_wrapper_.mono_time(),
|
||||
dht_wrapper_.networking(), dht_wrapper_.get_dht(), &DHTWrapper::funcs, &proxy_info,
|
||||
net_profile_.get()));
|
||||
|
||||
// Setup Onion Client
|
||||
onion_client_.reset(new_onion_client(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory,
|
||||
&dht_wrapper_.node().c_random, dht_wrapper_.mono_time(), net_crypto_.get(),
|
||||
dht_wrapper_.get_dht(), dht_wrapper_.networking()));
|
||||
}
|
||||
|
||||
Onion_Client *get_onion_client() { return onion_client_.get(); }
|
||||
Net_Crypto *get_net_crypto() { return net_crypto_.get(); }
|
||||
DHT *get_dht() { return dht_wrapper_.get_dht(); }
|
||||
Logger *get_logger() { return dht_wrapper_.logger(); }
|
||||
const uint8_t *dht_public_key() const { return dht_wrapper_.dht_public_key(); }
|
||||
const uint8_t *real_public_key() const { return nc_get_self_public_key(net_crypto_.get()); }
|
||||
const Random *get_random() { return &dht_wrapper_.node().c_random; }
|
||||
|
||||
IP_Port get_ip_port() const { return dht_wrapper_.get_ip_port(); }
|
||||
|
||||
void poll()
|
||||
{
|
||||
dht_wrapper_.poll();
|
||||
do_net_crypto(net_crypto_.get(), nullptr);
|
||||
do_onion_client(onion_client_.get());
|
||||
}
|
||||
|
||||
~OnionTestNode();
|
||||
|
||||
private:
|
||||
DHTWrapper dht_wrapper_;
|
||||
std::unique_ptr<Net_Profile, std::function<void(Net_Profile *)>> net_profile_;
|
||||
std::unique_ptr<Net_Crypto, void (*)(Net_Crypto *)> net_crypto_;
|
||||
std::unique_ptr<Onion_Client, void (*)(Onion_Client *)> onion_client_;
|
||||
};
|
||||
|
||||
template <typename DHTWrapper>
|
||||
OnionTestNode<DHTWrapper>::~OnionTestNode() = default;
|
||||
|
||||
using OnionNode = OnionTestNode<WrappedDHT>;
|
||||
|
||||
class OnionClientTest : public ::testing::Test {
|
||||
public:
|
||||
static void print_log(void *context, Logger_Level level, const char *file, uint32_t line,
|
||||
const char *func, const char *message, void *userdata)
|
||||
{
|
||||
fprintf(stderr, "[%d] %s:%u %s: %s\n", level, file, line, func, message);
|
||||
}
|
||||
|
||||
protected:
|
||||
SimulatedEnvironment env;
|
||||
};
|
||||
|
||||
TEST_F(OnionClientTest, CreationAndDestruction)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
EXPECT_NE(alice.get_onion_client(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, FriendManagement)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), friend_pk, friend_sk);
|
||||
|
||||
// Add Friend
|
||||
int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk);
|
||||
ASSERT_NE(friend_num, -1);
|
||||
|
||||
// Check Friend Num
|
||||
EXPECT_EQ(onion_friend_num(alice.get_onion_client(), friend_pk), friend_num);
|
||||
|
||||
// Add Same Friend Again
|
||||
EXPECT_EQ(onion_addfriend(alice.get_onion_client(), friend_pk), friend_num);
|
||||
|
||||
// Check Friend Count
|
||||
EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 1);
|
||||
|
||||
// Delete Friend
|
||||
EXPECT_NE(onion_delfriend(alice.get_onion_client(), friend_num), -1);
|
||||
EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 0);
|
||||
|
||||
// Check Friend Num after deletion
|
||||
EXPECT_EQ(onion_friend_num(alice.get_onion_client(), friend_pk), -1);
|
||||
|
||||
// Delete Invalid Friend
|
||||
EXPECT_EQ(onion_delfriend(alice.get_onion_client(), friend_num), -1);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, FriendStatus)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), friend_pk, friend_sk);
|
||||
|
||||
int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk);
|
||||
ASSERT_NE(friend_num, -1);
|
||||
|
||||
// Set DHT Key so we can get IP
|
||||
uint8_t dht_key[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), dht_key, friend_sk);
|
||||
EXPECT_EQ(onion_set_friend_dht_pubkey(alice.get_onion_client(), friend_num, dht_key), 0);
|
||||
|
||||
uint32_t lock_token;
|
||||
EXPECT_EQ(
|
||||
dht_addfriend(alice.get_dht(), dht_key, nullptr, nullptr, friend_num, &lock_token), 0);
|
||||
|
||||
// Set Online
|
||||
EXPECT_EQ(onion_set_friend_online(alice.get_onion_client(), friend_num, true), 0);
|
||||
|
||||
// Get Friend IP (should be 0 as not connected)
|
||||
IP_Port ip;
|
||||
EXPECT_EQ(onion_getfriendip(alice.get_onion_client(), friend_num, &ip), 0);
|
||||
|
||||
// Set Offline
|
||||
EXPECT_EQ(onion_set_friend_online(alice.get_onion_client(), friend_num, false), 0);
|
||||
|
||||
// Invalid friend num
|
||||
EXPECT_EQ(onion_set_friend_online(alice.get_onion_client(), 12345, true), -1);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, DHTKey)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), friend_pk, friend_sk);
|
||||
|
||||
int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk);
|
||||
ASSERT_NE(friend_num, -1);
|
||||
|
||||
uint8_t dht_key[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), dht_key, friend_sk);
|
||||
|
||||
// Set DHT Key
|
||||
EXPECT_EQ(onion_set_friend_dht_pubkey(alice.get_onion_client(), friend_num, dht_key), 0);
|
||||
|
||||
// Get DHT Key
|
||||
uint8_t retrieved_key[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
EXPECT_EQ(onion_getfriend_dht_pubkey(alice.get_onion_client(), friend_num, retrieved_key), 1);
|
||||
EXPECT_EQ(std::memcmp(dht_key, retrieved_key, CRYPTO_PUBLIC_KEY_SIZE), 0);
|
||||
|
||||
// Invalid friend
|
||||
EXPECT_EQ(onion_set_friend_dht_pubkey(alice.get_onion_client(), 12345, dht_key), -1);
|
||||
EXPECT_EQ(onion_getfriend_dht_pubkey(alice.get_onion_client(), 12345, retrieved_key), 0);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, BootstrapNodes)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
IP_Port ip_port;
|
||||
ip_init(&ip_port.ip, 1);
|
||||
ip_port.port = 1234;
|
||||
uint8_t pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), pk, pk);
|
||||
|
||||
EXPECT_TRUE(onion_add_bs_path_node(alice.get_onion_client(), &ip_port, pk));
|
||||
|
||||
Node_format nodes[MAX_ONION_CLIENTS];
|
||||
uint16_t count = onion_backup_nodes(alice.get_onion_client(), nodes, MAX_ONION_CLIENTS);
|
||||
EXPECT_GE(count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, ConnectionStatus)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
Onion_Connection_Status status = onion_connection_status(alice.get_onion_client());
|
||||
EXPECT_GE(status, ONION_CONNECTION_STATUS_NONE);
|
||||
EXPECT_LE(status, ONION_CONNECTION_STATUS_UDP);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, GroupChatHelpers)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), friend_pk, friend_sk);
|
||||
|
||||
int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk);
|
||||
ASSERT_NE(friend_num, -1);
|
||||
|
||||
Onion_Friend *friend_obj = onion_get_friend(alice.get_onion_client(), friend_num);
|
||||
EXPECT_NE(friend_obj, nullptr);
|
||||
|
||||
// Test Group Chat Public Key
|
||||
uint8_t gc_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
crypto_new_keypair(alice.get_random(), gc_pk, friend_sk);
|
||||
|
||||
onion_friend_set_gc_public_key(friend_obj, gc_pk);
|
||||
const uint8_t *retrieved_gc_pk = onion_friend_get_gc_public_key(friend_obj);
|
||||
EXPECT_EQ(std::memcmp(gc_pk, retrieved_gc_pk, CRYPTO_PUBLIC_KEY_SIZE), 0);
|
||||
|
||||
// Test Group Chat Flag
|
||||
EXPECT_FALSE(onion_friend_is_groupchat(friend_obj));
|
||||
uint8_t data[] = {1, 2, 3};
|
||||
onion_friend_set_gc_data(friend_obj, data, sizeof(data));
|
||||
EXPECT_TRUE(onion_friend_is_groupchat(friend_obj));
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, OOBReadInHandleAnnounceResponse)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
logger_callback_log(alice.get_logger(), OnionClientTest::print_log, nullptr, nullptr);
|
||||
WrappedDHT bob(env, 12345);
|
||||
FakeUdpSocket *bob_socket = bob.node().endpoint;
|
||||
|
||||
IP_Port bob_ip = bob.get_ip_port();
|
||||
const uint8_t *bob_pk = bob.dht_public_key();
|
||||
const uint8_t *bob_sk = bob.dht_secret_key();
|
||||
|
||||
// Bootstrap Alice to Bob
|
||||
dht_bootstrap(alice.get_dht(), &bob_ip, bob_pk);
|
||||
|
||||
// Add Bob as Onion Bootstrap node
|
||||
onion_add_bs_path_node(alice.get_onion_client(), &bob_ip, bob_pk);
|
||||
|
||||
// Get internal state
|
||||
uint64_t initial_recv_time = onion_testonly_get_last_packet_recv(alice.get_onion_client());
|
||||
|
||||
// Setup Memory
|
||||
Tox_Memory mem_struct = env.fake_memory().get_c_memory();
|
||||
const Memory *mem = &mem_struct;
|
||||
|
||||
// Observer
|
||||
bool triggered = false;
|
||||
bob_socket->set_recv_observer([&](const std::vector<uint8_t> &data, const IP_Port &from) {
|
||||
if (triggered)
|
||||
return;
|
||||
if (data.size() < 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)
|
||||
return;
|
||||
|
||||
// Layer 1
|
||||
if (data[0] != NET_PACKET_ONION_SEND_INITIAL)
|
||||
return;
|
||||
|
||||
uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE];
|
||||
uint8_t nonce[CRYPTO_NONCE_SIZE];
|
||||
memcpy(nonce, data.data() + 1, CRYPTO_NONCE_SIZE);
|
||||
const uint8_t *ephem_pk = data.data() + 1 + CRYPTO_NONCE_SIZE;
|
||||
|
||||
encrypt_precompute(ephem_pk, bob_sk, shared_key);
|
||||
|
||||
std::vector<uint8_t> decrypted1(
|
||||
data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE));
|
||||
int dlen = decrypt_data_symmetric(mem, shared_key, nonce,
|
||||
data.data() + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE,
|
||||
data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), decrypted1.data());
|
||||
if (dlen <= 0)
|
||||
return;
|
||||
|
||||
// Decrypted 1: [IP] [PK] [Encrypted 2]
|
||||
size_t offset = SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE;
|
||||
if (static_cast<size_t>(dlen) <= offset + CRYPTO_MAC_SIZE)
|
||||
return;
|
||||
ephem_pk = decrypted1.data() + SIZE_IPPORT;
|
||||
|
||||
encrypt_precompute(ephem_pk, bob_sk, shared_key);
|
||||
|
||||
std::vector<uint8_t> decrypted2(dlen - offset - CRYPTO_MAC_SIZE);
|
||||
dlen = decrypt_data_symmetric(
|
||||
mem, shared_key, nonce, decrypted1.data() + offset, dlen - offset, decrypted2.data());
|
||||
if (dlen <= 0)
|
||||
return;
|
||||
|
||||
// Decrypted 2: [IP] [PK] [Encrypted 3]
|
||||
if (static_cast<size_t>(dlen) <= offset + CRYPTO_MAC_SIZE)
|
||||
return;
|
||||
ephem_pk = decrypted2.data() + SIZE_IPPORT;
|
||||
encrypt_precompute(ephem_pk, bob_sk, shared_key);
|
||||
|
||||
std::vector<uint8_t> decrypted3(dlen - offset - CRYPTO_MAC_SIZE);
|
||||
dlen = decrypt_data_symmetric(
|
||||
mem, shared_key, nonce, decrypted2.data() + offset, dlen - offset, decrypted3.data());
|
||||
if (dlen <= 0)
|
||||
return;
|
||||
|
||||
// Decrypted 3: [IP] [Data]
|
||||
size_t data_offset = SIZE_IPPORT;
|
||||
if (static_cast<size_t>(dlen) <= data_offset)
|
||||
return;
|
||||
uint8_t *req = decrypted3.data() + data_offset;
|
||||
size_t req_len = dlen - data_offset;
|
||||
|
||||
// Announce Request: [131] [Nonce] [Alice PK] [Encrypted]
|
||||
if (req[0] != 0x87 && req[0] != 0x83)
|
||||
return;
|
||||
|
||||
uint8_t req_nonce[CRYPTO_NONCE_SIZE];
|
||||
memcpy(req_nonce, req + 1, CRYPTO_NONCE_SIZE);
|
||||
uint8_t alice_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
memcpy(alice_pk, req + 1 + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE);
|
||||
|
||||
uint8_t *req_enc = req + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE;
|
||||
size_t req_enc_len = req_len - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE);
|
||||
|
||||
std::vector<uint8_t> req_plain(req_enc_len - CRYPTO_MAC_SIZE);
|
||||
int plen = decrypt_data(
|
||||
mem, alice_pk, bob_sk, req_nonce, req_enc, req_enc_len, req_plain.data());
|
||||
|
||||
if (plen <= 0)
|
||||
return;
|
||||
|
||||
// Payload: [Ping ID (32)] [Search ID (32)] [Data PK (32)] [Sendback (Rest)]
|
||||
size_t sendback_offset = 32 + 32 + 32;
|
||||
if (static_cast<size_t>(plen) < sendback_offset + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH)
|
||||
return;
|
||||
uint8_t *sendback = req_plain.data() + sendback_offset;
|
||||
size_t sendback_len = ONION_ANNOUNCE_SENDBACK_DATA_LENGTH;
|
||||
|
||||
// Construct Malicious Response
|
||||
std::vector<uint8_t> resp;
|
||||
resp.push_back(NET_PACKET_ANNOUNCE_RESPONSE);
|
||||
resp.insert(resp.end(), sendback, sendback + sendback_len);
|
||||
|
||||
uint8_t resp_nonce[CRYPTO_NONCE_SIZE];
|
||||
random_nonce(alice.get_random(), resp_nonce);
|
||||
resp.insert(resp.end(), resp_nonce, resp_nonce + CRYPTO_NONCE_SIZE);
|
||||
|
||||
// Encrypted Payload: [is_stored (1)] [ping_id (32)]
|
||||
// Total 33 bytes. OMIT count.
|
||||
std::vector<uint8_t> payload(33, 0);
|
||||
|
||||
std::vector<uint8_t> ciphertext(33 + CRYPTO_MAC_SIZE);
|
||||
encrypt_data(mem, alice_pk, bob_sk, resp_nonce, payload.data(), 33, ciphertext.data());
|
||||
|
||||
resp.insert(resp.end(), ciphertext.begin(), ciphertext.end());
|
||||
|
||||
// Send to Alice
|
||||
bob_socket->sendto(resp.data(), resp.size(), &from);
|
||||
triggered = true;
|
||||
});
|
||||
|
||||
// Run simulation
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
env.advance_time(50);
|
||||
alice.poll();
|
||||
bob.poll();
|
||||
if (triggered) {
|
||||
// Give Alice time to process the malicious packet
|
||||
env.advance_time(50);
|
||||
alice.poll();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(triggered) << "Failed to trigger vulnerability (Alice didn't send announce request "
|
||||
"or we failed to parse it)";
|
||||
|
||||
// Check if the packet was accepted
|
||||
// If accepted, last_packet_recv should be updated
|
||||
uint64_t final_recv_time = onion_testonly_get_last_packet_recv(alice.get_onion_client());
|
||||
|
||||
// IF the vulnerability is present, the code accepts the packet and updates last_packet_recv.
|
||||
// We want the test to FAIL if vulnerability is present.
|
||||
// So we Assert that the packet was NOT accepted.
|
||||
EXPECT_EQ(initial_recv_time, final_recv_time)
|
||||
<< "Vulnerability Present: Malformed packet was accepted!";
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, DISABLED_IntegerOverflowNumFriends)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
std::memset(friend_pk, 0, sizeof(friend_pk));
|
||||
|
||||
// Add 65536 friends to trigger integer overflow of uint16_t num_friends
|
||||
// This loop demonstrates that we can add enough friends to wrap the counter.
|
||||
for (int i = 0; i < 65536; ++i) {
|
||||
// Ensure unique public key
|
||||
std::memcpy(friend_pk, &i, sizeof(int));
|
||||
|
||||
int res = onion_addfriend(alice.get_onion_client(), friend_pk);
|
||||
if (res == -1) {
|
||||
FAIL() << "Failed to add friend " << i << ". May be out of memory.";
|
||||
}
|
||||
}
|
||||
|
||||
// After 65536 adds, num_friends should be 65536 (no overflow with uint32_t)
|
||||
EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 65536);
|
||||
|
||||
// Add one more friend with a GUARANTEED unique key.
|
||||
// Previous keys only modified first 4 bytes.
|
||||
// Setting the last byte ensures uniqueness against all 65536 previous keys.
|
||||
std::memset(friend_pk, 0, sizeof(friend_pk));
|
||||
friend_pk[CRYPTO_PUBLIC_KEY_SIZE - 1] = 1;
|
||||
|
||||
int res = onion_addfriend(alice.get_onion_client(), friend_pk);
|
||||
EXPECT_EQ(res, 65536) << "Failed to add extra friend. Got index: " << res;
|
||||
EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 65537);
|
||||
|
||||
// Now demonstrate access is safe.
|
||||
Onion_Friend *f = onion_get_friend(alice.get_onion_client(), 65536);
|
||||
// Dereferencing 'f' triggers ASAN error if OOB.
|
||||
onion_friend_get_gc_public_key(f);
|
||||
}
|
||||
|
||||
TEST_F(OnionClientTest, OnionAnnounceResponse_TooShort)
|
||||
{
|
||||
OnionNode alice(env, 33445);
|
||||
logger_callback_log(alice.get_logger(), OnionClientTest::print_log, nullptr, nullptr);
|
||||
WrappedDHT bob(env, 12345);
|
||||
logger_callback_log(bob.logger(), OnionClientTest::print_log, nullptr, nullptr);
|
||||
FakeUdpSocket *bob_socket = bob.node().endpoint;
|
||||
|
||||
IP_Port bob_ip = bob.get_ip_port();
|
||||
const uint8_t *bob_pk = bob.dht_public_key();
|
||||
const uint8_t *bob_sk = bob.dht_secret_key();
|
||||
|
||||
dht_bootstrap(alice.get_dht(), &bob_ip, bob_pk);
|
||||
onion_add_bs_path_node(alice.get_onion_client(), &bob_ip, bob_pk);
|
||||
|
||||
uint64_t initial_recv_time = onion_testonly_get_last_packet_recv(alice.get_onion_client());
|
||||
bool triggered = false;
|
||||
|
||||
// Setup Memory
|
||||
Tox_Memory mem_struct = env.fake_memory().get_c_memory();
|
||||
const Memory *mem = &mem_struct;
|
||||
|
||||
bob_socket->set_recv_observer([&](const std::vector<uint8_t> &data, const IP_Port &from) {
|
||||
if (triggered)
|
||||
return;
|
||||
if (data.size() < 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)
|
||||
return;
|
||||
|
||||
if (data[0] != NET_PACKET_ONION_SEND_INITIAL)
|
||||
return;
|
||||
|
||||
uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE];
|
||||
uint8_t nonce[CRYPTO_NONCE_SIZE];
|
||||
memcpy(nonce, data.data() + 1, CRYPTO_NONCE_SIZE);
|
||||
const uint8_t *ephem_pk = data.data() + 1 + CRYPTO_NONCE_SIZE;
|
||||
|
||||
encrypt_precompute(ephem_pk, bob_sk, shared_key);
|
||||
|
||||
std::vector<uint8_t> decrypted1(
|
||||
data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE));
|
||||
int dlen = decrypt_data_symmetric(mem, shared_key, nonce,
|
||||
data.data() + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE,
|
||||
data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), decrypted1.data());
|
||||
if (dlen <= 0)
|
||||
return;
|
||||
|
||||
size_t offset = SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE;
|
||||
if (static_cast<size_t>(dlen) <= offset + CRYPTO_MAC_SIZE)
|
||||
return;
|
||||
ephem_pk = decrypted1.data() + SIZE_IPPORT;
|
||||
|
||||
encrypt_precompute(ephem_pk, bob_sk, shared_key);
|
||||
|
||||
std::vector<uint8_t> decrypted2(dlen - offset - CRYPTO_MAC_SIZE);
|
||||
dlen = decrypt_data_symmetric(
|
||||
mem, shared_key, nonce, decrypted1.data() + offset, dlen - offset, decrypted2.data());
|
||||
if (dlen <= 0)
|
||||
return;
|
||||
|
||||
if (static_cast<size_t>(dlen) <= offset + CRYPTO_MAC_SIZE)
|
||||
return;
|
||||
ephem_pk = decrypted2.data() + SIZE_IPPORT;
|
||||
encrypt_precompute(ephem_pk, bob_sk, shared_key);
|
||||
|
||||
std::vector<uint8_t> decrypted3(dlen - offset - CRYPTO_MAC_SIZE);
|
||||
dlen = decrypt_data_symmetric(
|
||||
mem, shared_key, nonce, decrypted2.data() + offset, dlen - offset, decrypted3.data());
|
||||
if (dlen <= 0)
|
||||
return;
|
||||
|
||||
size_t data_offset = SIZE_IPPORT;
|
||||
if (static_cast<size_t>(dlen) <= data_offset)
|
||||
return;
|
||||
uint8_t *req = decrypted3.data() + data_offset;
|
||||
size_t req_len = dlen - data_offset;
|
||||
|
||||
if (req[0] != 0x87 && req[0] != 0x83)
|
||||
return;
|
||||
|
||||
uint8_t req_nonce[CRYPTO_NONCE_SIZE];
|
||||
memcpy(req_nonce, req + 1, CRYPTO_NONCE_SIZE);
|
||||
uint8_t alice_pk[CRYPTO_PUBLIC_KEY_SIZE];
|
||||
memcpy(alice_pk, req + 1 + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE);
|
||||
|
||||
uint8_t *req_enc = req + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE;
|
||||
size_t req_enc_len = req_len - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE);
|
||||
|
||||
std::vector<uint8_t> req_plain(req_enc_len - CRYPTO_MAC_SIZE);
|
||||
int plen = decrypt_data(
|
||||
mem, alice_pk, bob_sk, req_nonce, req_enc, req_enc_len, req_plain.data());
|
||||
|
||||
if (plen <= 0)
|
||||
return;
|
||||
|
||||
size_t sendback_offset = 32 + 32 + 32;
|
||||
if (static_cast<size_t>(plen) < sendback_offset + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH)
|
||||
return;
|
||||
uint8_t *sendback = req_plain.data() + sendback_offset;
|
||||
size_t sendback_len = ONION_ANNOUNCE_SENDBACK_DATA_LENGTH;
|
||||
|
||||
std::vector<uint8_t> resp;
|
||||
resp.push_back(NET_PACKET_ANNOUNCE_RESPONSE);
|
||||
resp.insert(resp.end(), sendback, sendback + sendback_len);
|
||||
|
||||
uint8_t resp_nonce[CRYPTO_NONCE_SIZE];
|
||||
random_nonce(alice.get_random(), resp_nonce);
|
||||
resp.insert(resp.end(), resp_nonce, resp_nonce + CRYPTO_NONCE_SIZE);
|
||||
|
||||
// PAYLOAD SIZE 33 (1 + 32)
|
||||
// This is exactly what triggers the missing byte read for nodes_count
|
||||
std::vector<uint8_t> payload(33, 0);
|
||||
|
||||
std::vector<uint8_t> ciphertext(payload.size() + CRYPTO_MAC_SIZE);
|
||||
encrypt_data(
|
||||
mem, alice_pk, bob_sk, resp_nonce, payload.data(), payload.size(), ciphertext.data());
|
||||
|
||||
resp.insert(resp.end(), ciphertext.begin(), ciphertext.end());
|
||||
|
||||
bob_socket->sendto(resp.data(), resp.size(), &from);
|
||||
triggered = true;
|
||||
});
|
||||
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
env.advance_time(50);
|
||||
alice.poll();
|
||||
bob.poll();
|
||||
if (triggered) {
|
||||
env.advance_time(50);
|
||||
alice.poll();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(triggered);
|
||||
EXPECT_EQ(onion_testonly_get_last_packet_recv(alice.get_onion_client()), initial_recv_time);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user