Merge pull request #5640

542cab02 rpc: restrict the recent cutoff size in restricted RPC mode (moneromooo-monero)
434e617a ensure no NULL is passed to memcpy (moneromooo-monero)
279f1f2c abstract_tcp_server2: improve DoS resistance (moneromooo-monero)
756773e5 serialization: check stream good flag at the end (moneromooo-monero)
e3f714aa tree-hash: allocate variable memory on heap, not stack (moneromooo-monero)
67baa3a6 cryptonote: throw on tx hash calculation error (moneromooo-monero)
d6bb9ecc serialization: fail on read_varint error (moneromooo-monero)
19490e44 cryptonote_protocol: fix another potential P2P DoS (moneromooo-monero)
fa4aa47e cryptonote_protocol: expand basic DoS protection (moneromooo-monero)
3c953d53 cryptonote_protocol_handler: prevent potential DoS (anonimal)
b873b69d epee: basic sanity check on allocation size from untrusted source (moneromooo-monero)
This commit is contained in:
Riccardo Spagni 2019-06-14 16:08:58 +02:00
commit c58255ec12
No known key found for this signature in database
GPG key ID: 55432DF31CCD4FCD
15 changed files with 78 additions and 28 deletions

View file

@ -54,6 +54,9 @@
#undef MONERO_DEFAULT_LOG_CATEGORY #undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net" #define MONERO_DEFAULT_LOG_CATEGORY "net"
#define AGGRESSIVE_TIMEOUT_THRESHOLD 120 // sockets
#define NEW_CONNECTION_TIMEOUT_LOCAL 1200000 // 2 minutes
#define NEW_CONNECTION_TIMEOUT_REMOTE 10000 // 10 seconds
#define DEFAULT_TIMEOUT_MS_LOCAL 1800000 // 30 minutes #define DEFAULT_TIMEOUT_MS_LOCAL 1800000 // 30 minutes
#define DEFAULT_TIMEOUT_MS_REMOTE 300000 // 5 minutes #define DEFAULT_TIMEOUT_MS_REMOTE 300000 // 5 minutes
#define TIMEOUT_EXTRA_MS_PER_BYTE 0.2 #define TIMEOUT_EXTRA_MS_PER_BYTE 0.2
@ -189,7 +192,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
m_protocol_handler.after_init_connection(); m_protocol_handler.after_init_connection();
reset_timer(get_default_timeout(), false); reset_timer(boost::posix_time::milliseconds(m_local ? NEW_CONNECTION_TIMEOUT_LOCAL : NEW_CONNECTION_TIMEOUT_REMOTE), false);
// first read on the raw socket to detect SSL for the server // first read on the raw socket to detect SSL for the server
buffer_ssl_init_fill = 0; buffer_ssl_init_fill = 0;
@ -691,7 +694,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
{ {
unsigned count; unsigned count;
try { count = host_count(m_host); } catch (...) { count = 0; } try { count = host_count(m_host); } catch (...) { count = 0; }
const unsigned shift = std::min(std::max(count, 1u) - 1, 8u); const unsigned shift = get_state().sock_count > AGGRESSIVE_TIMEOUT_THRESHOLD ? std::min(std::max(count, 1u) - 1, 8u) : 0;
boost::posix_time::milliseconds timeout(0); boost::posix_time::milliseconds timeout(0);
if (m_local) if (m_local)
timeout = boost::posix_time::milliseconds(DEFAULT_TIMEOUT_MS_LOCAL >> shift); timeout = boost::posix_time::milliseconds(DEFAULT_TIMEOUT_MS_LOCAL >> shift);
@ -730,8 +733,6 @@ PRAGMA_WARNING_DISABLE_VS(4355)
template<class t_protocol_handler> template<class t_protocol_handler>
void connection<t_protocol_handler>::reset_timer(boost::posix_time::milliseconds ms, bool add) void connection<t_protocol_handler>::reset_timer(boost::posix_time::milliseconds ms, bool add)
{ {
if (m_connection_type != e_connection_type_RPC)
return;
MTRACE("Setting " << ms << " expiry"); MTRACE("Setting " << ms << " expiry");
auto self = safe_shared_from_this(); auto self = safe_shared_from_this();
if(!self) if(!self)

View file

@ -136,6 +136,7 @@ namespace epee
//for pod types //for pod types
array_entry_t<type_name> sa; array_entry_t<type_name> sa;
size_t size = read_varint(); size_t size = read_varint();
CHECK_AND_ASSERT_THROW_MES(size <= m_count, "Size sanity check failed");
sa.reserve(size); sa.reserve(size);
//TODO: add some optimization here later //TODO: add some optimization here later
while(size--) while(size--)

View file

@ -64,6 +64,7 @@ void buffer::append(const void *data, size_t sz)
size_t reserve = (((size() + sz) * 3 / 2) + 4095) & ~4095; size_t reserve = (((size() + sz) * 3 / 2) + 4095) & ~4095;
new_storage.reserve(reserve); new_storage.reserve(reserve);
new_storage.resize(size()); new_storage.resize(size());
if (size() > 0)
memcpy(new_storage.data(), storage.data() + offset, storage.size() - offset); memcpy(new_storage.data(), storage.data() + offset, storage.size() - offset);
offset = 0; offset = 0;
std::swap(storage, new_storage); std::swap(storage, new_storage);

View file

@ -62,12 +62,14 @@ wipeable_string::wipeable_string(wipeable_string &&other)
wipeable_string::wipeable_string(const std::string &other) wipeable_string::wipeable_string(const std::string &other)
{ {
grow(other.size()); grow(other.size());
if (size() > 0)
memcpy(buffer.data(), other.c_str(), size()); memcpy(buffer.data(), other.c_str(), size());
} }
wipeable_string::wipeable_string(std::string &&other) wipeable_string::wipeable_string(std::string &&other)
{ {
grow(other.size()); grow(other.size());
if (size() > 0)
memcpy(buffer.data(), other.c_str(), size()); memcpy(buffer.data(), other.c_str(), size());
if (!other.empty()) if (!other.empty())
{ {
@ -79,6 +81,7 @@ wipeable_string::wipeable_string(std::string &&other)
wipeable_string::wipeable_string(const char *s) wipeable_string::wipeable_string(const char *s)
{ {
grow(strlen(s)); grow(strlen(s));
if (size() > 0)
memcpy(buffer.data(), s, size()); memcpy(buffer.data(), s, size());
} }
@ -112,15 +115,19 @@ void wipeable_string::grow(size_t sz, size_t reserved)
} }
size_t old_sz = buffer.size(); size_t old_sz = buffer.size();
std::unique_ptr<char[]> tmp{new char[old_sz]}; std::unique_ptr<char[]> tmp{new char[old_sz]};
memcpy(tmp.get(), buffer.data(), old_sz * sizeof(char));
if (old_sz > 0) if (old_sz > 0)
{
memcpy(tmp.get(), buffer.data(), old_sz * sizeof(char));
memwipe(buffer.data(), old_sz * sizeof(char)); memwipe(buffer.data(), old_sz * sizeof(char));
}
buffer.reserve(reserved); buffer.reserve(reserved);
buffer.resize(sz); buffer.resize(sz);
memcpy(buffer.data(), tmp.get(), old_sz * sizeof(char));
if (old_sz > 0) if (old_sz > 0)
{
memcpy(buffer.data(), tmp.get(), old_sz * sizeof(char));
memwipe(tmp.get(), old_sz * sizeof(char)); memwipe(tmp.get(), old_sz * sizeof(char));
} }
}
void wipeable_string::push_back(char c) void wipeable_string::push_back(char c)
{ {

View file

@ -1077,11 +1077,11 @@ void BlockchainLMDB::add_tx_amount_output_indices(const uint64_t tx_id,
int result = 0; int result = 0;
int num_outputs = amount_output_indices.size(); size_t num_outputs = amount_output_indices.size();
MDB_val_set(k_tx_id, tx_id); MDB_val_set(k_tx_id, tx_id);
MDB_val v; MDB_val v;
v.mv_data = (void *)amount_output_indices.data(); v.mv_data = num_outputs ? (void *)amount_output_indices.data() : (void*)"";
v.mv_size = sizeof(uint64_t) * num_outputs; v.mv_size = sizeof(uint64_t) * num_outputs;
// LOG_PRINT_L1("tx_outputs[tx_hash] size: " << v.mv_size); // LOG_PRINT_L1("tx_outputs[tx_hash] size: " << v.mv_size);

View file

@ -116,6 +116,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen)
local_abort("Bad keccak use"); local_abort("Bad keccak use");
} }
if (inlen > 0)
memcpy(temp, in, inlen); memcpy(temp, in, inlen);
temp[inlen++] = 1; temp[inlen++] = 1;
memset(temp + inlen, 0, rsiz - inlen); memset(temp + inlen, 0, rsiz - inlen);

View file

@ -30,6 +30,7 @@
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "hash-ops.h" #include "hash-ops.h"
@ -82,23 +83,24 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) {
size_t cnt = tree_hash_cnt( count ); size_t cnt = tree_hash_cnt( count );
char ints[cnt][HASH_SIZE]; char *ints = calloc(cnt, HASH_SIZE); // zero out as extra protection for using uninitialized mem
memset(ints, 0 , sizeof(ints)); // zero out as extra protection for using uninitialized mem assert(ints);
memcpy(ints, hashes, (2 * cnt - count) * HASH_SIZE); memcpy(ints, hashes, (2 * cnt - count) * HASH_SIZE);
for (i = 2 * cnt - count, j = 2 * cnt - count; j < cnt; i += 2, ++j) { for (i = 2 * cnt - count, j = 2 * cnt - count; j < cnt; i += 2, ++j) {
cn_fast_hash(hashes[i], 64, ints[j]); cn_fast_hash(hashes[i], 64, ints + j * HASH_SIZE);
} }
assert(i == count); assert(i == count);
while (cnt > 2) { while (cnt > 2) {
cnt >>= 1; cnt >>= 1;
for (i = 0, j = 0; j < cnt; i += 2, ++j) { for (i = 0, j = 0; j < cnt; i += 2, ++j) {
cn_fast_hash(ints[i], 64, ints[j]); cn_fast_hash(ints + i * HASH_SIZE, 64, ints + j * HASH_SIZE);
} }
} }
cn_fast_hash(ints[0], 64, root_hash); cn_fast_hash(ints, 64, root_hash);
free(ints);
} }
} }

