Skip to content

core: Retry privileged binds on EACCES instead of checking port < 1024 - #8253

Open
francislavoie wants to merge 1 commit into
masterfrom
bindcap-retry-eacces
Open

francislavoie wants to merge 1 commit into
masterfrom
bindcap-retry-eacces

Conversation

@francislavoie

Copy link
Copy Markdown
Member

Assistance Disclosure

Claude Code (Opus 5.5) wrote the code, tests and this description under my direction; I reviewed the approach and verified the results below.

What

Follow-up to #8102, addressing @Minding000's review comment about the hardcoded 1024 cutoff. Thanks for catching that!

WithBindCapability no longer checks port < 1024 up front. It calls listen normally, and only if that fails with EACCES on a TCP/UDP port, and CAP_NET_BIND_SERVICE is permitted but not effective, it raises the capability on a locked thread and retries (same mechanism as before).

Why

The privileged range is set by net.ipv4.ip_unprivileged_port_start (0 to 65535, applies to IPv6 too). With a value above 1024, a non-root bind to e.g. :1500 failed even though the capability was permitted. With a value below 1024 (Docker's default is 0), #8102 raised the capability needlessly.

Retrying on EACCES was chosen over reading /proc/sys/net/ipv4/ip_unprivileged_port_start because it follows whatever the kernel actually enforces and doesn't depend on /proc/sys being visible (e.g. systemd ProcSubset=pid). A failed listenReusable only touches the listener pool on success, so the retry has no side effects to undo.

Testing

Static build with setcap cap_net_bind_service=+p, in Docker. "ps" is --sysctl net.ipv4.ip_unprivileged_port_start:

Scenario master this PR
non-root, ps=2000, :1500 ✗ bind: permission denied ✓
non-root, ps=1024, :80 ✓ ✓
non-root, ps=1024, HTTPS + HTTP/3 (80, 443 tcp+udp) ✓ ✓
non-root, default (ps=0), :80 ✓ ✓
non-root / root, --cap-drop ALL, :8080 ✓ ✓
non-root, --cap-drop ALL, ps=1024, :80 ✗ (expected) ✗ (same error)
root, ps=1024, :80 ✓ ✓

CapEff is 0 on every thread afterwards for non-root runs. Unit tests cover when listen is retried.

🤖 Generated with Claude Code

WithBindCapability assumed the kernel's privileged port range ends at
1024, but it is set by net.ipv4.ip_unprivileged_port_start, which can
be anywhere from 0 (Docker's default) to 65535. With a value above
1024, a non-root bind to e.g. :1500 failed even though the capability
was permitted.

Now listen is called normally first, and only if it fails with EACCES
on a TCP/UDP port, and CAP_NET_BIND_SERVICE is permitted but not
effective, the capability is raised and the bind retried. This also
skips raising it when the range starts lower than the port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@francislavoie francislavoie added the bug 🐞 Something isn't working label Oct 18, 2026
@francislavoie francislavoie added this to the v2.12.0 milestone Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug 🐞 Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant