MonerochanNews

rss
Guides Opinions Interviews

The Monero-oxide bug bounty should take privacy seriously

Recently, a bug bounty program was announced for the Monero-oxide project. The bounty is sponsored by a new organization in the Monero ecosystem called Privacy Powerup. Ironically, the bug bounty declares privacy leaks for the most common Monero user out of scope. Here is what should be done.

By Spirobel

Signing away your privacy can be a lot more dangerous than a loss of funds. Life and liberty are at stake.

For a decentralized exchange like Serai, the threat model is different though. No life or liberty to protect in this case, only funds. Monero-oxide is a Monero wallet library that was initially developed for Serai, but is now aspiring to be a Monero public good. To become a true ecosystem project it needs to serve the needs of all wallet authors. The current state of the bug bounty program reflects that this aspiration has not been met yet.

The danger is that there is a mismatch between the threat model of the Monero-oxide project and the threat model of the average Monero user.

The vast majority of Monero users use remote nodes. This will remain the case for the foreseeable future. It is something we have to work with. We cannot just define it as out of scope when it affects the privacy of the average user and therefore all of us. Even if you run your own node, you still cant blend in as a clothed person if the crowd is naked.

There is a common misconception, that is sadly shared by the current Monero-oxide leadership: When using a remote node, your expectation of privacy is severely degraded and any defense against that is “best effort” in nature.

This is wrong. The node operator cannot force you into signing a transaction or engage in a pattern of communication that would reveal your true spent output. Currently the most commonly used Monero wallet library throws an error and the recommendation by Sarang Noether in the Breaking Monero episode on remote nodes is to disconnect and reconnect to a different node that is non faulty.

It does not matter if the node operator is malicious or just faulty. A faulty node is useless in any case, so there is no point in trying to work with it. The Monero-oxide library on the other hand, made the mistake of communicating the real spent output in every case.

How did I find out about this?

Note: You can skip to the next section if you want to avoid the technical details.

My journey into this started when I discovered two nested while loops in the decoy selection function of the Monero-oxide library. As my displeasure grew with these two nested while loops, I started to wonder if they could be exploited.

The outer while loop does the exact opposite of what Sarang recommends: It tries to work with a faulty node instead of disconnecting from it. The attack I wanted to investigate goes like this:

On the first try the real output will be included (so the node can not tell which one it is after seeing the real transaction later on chain). Can we feed the wallet bogus data so it has to ask for more possible decoys and therefore we can deduce the real spent output? Remember: the real spend will only be included in the first attempt to select decoys.

While I worked on the proof of concept, I realized something even worse:

whileloop.png

Can you see the iters += 1 directly at the beginning of the outer while loop? I couldn’t believe it at first, but later on the condition to include the real spent in the requested decoys checks against iters == 0. So this condition is never met and the real spent and therefore the transaction graph of everyone using remote nodes is revealed to the node operator. (or people with access to the node operators servers)

The Monero-oxide leadership pushed an incomplete fix to these two issues and declared all of this as out of scope for the bug bounty. The fix included a first_iter boolean to ensure the real spent is included in the first attempt to select decoys. Their attempt at fixing the issue of the real spent being revealed through forcing the second iteration of the outer while loop was to throw an error in case the real spent is not included in the first response from the node.

incomplete_fix.png

In theory this should have fixed the issue, but after looking at the fix I discovered that the for loop over the node response can be cut short by returning an empty array of outputs. So again the check is bypassed and the real spent revealed.

For the end user there is no difference between a normal node seeing the real spent output because it wasn’t included in the first request or first receiving an empty array of outputs and communicating the real spent by later including it in a transaction that contains only known decoys.

This is a result of ignoring Sarangs advice to disconnect from faulty nodes and throw an error instead of trying to work with them.

Additional context (as requested by the Monero-oxide leadership): goodwill payment of $1000 was offered and paid for the initial report plus $500 for the get_outs concern.

What is to be done?

We need to make sure that trying to spend with a remote node in a privacy-preserving way only ever becomes a Denial of Service vector.

This is the status quo with the current monero wallet library and we should keep it that way. Here is my recommendation to uphold this as Monero-oxide gains more adoption:

  1. Clarify the threat model of the Monero-oxide library and make sure it includes the privacy of the average Monero user. One way to achieve this, is to state that the current impact scope of “Signing of unintended messages” includes privacy leaks for the average user. It should be split into critical and high severity levels. The high severity level is for privacy leaks towards the node, while critical means to any passive observer.
  2. Make sure wallet authors become formal stakeholders of this library. Currently we have a node developer and a dex developer as stakeholders. Wallet developers should be included as well to ensure there is a balance between the different threat models.