Skip to content

fix(mobile): close duplicated TUN fd after engine.Stop to prevent fd leak - #18

Merged
Hidden-Node merged 1 commit into
mainfrom
advisor/004-tun-fd-leak
Jul 19, 2026
Merged

fix(mobile): close duplicated TUN fd after engine.Stop to prevent fd leak#18
Hidden-Node merged 1 commit into
mainfrom
advisor/004-tun-fd-leak

Conversation

@Hidden-Node

Copy link
Copy Markdown
Owner

Problem

StartTun and StartTunBridge call dupFd(int(fd)) before handing the
duplicated fd to engine.Insert(key). The dup is necessary to avoid a
SIGSEGV race with Android's ParcelFileDescriptor.close() (commit 41c3eef).

However, the dup'd fd is never closed by the Go side. engine.Stop() closes
the fd it registered — but only on the happy path where _defaultDevice is
non-nil. If engine.Stop() panics (the recover() at mobile.go:282/328
swallows it) or engine.Start() never completed, the fd leaks. On Android,
where users hot-connect profiles many times per session, this leaks one fd per
cycle until the kernel refuses (EMFILE — "VPN interface could not be
established").

Fix

  • Add tunOwnedFd int32 = -1 (guarded by engineMu) to track the most
    recently dup'd fd inserted into the engine.
  • StartTun / StartTunBridge: defensively close any pre-existing tracked fd
    before re-inserting, then record the new safeFd.
  • StopTun / StopTunBridge: explicit syscall.Close(tunOwnedFd) + reset to
    -1 after the recover()-wrapped engine.Stop().
  • The load-bearing recover() (commit 41c3eef) is preserved in both Stop
    functions.
  • StopTunBridge's engineMu.Unlock()tun.StopFakeDNSProxy() ordering is
    preserved.

On the happy path (tun2socks closes the fd via _defaultDevice.Close()), the
explicit close is a harmless double-close (EBADF is ignored). The benefit is
on the unhappy paths where tun2socks does NOT close the fd.

Files changed

  • mobile/mobile.go — +25/-1

Verification

  • go vet ./mobile/... passes (on Linux target; Windows reports false-positive
    on syscall.Dup which is unix-only)
  • gofmt -l mobile/mobile.go — clean for this patch (residual pre-existing
    issues in surrounding code are out of scope)
  • gomobile bind + gradlew assembleDebug verified via CI

Test plan

No new unit tests. Runtime verification: connect/disconnect a VPN profile ~30
times on a real device and confirm no EMFILE / "VPN interface could not be
established" error.

Closes plan 004.

@Hidden-Node
Hidden-Node merged commit 9f000d8 into main Jul 19, 2026
1 check passed
@Hidden-Node
Hidden-Node deleted the advisor/004-tun-fd-leak branch July 19, 2026 22:41
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