Skip to content

fix(dns): EPERM means the bridge is alive, not stale - #456

Merged
ralyodio merged 1 commit into
mainfrom
fix/dns-isalive-eperm
Aug 30, 2026
Merged

fix(dns): EPERM means the bridge is alive, not stale#456
ralyodio merged 1 commit into
mainfrom
fix/dns-isalive-eperm

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

isAlive asked process.kill(pid, 0) and caught every failure as "dead":

try { process.kill(pid, 0); return true; }
catch { return false; }

That call fails two ways which mean opposite things — ESRCH for a pid that is gone, and EPERM for one that is there but belongs to another user.

The second is the case this tool manufactures. dns enable escalates, so the bridge it starts is root's while every later status asking after it is not. A live root-owned bridge read as a stale pidfile for the rest of its life, and each caller drew a worse conclusion from it than the last:

caller what it did with a live root-owned bridge
status printed bridge NOT running — stale pidfile for <pid>, then advised dns enable as the fix
stopDaemon deleted the pidfile and reported it had cleared a stale entry, while the daemon kept running
startDaemon saw nothing running and started a second bridge on 127.0.0.1 under the working one on 0.0.0.0

The last one is the outage. The kernel delivers to the more specific socket, so the advice shadowed the bridge it was meant to rescue — the failure bridgePresence already documents, reached by believing our own liveness check.

The change

Only ESRCH is dead. EPERM is alive and someone else's, and the caller is told rather than having it papered over — stop returns Operation not permitted instead of silently orphaning the daemon, and start short-circuits on alreadyRunning instead of shadowing it.

isAlive gains an injectable kill so both branches are testable without privileges, matching how every other system call here is reached.

Scope

The port probe in bridgePresence was already authoritative and is untouched — it is why status still correctly said nothing was answering on 5354. This fixes the half that reads the pidfile.

Not addressed here: the bridge still is not a systemd unit, so it does not survive a reboot on desktops. That is the separate gap docs/hosting-a-moshpit-name.md points at deploy/moshcode-dns.service for, which is not shipped in the package.

Tests

Four added to test/dns-daemon-verify.test.mjs. Three fail without the fix; the ESRCH one is the control and passes either way.

✖ EPERM means alive and someone else's, not dead
✔ ESRCH is the only failure that means dead
✖ a real process this test cannot signal reads as alive
✖ a pidfile naming another user's live process is running, not stale

isAlive(1) holds whoever runs the suite: unprivileged it is EPERM, as root it simply succeeds.

Full suite: 2698 tests, 0 failures, 334 skipped (the usual apps/pwa dependency guards).

Observed as dns status reporting a stale pidfile on a Kubuntu desktop whose bridge had been up the entire time.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp

`isAlive` asked `process.kill(pid, 0)` and caught every failure as "dead".
That call fails two ways which mean opposite things: ESRCH for a pid that is
gone, and EPERM for one that is there but belongs to another user.

The second is the case this tool manufactures. `dns enable` escalates, so the
bridge it starts is root's while every later `status` asking after it is not.
A live root-owned bridge therefore reported as a stale pidfile for the rest of
its life, and each caller drew a worse conclusion from it than the last:

  - `status` printed "bridge NOT running - stale pidfile for <pid>" and
    advised `dns enable` as the fix
  - `stopDaemon` deleted the pidfile and reported it had cleared a stale
    entry, while the daemon it named kept running
  - `startDaemon` saw nothing running and started a second bridge on
    127.0.0.1 underneath the working one on 0.0.0.0. The kernel delivers to
    the more specific socket, so the advice shadowed the bridge it was meant
    to rescue and took the machine's resolver down

Only ESRCH is dead now. EPERM is alive and someone else's, which the caller is
told rather than having it papered over: `stop` returns "Operation not
permitted" instead of silently orphaning the daemon, and `start` short-circuits
on `alreadyRunning` instead of shadowing it.

`isAlive` takes an injectable `kill` so both branches are testable without
privileges, matching how every other system call here is reached.

Observed as `dns status` reporting a stale pidfile on a Kubuntu desktop whose
bridge had been up the entire time. The port probe in `bridgePresence` was
already authoritative and is untouched; this fixes the half that reads the
pidfile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

0 finding(s) in the 2 file(s) this pull request changes.

Nothing in the files this pull request changes.

88 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 73 | **LOW**: 10

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:138
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:152
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:178
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:365
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:369
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:414
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:663
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:859
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:861
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:920
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:966
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1036
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1139
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1162
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1184

…and 68 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 762fc7d into main Aug 30, 2026
6 checks passed
@ralyodio ralyodio mentioned this pull request Aug 30, 2026
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