Repository navigation
Conversation
arti 2.6 requires Rust 1.91.
Member
Author
|
Supercedes #33 , which only bumped us to the point at which the upstream Foundation-Devices/tor had at the time this was created. |
sneurlax
force-pushed
the
feat/bump-arti
branch
2 times, most recently
from
October 6, 2026 18:28
48acab2 to
5c3f182
Compare
tor_client_bootstrap() rebuilt the owning Box from the raw pointer Dart holds and let it drop on return, freeing the TorClient allocation while Dart kept using the address. tor_client_set_dormant() did the same dance but leaked the Box back, so it only worked as long as it was never preceded by a bootstrap call. Any set_dormant() after start() was a use after free; a bootstrap failure followed by stop() left a dangling handle. Borrow the handle in both functions instead and add tor_client_free() as the single place where ownership is taken back. The Dart wrapper frees the client when the proxy is stopped and nulls the pointer so the existing ClientNotActive guard covers later calls. Adds unit tests that exercise repeated calls through one handle, and a failed tor_client_bootstrap() followed by dormancy calls, each ending in an explicit free. The bootstrap test corrupts the directory cache after the client opens it, so bootstrap fails at once without network access. Both segfault against the previous implementation.
arti 1.4.6 -> 2.6.0, arti-client/tor-rtcompat/tor-config 0.33 -> 0.46.
socks::run_socks_proxy() binds its listeners inside the future, and that future ran in the task spawned by start_proxy(), so a bind failure (typically EADDRINUSE on the port Dart picked moments earlier) ended up as the JoinHandle's result and nothing ever looked at it. tor_start() still returned live handles, tor_client_bootstrap() reported success because it only checks the client, and Dart published a proxy port with nothing listening on it. Bind on the proxy runtime synchronously in tor_start(), set LAST_ERROR and return null handles when that fails, and hand the already bound listeners to socks::run_socks_proxy_with_listeners(). Dart already treats a null client as a start failure and surfaces the Rust error, so no wrapper change is needed. libc becomes a direct dependency for the EAFNOSUPPORT check that run_socks_proxy() does internally. Unit tests cover the occupied-port failure and that a successful bind holds the port until the listeners are dropped.
Lockfile-only, semver-compatible updates:
bytes 1.6.0 -> 1.12.1 RUSTSEC-2026-0007, yanked
crossbeam-channel 0.5.13 -> 0.5.17 RUSTSEC-2025-0024, yanked
openssl 0.10.64 -> 0.10.81 RUSTSEC-2024-0357, -2025-0004,
-2025-0022
openssl-sys 0.9.102 -> 0.9.117 required by openssl
openssl-src 3.3.1 -> 3.6.3 vendored OpenSSL used on Linux
and Android; 3.3.1 predates
several OpenSSL security releases
None of these looked reachable in a practical way from the plugin, but
the versions were flagged in review and two were yanked.
Reconcile the handle-lifetime and listener-readiness fixes with the arti 2.6.0 API: the client handle is Arc<TorClient<..>> here, the proxy entry point is arti::proxy::run_proxy_with_listeners() with ListenProtocols, and tor_rtcompat's listen() takes TcpListenOptions. Changelog entries move from Unreleased into 0.2.0.
Brings in the security-advisory lockfile update (bytes, crossbeam-channel, openssl, openssl-sys, openssl-src). The lockfile merged cleanly and resolves to the same versions as running the update directly on this branch.
sneurlax
force-pushed
the
feat/bump-arti
branch
2 times, most recently
from
October 6, 2026 21:34
8a5f047 to
0ef60cf
Compare
stop() now resets the status, port and handles, so Tor can be started again after it was stopped. Resetting the status alone let a start() that was still in flight finish after stop() and publish its handles over those of a newer start(), leaving a running Tor client and proxy that nothing could release. Run start() and stop() one at a time, in call order, through a queue: a stop() issued during a pending start() waits for it and then tears down what it started, and a start() issued after that stop() begins only once the stop has finished. Native calls go through a TorNative class that tests can replace, so the ordering tests run deterministically without Tor. The restart test against the real network runs when TOR_NETWORK_TESTS is set. Route greetings through the same injectable native layer and cover the substitution without loading the real library.
The locked num-bigint-dig 0.8.4 was incompatible with the compiler in use. Update it to 0.8.6 and refresh its checksum and resolved dependencies.
A start() made while another was in flight returned immediately because the status was already "starting", so its caller went on to read port before Tor was ready and got an exception. Keep the pending attempt and hand it to later start() callers, so they all complete when Tor is up or fail with the same error. A stop() clears the pending attempt, so a start() requested after it begins a fresh one instead of joining the attempt that stop() is about to tear down.
start() stored the client and proxy handles before bootstrapping. When bootstrap then failed it only set the status back to off, so the proxy kept running and the next start() overwrote both handles, leaking a Tor client nothing could free. Tear down whatever the native start produced before rethrowing.
disable() is documented to prevent traffic flowing through the proxy but only set the Dart status to off. The SOCKS listener kept accepting and relaying connections, so a caller that relied on disable() alone stayed on Tor while the status said otherwise. Make disable() an alias for stop(). It now returns a Future; existing callers that ignore the result, or follow it with stop() as Stack Wallet does, keep working because stop() is idempotent and queued.
Dart probed a free port before Rust bound it, allowing another process to claim it. Allocate the port while binding the IPv4 listener, reserve the same port on available IPv6 loopback, and return the bound port to Dart. Retry an OS-assigned port when another process holds its IPv6 twin, with a bounded error after four attempts. Never retry a caller-requested port. Recognize disabled IPv6 on Windows and fall back to IPv4 only when IPv6 is unavailable. Cover fallback decisions and occupied-port errors.
Both paths were allocated with toNativeUtf8() and never freed. tor_start() only needs them for the duration of the call (it copies them into the arti config), so free them in a finally once it returns, on success and on failure.
tor_last_error_message() hands out a CString allocated by Rust, but no function existed to give it back, so Dart copied the message and leaked the allocation on every reported error. Export tor_string_free() and call it from throwRustException() once the message has been copied. Bindings and header are regenerated. Adds a Rust round-trip test and a Dart test that drives a real tor_start() failure on an occupied port, which exercises this path without network access.
tor_last_error_message() unwrapped CString::new(), which fails on an interior NUL. The resulting panic would abort the process at the FFI boundary. Drop NULs before building the C string and add a Rust test.
util.rs uses update_last_error only in the nofile-limit functions, which are compiled out on Windows, so every Windows build warned about an unused import.
Tor.stop() called tor_client_free() synchronously on the calling isolate and did not wait for anything. Releasing the client is about to shut down its runtime, which can take a few seconds, so run it through Isolate.run() the way start already runs tor_start(), and complete stop() only once it returns. A failed start waits for the same cleanup before rethrowing. The test fake now completes freeClient() asynchronously, so the ordering tests fail if a caller stops waiting for it.
Unbounded bootstrap serialized stop behind network work, and incomplete teardown left clients alive and data directories locked. Give each client a cancellable deadline and an owned runtime, activate the proxy only after bootstrap, and join its accept loop before releasing the client. Join starts with identical raw arguments, resolve physical directories asynchronously inside the queue, and cancel every pending start on stop. Reuse a running instance only for its physical directory. Retain failed releases in order and retry before opening another client; keep status on until a running instance is released, or starting after failed startup cleanup. Never use lifecycle status as a proxy liveness monitor. Preserve typed bootstrap failures and their stacks if cleanup also fails, report secondary errors, and return the native cause chain without repeating embedded suffixes. Namespace C status constants, preserve numeric ABI and Dart names, and compile-check C/C++ interoperability. Include deterministic lifecycle, error-chain and header regressions, documentation and release notes describing the completed behavior.
sourceFingerprint() decoded every file under rust/src and rust/patches as UTF-8, so a stray .DS_Store failed the build. Source builds now use the fingerprint for their dependencies, which spread the failure from prebuilt builds to every build. Even a decodable stray file changed the hash and made a valid prebuilt manifest look mismatched. Skip entries with a hidden path segment, and hash raw bytes with CRLF folded to LF. The hash of valid sources is unchanged.
sneurlax
force-pushed
the
feat/bump-arti
branch
from
October 6, 2026 22:24
0ef60cf to
1661bba
Compare
Exclude vendored Cargo output from fingerprints and avoid hashing all Rust inputs during source builds while retaining full prebuilt verification.
Use the 0.47.0 Arti crates and Rust 1.92.0. Rebase the Windows clock-resolution fix onto saturating-time 0.5.0, which still needs the no-progress guard, and exercise 100ns rounding on every host. Verified Rust tests, patched-crate tests, Clippy, and 11 offline Flutter tests (one live-network test skipped); Dart analysis is clean. Do not pin tor-config directly; nothing in the crate uses it.
The example used the old socket API, duplicated initial startup, and left async lifecycle failures uncaught. Adopt socks_socket 2.0.0, await request writes and shutdown, and display start and stop exceptions for retry. Keep Stop available after failed release, disable requests during teardown, clear errors after success, and guard state updates after disposal. Cover write failures, lifecycle error display, retry and disposal with widget tests.
Keep the existing release symbol configuration while checking whether packaged macOS native assets can use Cargo's default debuginfo stripping. An optional CI comparison records exported symbols, loading, and size before removing or narrowing this setting.
Update semver-compatible lockfile entries for anyhow, event-listener, memmap2, keccak, rand, bytemuck, and subtle. This resolves the fixable unsoundness and yanked-version findings in the current RustSec database.
Keep the reviewed saturating-time 0.5.0 fork vendored under rust/patches at cypherstack/tor e4c225d, so source builds fetch only from crates.io and do not depend on a git branch staying alive. Track it in the prebuilt source fingerprint, but leave out the target directory and Cargo.lock that building the crate on its own leaves behind, and ignore both in git.
Check Rust and Dart, root and example tests, Windows time/IPv6/file release, and packaged applications on pull requests. Reserve the full native target matrix for release tags and manual dispatches. Audit dependencies using a cached pinned executable without granting verification write access. Share pinned toolchain installation and desktop library loading checks. Keep packaged macOS export validation and offer an optional default-versus- none stripping comparison that records exports, loading, and size.
sneurlax
force-pushed
the
feat/bump-arti
branch
from
October 14, 2026 17:06
f957f44 to
e154972
Compare
CfgPath::new expanded dollar variables and tilde prefixes in caller-provided paths, rejecting otherwise valid directory names. Use CfgPath::new_literal for both state and cache paths. Cover literal directory creation and cleanup with a native regression and describe the behavior in the release notes.
sourceFingerprint() hashed rust/src but not rust/patches, so editing the vendored saturating-time crate left the prebuilt fingerprint unchanged. A stale prebuilt would still verify and Flutter's hook cache would not rebuild. Hash and track rust/patches the same way as rust/src. Source builds declare the same inputs too: cargo's dep-info, which RustBuilder tracks, lists only this crate's own sources.
sneurlax
force-pushed
the
feat/bump-arti
branch
3 times, most recently
from
October 15, 2026 21:02
ef6a8a6 to
ef2a842
Compare
…ield The Bitcoin onion button checked the HTTP onion field, so a Bitcoin entry without a port threw from int.parse and left its socket open. Each test now closes its socket or HTTP client in a finally block.
sneurlax
force-pushed
the
feat/bump-arti
branch
from
October 16, 2026 21:18
ef2a842 to
61f8e7e
Compare
Measured on macOS 26 with Xcode 26.6, Rust 1.92.0 and Flutter 3.47.2: the default, "debuginfo" and "symbols" settings keep every exported tor_* function on aarch64 and x86_64, in plain, prebuilt-style and hook builds, and the packaged example starts Tor. rustc runs rust-objcopy --strip-debug (or --discard-all for "symbols"), which leaves global symbols, and Flutter strips packaged dylibs with strip -x -S anyway.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps Arti to 2.7.0 (needs Rust 1.92) and fixes FFI handle lifetimes and the Tor start/stop lifecycle found in review.
Breaking changes
tor_startreturnsstruct Tor { client, proxy, port }. The SOCKS listeners are bound but idle untiltor_proxy_start(proxy)runs after bootstrap. Dart'sTor.startdoes this for you.Tor.disablenow stops the proxy and returnsFuture<void>, which can throw on release failure. Await it or attach an error handler.Tor.startwaited indefinitely. Pass a longerbootstrapTimeouton slow networks.tor_client_bootstrapreturns aTorBootstrapStatuscode. C enumerators are prefixedTorBootstrapStatus_; numeric values and Dart names are unchanged.Fixes
tor_client_bootstrapandtor_client_set_dormantno longer free the client handle Dart still holds. Addstor_client_free, andTor.stopreleases it.Tor.stopresets state, so Tor can restart. Astopduring bootstrap cancels it promptly. Concurrentstartcalls share one attempt.lastCleanupError.NullClientinstead of aborting the process.tor_startare freed.