View file

@ -320,7 +320,7 @@ namespace cryptonote
} }
if (!typename Archive<W>::is_saving()) if (!typename Archive<W>::is_saving())
pruned = true; pruned = true;
return true; return ar.stream().good();
} }
private: private:

View file

@ -221,8 +221,7 @@ namespace cryptonote
tx.invalidate_hashes(); tx.invalidate_hashes();
//TODO: validate tx //TODO: validate tx
get_transaction_hash(tx, tx_hash); return get_transaction_hash(tx, tx_hash);
return true;
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash) bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash)
@ -975,6 +974,7 @@ namespace cryptonote
{ {
crypto::hash h = null_hash; crypto::hash h = null_hash;
get_transaction_hash(t, h, NULL); get_transaction_hash(t, h, NULL);
CHECK_AND_ASSERT_THROW_MES(get_transaction_hash(t, h, NULL), "Failed to calculate transaction hash");
return h; return h;
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
@ -1327,7 +1327,7 @@ namespace cryptonote
txs_ids.reserve(1 + b.tx_hashes.size()); txs_ids.reserve(1 + b.tx_hashes.size());
crypto::hash h = null_hash; crypto::hash h = null_hash;
size_t bl_sz = 0; size_t bl_sz = 0;
get_transaction_hash(b.miner_tx, h, bl_sz); CHECK_AND_ASSERT_THROW_MES(get_transaction_hash(b.miner_tx, h, bl_sz), "Failed to calculate transaction hash");
txs_ids.push_back(h); txs_ids.push_back(h);
for(auto& th: b.tx_hashes) for(auto& th: b.tx_hashes)
txs_ids.push_back(th); txs_ids.push_back(th);

View file

@ -52,6 +52,7 @@ PUSH_WARNINGS
DISABLE_VS_WARNINGS(4355) DISABLE_VS_WARNINGS(4355)
#define LOCALHOST_INT 2130706433 #define LOCALHOST_INT 2130706433
#define CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT 500
namespace cryptonote namespace cryptonote
{ {

View file

@ -809,12 +809,27 @@ namespace cryptonote
NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response; NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response;
fluffy_response.b.block = t_serializable_object_to_blob(b); fluffy_response.b.block = t_serializable_object_to_blob(b);
fluffy_response.current_blockchain_height = arg.current_blockchain_height; fluffy_response.current_blockchain_height = arg.current_blockchain_height;
std::vector<bool> seen(b.tx_hashes.size(), false);
for(auto& tx_idx: arg.missing_tx_indices) for(auto& tx_idx: arg.missing_tx_indices)
{ {
if(tx_idx < b.tx_hashes.size()) if(tx_idx < b.tx_hashes.size())
{ {
MDEBUG(" tx " << b.tx_hashes[tx_idx]); MDEBUG(" tx " << b.tx_hashes[tx_idx]);
if (seen[tx_idx])
{
LOG_ERROR_CCONTEXT
(
"Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX"
<< ", request is asking for duplicate tx "
<< ", tx index = " << tx_idx << ", block tx count " << b.tx_hashes.size()
<< ", block_height = " << arg.current_blockchain_height
<< ", dropping connection"
);
drop_connection(context, true, false);
return 1;
}
txids.push_back(b.tx_hashes[tx_idx]); txids.push_back(b.tx_hashes[tx_idx]);
seen[tx_idx] = true;
} }
else else
{ {
@ -914,6 +929,17 @@ namespace cryptonote
int t_cryptonote_protocol_handler<t_core>::handle_request_get_objects(int command, NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context) int t_cryptonote_protocol_handler<t_core>::handle_request_get_objects(int command, NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)
{ {
MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)"); MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)");
if (arg.blocks.size() + arg.txs.size() > CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT)
{
LOG_ERROR_CCONTEXT(
"Requested objects count is too big ("
<< arg.blocks.size() + arg.txs.size() << ") expected not more then "
<< CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT);
drop_connection(context, false, false);
return 1;
}
NOTIFY_RESPONSE_GET_OBJECTS::request rsp; NOTIFY_RESPONSE_GET_OBJECTS::request rsp;
if(!m_core.handle_get_objects(arg, rsp, context)) if(!m_core.handle_get_objects(arg, rsp, context))
{ {

View file

@ -252,7 +252,7 @@ namespace rct {
{ {
FIELD(type) FIELD(type)
if (type == RCTTypeNull) if (type == RCTTypeNull)
return true; return ar.stream().good();
if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2) if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2)
return false; return false;
VARINT_FIELD(txnFee) VARINT_FIELD(txnFee)
@ -312,7 +312,7 @@ namespace rct {
ar.delimit_array(); ar.delimit_array();
} }
ar.end_array(); ar.end_array();
return true; return ar.stream().good();
} }
}; };
struct rctSigPrunable { struct rctSigPrunable {
@ -325,7 +325,7 @@ namespace rct {
bool serialize_rctsig_prunable(Archive<W> &ar, uint8_t type, size_t inputs, size_t outputs, size_t mixin) bool serialize_rctsig_prunable(Archive<W> &ar, uint8_t type, size_t inputs, size_t outputs, size_t mixin)
{ {
if (type == RCTTypeNull) if (type == RCTTypeNull)
return true; return ar.stream().good();
if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2) if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2)
return false; return false;
if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2) if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2)
@ -429,7 +429,7 @@ namespace rct {
} }
ar.end_array(); ar.end_array();
} }
return true; return ar.stream().good();
} }
}; };

