Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for fetching and submitting transactions over Tor #193

Open
wants to merge 2 commits into
base: release/0.13.0
Choose a base branch
from

Conversation

str4d
Copy link
Contributor

@str4d str4d commented Feb 14, 2025

No description provided.

Comment on lines +3553 to +3554
/// The two `TorRuntime`s will share internal state and configuration, but their streams
/// will never share circuits with one another.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed opportunity for a "don't cross the streams!" joke ;-)

Comment on lines +3565 to +3566
/// - `tor_runtime` must be non-null and point to a struct having the layout of
/// [`TorRuntime`].
Copy link
Contributor

@daira daira Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that sufficient? I don't think it would be ok to memcpy a TorRuntime and pass a pointer to the copy; I think it has to be a pointer returned by an API function with return type *mut TorRuntime (that has not subsequently been freed).

Same comment for all uses of the "point to a struct having the layout of" wording.

Copy link
Contributor Author

@str4d str4d Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safety reqirements here are those necessary for the unsafe { tor_runtime.as_mut() } below to be safe. In particular, if non-null it must be convertible to a reference.

AFAICT it would be perfectly fine to memcpy a TorRuntime as long as you then discard the old one, which in Rust would be equivalent to a move. If you memcpy and then attempt to use both, that would be equivalent to impl Copy for TorRuntime which is disallowed because Arc does not implement Copy (because any copy cannot be purely bitwise due to also needing to update reference counters).

But that has no bearing on this method: as long as it can construct a valid &mut TorRuntime then it will work as long as the underlying TorRuntime has the correct layout and you don't try to use both copies simultaneously (as per the safety requirements). The problem you envisage specifically arises if you:

  • Obtain a *mut TorRuntime
  • memcpy it in some way to obtain a second *mut TorRuntime.
  • Call zcashlc_free_tor_runtime with both, which will cause a double-free (or more likely other problems because the memcpyed one was not allocated with the Rust allocator).

So I think the only change I'd make here is independent of this PR: document on all zcashlc_free_* methods where their arguments must have come from.

Copy link
Contributor

@daira daira Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT it would be perfectly fine to memcpy a TorRuntime as long as you then discard the old one, which in Rust would be equivalent to a move.

Agreed: the copy operation itself is not UB; it's only using the copy that is unsafe. But I don't agree that you can free or discard the original and safely use the copy (including freeing the copy). That's not safe in general for opaque types. You can only safely use it via the pointer you were originally given.

Reason 1: If the structure (directly or indirectly) contains pointers into itself, those pointers would be invalidated by effectively moving it.

Reason 2: It would be UB to use with_exposed_provenance[_mut] to reconstruct a pointer into such a copied type, after only exposing a pointer into the original. (Regardless of whether the current code in the Swift SDK does this, it might need to do it in future, as the corresponding Android PR does.)

Copy link
Contributor

@daira daira Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contrast with https://github.com/Electric-Coin-Company/zcash-light-client-ffi/pull/196/files#r1966356159 where the "must point to a struct having the layout of" wording is sufficient because we know that the struct is "plain old data". Here, the TorRuntime struct contains a tor::Client which we don't know anything about (without peering into its implementation for a given version of the tor crate).

Comment on lines +3574 to +3577
// SAFETY: We ensure unwind safety by:
// - using `*mut TorRuntime` and respecting mutability rules on the Swift side, to
// avoid observing the effects of a panic in another thread.
// - discarding the `TorRuntime` whenever we get an error that is due to a panic.
Copy link
Contributor

@daira daira Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safety documentation doesn't say that the caller has to discard the TorRuntime pointer on error.

Same comment for all similar unwind safety justifications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This safety documentation comes from the PR in which I added exchange rate fetching (#142). Unfortunately I can't find where I determined that this was the way to ensure unwind safety for *mut T. AFAICT the Swift caller is not discarding the TorRuntime in existing usage when getting an error that is due to a panic; I don't recall whether I meant here that tor_runtime is not used outside of catch_panic, or whether I did indeed intend for us to treat it that way in Swift and we're not doing so. The fact that I referenced Swift in the first point but not the second means I might have meant the former?

In any case, what we're doing here is the same as what we're doing everywhere else, so I've opened #194 for reviewing what we currently do and ensuring it is correct, or fixing it if not.

Copy link
Contributor

@daira daira Feb 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT the Swift caller is not discarding the TorRuntime in existing usage when getting an error that is due to a panic [...]

Indeed, and I think that means it is not panic-safe. [Moved the rest of this comment to #194 so that we're not having the conversation in two places, but note that one of the possible solutions —the one that is less complicated to implement and more typesafe— requires changing the API.]

Copy link
Contributor

@daira daira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking comments about safety documentation.

Comment on lines +3580 to +3587
let res = catch_panic(|| {
let tor_runtime =
unsafe { tor_runtime.as_mut() }.ok_or_else(|| anyhow!("A Tor runtime is required"))?;

let isolated_client = tor_runtime.isolated_client();

Ok(Box::into_raw(Box::new(isolated_client)))
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants