Skip to content

fix(settings): coerce local-bind ports to non-root range >= 1025 - #46

Merged
Hidden-Node merged 1 commit into
mainfrom
advisor/020-nonroot-port-floor-1025
Jul 24, 2026
Merged

fix(settings): coerce local-bind ports to non-root range >= 1025#46
Hidden-Node merged 1 commit into
mainfrom
advisor/020-nonroot-port-floor-1025

Conversation

@Hidden-Node

Copy link
Copy Markdown
Owner

Android apps cannot bind TCP/UDP ports 1..1024 without root. Five local-bind sites validated user input with coerceIn(1, 65535), so a user entering 80, 443, or 53 for any locally-bound port would pass validation, get the value saved, then hit BindException: EACCES at connect time on a non-rooted device -- surfacing as a generic "Go core error" with no clue that the port was the problem.

Tighten the five local-bind sites to coerceIn(1025, 65535):

  • GlobalSettingsStore.kt: internet-sharing SOCKS + HTTP ports
  • MainActivity.kt: TOML-import listen port
  • ProfilesScreen.kt: TOML-import listen port
  • SettingsViewModel.kt: manual field-update listen port

Leave the two coerceIn(1, 65535) calls in MasterDnsVpnService.kt parseConnectTarget() untouched: those parse the remote destination port from an HTTP CONNECT request, and browsers legitimately CONNECT to :80 and :443 -- clamping them to >= 1025 would break the proxy's primary use case.

Add GlobalSettingsPortRangeTest with 3 JVM-runnable tests pinning the new floor (80 -> 1025, 443 -> 1025, 99999 -> 65535, 8090 -> 8090, 18000 -> 18000) so a future refactor that drops the coerceIn or renames the literal fails loudly.

Plan 020.

Android apps cannot bind TCP/UDP ports 1..1024 without root. Five
local-bind sites validated user input with coerceIn(1, 65535), so a
user entering 80, 443, or 53 for any locally-bound port would pass
validation, get the value saved, then hit BindException: EACCES at
connect time on a non-rooted device -- surfacing as a generic "Go
core error" with no clue that the port was the problem.

Tighten the five local-bind sites to coerceIn(1025, 65535):
- GlobalSettingsStore.kt: internet-sharing SOCKS + HTTP ports
- MainActivity.kt: TOML-import listen port
- ProfilesScreen.kt: TOML-import listen port
- SettingsViewModel.kt: manual field-update listen port

Leave the two coerceIn(1, 65535) calls in MasterDnsVpnService.kt
parseConnectTarget() untouched: those parse the *remote destination*
port from an HTTP CONNECT request, and browsers legitimately CONNECT
to :80 and :443 -- clamping them to >= 1025 would break the proxy's
primary use case.

Add GlobalSettingsPortRangeTest with 3 JVM-runnable tests pinning the
new floor (80 -> 1025, 443 -> 1025, 99999 -> 65535, 8090 -> 8090,
18000 -> 18000) so a future refactor that drops the coerceIn or
renames the literal fails loudly.

Plan 020.
@Hidden-Node
Hidden-Node merged commit 6fd18bd into main Jul 24, 2026
2 checks passed
@Hidden-Node
Hidden-Node deleted the advisor/020-nonroot-port-floor-1025 branch July 24, 2026 15:47
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