simplewallet: plug a timing leak

As reported by Tramèr et al, timing of refresh requests can be used
to see whether a password was requested (and thus at least one output
received) since this will induce a delay in subsequent calls.
To avoid this, we schedule calls at a given time instead of sleeping
for a set time (which would make delays additive).
To further avoid a scheduled call being during the time in which a
password is prompted, the actual scheduled time is now randomized.
This commit is contained in:
moneromooo-monero 2019-10-15 10:30:50 +00:00
parent dcff02e4c3
commit 38f6910481
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
2 changed files with 32 additions and 11 deletions

View file

@ -8807,22 +8807,41 @@ void simple_wallet::check_for_messages()
//----------------------------------------------------------------------------------------------------
void simple_wallet::wallet_idle_thread()
{
const boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::universal_time();
while (true)
{
boost::unique_lock<boost::mutex> lock(m_idle_mutex);
if (!m_idle_run.load(std::memory_order_relaxed))
break;
#ifndef _WIN32
m_inactivity_checker.do_call(boost::bind(&simple_wallet::check_inactivity, this));
// if another thread was busy (ie, a foreground refresh thread), we'll end up here at
// some random time that's not what we slept for, so we should not call refresh now
// or we'll be leaking that fact through timing
const boost::posix_time::ptime now0 = boost::posix_time::microsec_clock::universal_time();
const uint64_t dt_actual = (now0 - start_time).total_microseconds() % 1000000;
#ifdef _WIN32
static const uint64_t threshold = 10000;
#else
static const uint64_t threshold = 2000;
#endif
m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this));
m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this));
m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this));
if (dt_actual < threshold) // if less than a threshold... would a very slow machine always miss it ?
{
#ifndef _WIN32
m_inactivity_checker.do_call(boost::bind(&simple_wallet::check_inactivity, this));
#endif
m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this));
m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this));
m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this));
if (!m_idle_run.load(std::memory_order_relaxed))
break;
m_idle_cond.wait_for(lock, boost::chrono::seconds(1));
if (!m_idle_run.load(std::memory_order_relaxed))
break;
}
// aim for the next multiple of 1 second
const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
const auto dt = (now - start_time).total_microseconds();
const auto wait = 1000000 - dt % 1000000;
m_idle_cond.wait_for(lock, boost::chrono::microseconds(wait));
}
}
//----------------------------------------------------------------------------------------------------

View file

@ -448,10 +448,12 @@ namespace cryptonote
std::atomic<bool> m_locked;
std::atomic<bool> m_in_command;
template<uint64_t mini, uint64_t maxi> struct get_random_interval { public: uint64_t operator()() const { return crypto::rand_range(mini, maxi); } };
epee::math_helper::once_a_time_seconds<1> m_inactivity_checker;
epee::math_helper::once_a_time_seconds<90> m_refresh_checker;
epee::math_helper::once_a_time_seconds<90> m_mms_checker;
epee::math_helper::once_a_time_seconds<90> m_rpc_payment_checker;
epee::math_helper::once_a_time_seconds_range<get_random_interval<80 * 1000000, 100 * 1000000>> m_refresh_checker;
epee::math_helper::once_a_time_seconds_range<get_random_interval<90 * 1000000, 110 * 1000000>> m_mms_checker;
epee::math_helper::once_a_time_seconds_range<get_random_interval<90 * 1000000, 115 * 1000000>> m_rpc_payment_checker;
std::atomic<bool> m_need_payment;
boost::posix_time::ptime m_last_rpc_payment_mining_time;