Skip to content

engine: run tor as the tor user on Arch with reliable start/stop#198

Open
UncleJ4ck wants to merge 1 commit into
htrgouvea:developfrom
UncleJ4ck:fix/tor-lifecycle
Open

engine: run tor as the tor user on Arch with reliable start/stop#198
UncleJ4ck wants to merge 1 commit into
htrgouvea:developfrom
UncleJ4ck:fix/tor-lifecycle

Conversation

@UncleJ4ck

@UncleJ4ck UncleJ4ck commented Jun 10, 2026

Copy link
Copy Markdown

What was a problem?

On Arch, .configs/arch-torrc never set User, so tor -f ran as root. The OUTPUT rules pass tor's own traffic with -m owner --uid-owner tor -j RETURN, which never matches a root-owned tor, so tor's connections to its guards were redirected back into its own TransPort and it never bootstrapped. Nothing was torified. Every other distro config (debian, fedora, opensuse, void) already pins User; arch was the only one missing it.

Three more issues surfaced in the same path:

  • Start launched tor with tor -f and then also ran systemctl start tor, a second instance with no TransPort, while Stop only ran systemctl stop tor. The tor -f daemon was left running, kept 9051/9061, and broke the next start.
  • Stop flushed the OUTPUT chain and left it empty, dropping any firewall the user already had.
  • The filter chain only rejected udp and icmp, so any other protocol could leak, and rules were applied after tor came up, leaving a window where traffic could escape.

How this PR fixes the problem?

  • arch-torrc pins User tor and matches the other configs (DataDirectory, ClientOnly, PidFile, control socket).
  • One tor lifecycle: started with tor -f, stopped via its pid file. No systemctl double-launch, no orphan.
  • Start snapshots the firewall and Stop restores it, paired with an explicit OUTPUT flush, because an iptables-save taken before any rule exists is empty on nft and restore alone would leave nipe's rules in place.
  • Default-deny on the filter OUTPUT chain: udp, icmp and every other protocol that is not explicitly allowed is rejected.
  • Rules are applied before tor starts and conntrack is flushed, so there is no leak window and no pre-existing flow keeps bypassing tor through the ESTABLISHED rule.
  • torrc path resolved with FindBin; tor runtime and log dirs are created with the right owner and a private (0700) control-socket dir (on Arch neither /run/tor nor /var/log/tor exists outside the systemd unit, which tor -f bypasses); non-loopback IPv6 is rejected.
  • build_rules is split into a pure function so the firewall policy is unit testable.

Check lists (check x in [ ] of list items)

  • Test passed
  • Coding style (indentation, etc)

Additional Comments (if any)

t/20-rules.t asserts the rule set and, specifically, that the owner RETURN precedes the catch-all REDIRECT, the ordering this fix depends on.

Runtime verification (privileged containers):

  • Arch and Ubuntu: tor runs as the tor user (not root), nipe status reports IsTor: true with a real exit IP, nipe newnym rotates it, nipe stop removes the rules.
  • Firewall restore was tested with a populated ruleset, not just an empty one: a pre-existing OUTPUT rule is snapshotted on start and is intact after stop, with nipe's redirect and default-deny gone.
  • perlcritic passes against the repo .perlcriticrc (via the ghcr.io/natanlao/critic image).

Not runtime tested by me: Void, Fedora, openSUSE, CentOS (same tor -f code path, covered by the per-distro CI smoke workflows), the IPv6 reject path, and bare hardware.

Two behaviour changes worth noting: rules are applied before tor comes up, so start is fail-closed (nothing leaves until tor is up; if tor cannot start, run stop to restore connectivity). start also flushes conntrack, which drops connections opened before nipe started so they re-establish through tor.

@UncleJ4ck UncleJ4ck force-pushed the fix/tor-lifecycle branch from ab5a1d9 to 6eadd0b Compare June 11, 2026 14:59
@UncleJ4ck UncleJ4ck force-pushed the fix/tor-lifecycle branch from 6eadd0b to 5689168 Compare June 11, 2026 15:11
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.

1 participant