Imagine the scenario where the attacking client picks a rendezvous point that happens to be the hidden service's primary (first) entry guard. Then
The hidden service will look through its preemptively built circuits to see if it can extend any of them. circuit_find_to_cannibalize() will decide that none of the existing circuits are suitable, because they would cause a duplicate hop.
So the hidden service decides to make a new circuit. In populate_live_entry_guards() it compares node to chosen_exit and then exit_family, and skips it if there's overlap.
So the result is that even when NumEntryGuards is 1, the hidden service will end up using its second entry guard for this circuit. Surprising. This isn't terrible, since I don't see any way to induce it to contact a third entry guard, but still, two is more than one, especially when you thought you were getting only one. (Imagine an attack where the adversary has you rendezvous with every relay that has the Guard flag -- now you're guaranteed to step over to your second guard at least once.)
Where it gets messy is if the user explicitly set EntryNodes, like some hidden service operators think is wise, so they can use an entry node they trust. In that case entry_list_is_constrained() is true, so populate_live_entry_guards() will happily return an empty list if your one choice is inappropriate, resulting in choose_random_entry_impl() returning NULL. The external behavior is that if you send the hidden service the right rendezvous point, it never connects. That's kind of bad, especially since some people argue setting an explicit EntryNode is a wise move.
Now, learning about the hidden service's choice of primary guard isn't the end of the world, because there are other reasonably straightforward ways to enumerate the entry guard of a hidden service currently, e.g. the predecessor attack (run a middle relay, rendezvous with the hidden service many times, and see which prior hop shows up in the circuit the most times). But we'd like to fix those, e.g. by moving to a layered entry guard design, and if we do, this issue will remain.
Reported by 'ghetto' on irc.
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Child items ...
Show closed items
Linked items 0
Link issues together to show that they're related.
Learn more.
Sebastian and I pondered this one for a little while and our current thought is that the best way forward is to relax the requirement preventing duplicate hops in the circuit, in the rendezvous circuit case.
That is, if you're picking a guard for a rendezvous circuit, don't do those two comparisons in populate_live_entry_guards().
Now people who set EntryNode specifically will resume connecting. And those who don't set it will use their primary guard and thus not behave differently as a function of the rendezvous point.
It still makes us a bit nervous though because now the client can cause the hidden service to essentially make a short-circuited circuit to a relay that the client controls.
GuardA -> Middle -> GuardA is too bizarre for anyone monitoring the network and sends red flags.
2) Increase the hop by one just for this situation. GuardA -> Middle1 -> Middle2 -> GuardA
Actually Tor does this one already -- the path length is "three hops not counting ones that somebody else picked for you", so the hidden service makes GuardA -> Middle1 -> Middle2 and then extends that to the rendezvous point.
Edit:
4) Take the shortcut and talk to the rend point directly.
I thought of this last week and decided to try it. I hacked my tor client to always use a specific RP node and set that node as EntryNode for an HS I control. One single circuit and the client received a failure thus confirming the attack. There are ~3000 guards right now in the network, testing them all takes few minutes thus the guard discovery is serious (of course considering EntryNode being used).
I don't think by passing EntryNodes if defined is a good idea here. Apart from doing things in the background that the user explicitly asked not to do (bad), a far fetched example is that if the operator decided to firewall all nodes except the entry one and then Tor tries to connect to it and fails, well the attack is still usable. What I mean by this is that there are maybe external variables on why an operator sets EntryNodes thus we should respect it.
Accepting GuardA -> Middle1 -> Middle2 -> GuardA for rendezvous circuit seems to me the straight fix for a situation that is not really good right now.
We could go as far as denying the use of EntryNodes for an HS because of this serious security issue but that sounds maybe a bit too much.
After a discussion on IRC with nickm, we agree on a solution. This issue only happens if EntryNodes has only one single entry so we should trigger a warning in that case if you are an HS and add a comment in the EntryNodes documentation about this issue with one node.
Trigger a warning and fail the circuit? Or trigger a warning and do the four hop circuit as described above? I assume you mean the former. But isn't having only one EntryNode the safest, best recommendation we can have for when you're setting EntryNodes? Especially in the scenario where you run that relay or know its operator.
I worry that we're warning people away from what might actually be the best recommended behavior for some of them.
Trigger a warning and fail the circuit? Or trigger a warning and do the four hop circuit as described above? I assume you mean the former. But isn't having only one EntryNode the safest, best recommendation we can have for when you're setting EntryNodes? Especially in the scenario where you run that relay or know its operator.
No, it would be trigger a warning and don't fail. But now that I think of this that doesn't make much sense because between failing voluntarily or failing because we can't connect to the guard as the chosen exit is roughly the same... So this solution doesn't work.
I worry that we're warning people away from what might actually be the best recommended behavior for some of them.
If you are an HS and you set only one single EntryNodes, it's obvisouly not the recommended behavior for now.
We use one guard right now (primary) but we always have a secondary in case we have an unstable primary (and also to avoid this issue). With EntryNodes, this "secondary" guard is bypassed leading to that issue.
IMO, HS + one EntryNodes, you shouldn't be able to start tor at all, period. It's either that or we allow exiting at your guard which I don't think is ideal.
I'll try to summarize the above and have a pros/cons list for each possible solution. We assume an operator running an HS and EntryNode is set with a single entry. Please correct any wrong reasons, add any new ones and argue with some possible improvement.
Warning at startup + do NOT fail the circuit
Pros:
Relay operator is notified iff she is looking at the logs.
HS will be able to pin a single guard because one guard is recommended.
Cons:
Attack NOT mitigated.
Warning at startup + fail the circuit
Pros:
Relay operator is notified iff she is looking at the logs.
Cons:
Does not mitigate the attack at all because that's the current behavior without the warning. We can't exit at our entry point and we don't have a secondary guard.
Error at startup. Tor doesn't start and we tell operator why.
Pros:
Attack is mitigated
Cons:
Confuses the operator since one single guard is what's recommended?
Could break some HS configuration out there raising questions and paranoia (maybe good?)
Exit at your guard only for rendezvous point.
Pros:
Attack is mitigated
Not breaking any current configuration nor confusing operator.
Cons:
Bad for anonymity reason to exit at your entry? Could be maybe issues with timing?
Breaks tor path selection for a specific case which might be bad.
From IRC, we'll got with 3). I'll propose a patch soon hopefully.
< nickm> dgoulet: if you had to pick tomorrow, which option would you pick (for #14917)< dgoulet> nickm: "Error at startup. Tor doesn't start and we tell operator why.", it's either that or we allow exiting at our Guard which I'm unsure of the implication of that< nickm> what about "Error at startup. Tor doesn't start and we tell the operator why, and how to fix it"< dgoulet> nickm: ah well of course, error means lots of info to help op :) < dgoulet> nickm: with man page update also< nickm> dgoulet: okay. I think that 1-3 are mostly the same code. < nickm> dgoulet: and if you think 3 is best, 3 is probably better than nothing< nickm> so the only question is, is 3 better than 4?< nickm> I think it is.< nickm> so let's go with your idea there.< nickm> copy our discussion, minus other people's text, into the ticket, and code as seems best?
haven't read ticket, but from brief discussion with dgoulet, it sounds like we should roll out the conservative choice for now (I think that's warn and fail), and then call out the research questions from legacy/trac#4 (closed) so people don't forget about them. As opposed to picking the easy route, closing the ticket, and then pretending that there were no research questions here.
Feel free to change the log message with a cleaner better English :).
I have not added "bugfix on ..." in the change file since I think this is an issue since EntryNodes and hidden service have been coexisting. Not sure what version of Tor that would apply on :S ...
I discovered this thread while configuring an onion service with the EntryNodes option set. I believe (after checking the tor-0.2.9.8 source code) that a similar problem arises when the EntryNodes option is set AND the operator configures entry nodes that are part of the same family or the same /16. (let's say that the operator configures the service with its own guard nodes running in the same cloud provider, thinking this move is wise). Then this happens:
When someone use a RDV point of the same family or the same /16 than the onion's guards, then as you said: "entry_list_is_constrained() is true, so populate_live_entry_guards() will happily return an empty list if your one choice is inappropriate, resulting in choose_random_entry_impl() returning NULL".
Is there a reason why we do not check family, /16 and user misconfiguration ? "EntryNodes fingerprint1, fingerprint1" works just fine for example. What do you think ?
I'm a Masters student writing my thesis on de-anonymization. I've been attempting to make my hidden service use a specific entry node, however, an error occurs which pointed me to this thread. While looking into options, I discovered that by making my entry node a Tor bridge, I might be able to bypass these security features. This didn't work and ultimately sent me back to this thread. I was wondering if there was a tangible way to make my hidden service use my entry node. Whether it be by editing the torrc file or by injecting the nodes fingerprint in the hidden service directory.