View file

@ -59,6 +59,8 @@ using namespace epee;
#define MAX_RESTRICTED_FAKE_OUTS_COUNT 40 #define MAX_RESTRICTED_FAKE_OUTS_COUNT 40
#define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 5000 #define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 5000
#define OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION (3 * 86400) // 3 days max, the wallet requests 1.8 days
namespace namespace
{ {
void add_reason(std::string &reasons, const char *reason) void add_reason(std::string &reasons, const char *reason)
@ -1882,6 +1884,13 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_HISTOGRAM>(invoke_http_mode::JON_RPC, "get_output_histogram", req, res, r)) if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_HISTOGRAM>(invoke_http_mode::JON_RPC, "get_output_histogram", req, res, r))
return r; return r;
const bool restricted = m_restricted && ctx;
if (restricted && req.recent_cutoff > 0 && req.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION)
{
res.status = "Recent cutoff is too old";
return true;
}
std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram;
try try
{ {

View file

@ -146,7 +146,8 @@ struct binary_archive<false> : public binary_archive_base<std::istream, false>
void serialize_uvarint(T &v) void serialize_uvarint(T &v)
{ {
typedef std::istreambuf_iterator<char> it; typedef std::istreambuf_iterator<char> it;
tools::read_varint(it(stream_), it(), v); // XXX handle failure if (tools::read_varint(it(stream_), it(), v) < 0)
stream_.setstate(std::ios_base::failbit);
} }
void begin_array(size_t &s) void begin_array(size_t &s)

View file

@ -212,7 +212,7 @@ inline bool do_serialize(Archive &ar, bool &v)
* \brief self-explanatory * \brief self-explanatory
*/ */
#define END_SERIALIZE() \ #define END_SERIALIZE() \
return true; \ return ar.stream().good(); \
} }
/*! \macro VALUE(f) /*! \macro VALUE(f)