3.12.1 Port Reservations
Ports are a shared, unowned namespace. Linux protects it with one rule:
bind(2) below port 1024 needs CAP_NET_BIND_SERVICE, and everything
above is anyone's. That is too coarse in one direction — any privileged
process gets every low port — and absent in the other: nothing protects
8080, 3306 or 5432 from whichever process asks first.
On Peios the only thing worth authorising about a port is the claim
— binding a non-zero port — and it is authorised the way everything
else is: by a security descriptor. CAP_NET_BIND_SERVICE is in the
always-allow set (§3.10.2), so the Linux floor never refuses first, and
every claim reaches security_socket_bind(), where KACS decides.
The precedent is not Linux but HTTP.sys's URL reservations on Windows: a sparse table of namespace prefixes, each carrying a descriptor, consulted when a name is claimed, default-permissive where nothing is reserved. This chapter moves that model down to the port layer.
3.12.1.1 The object #
A reservation is a selector mapped to a descriptor. The selector names a set of protocols and an inclusive port range:
<proto>[,<proto>]:<lo>[-<hi>] proto ∈ tcp | udp | *
tcp:80, udp:53, tcp,udp:1-1023, *:8080. Ports are decimal,
1–65535, no leading zeros, lo <= hi; a protocol may not repeat, so
every selector has one spelling. The address family is deliberately not
part of the selector: a reservation covers IPv4 and IPv6 alike, or
dual-stack squatting — claiming the v6 side of a port reserved on v4 —
would bypass it.
The rights are those of <pkm/net.h>:
| Right | Grants |
|---|---|
KACS_PORT_BIND (0x00000001) | bind(2) to any port the selector contains |
READ_CONTROL | reading the descriptor |
That is the whole mask. Editing a reservation is a registry write,
governed by the registry key's own descriptor — one edit policy for the
table — so WRITE_DAC and WRITE_OWNER inside a port descriptor mean
nothing and are never honoured. The generic mapping is GENERIC_EXECUTE
→ PORT_BIND, GENERIC_READ → READ_CONTROL, GENERIC_WRITE → nothing,
GENERIC_ALL → both.
3.12.1.2 The table #
Reservations live as values under one registry key:
Machine\System\Network\TcpIp\PortReservations\
@ REG_BINARY SD the default reservation
tcp,udp:1-1023 REG_BINARY SD
tcp:80 REG_BINARY SD
Each value's name is its selector and its data a self-relative
descriptor. The key's unnamed default value, @, is the default
reservation: the descriptor for every port no selector contains. The
shipped seed grants PORT_BIND to Everyone on @ — an unreserved port
is anyone's, as on Windows — and to SYSTEM and the bind-low-ports
capability SID on tcp,udp:1-1023, which is the Linux convention
restated as policy. Services then carry their own entries:
tcp:80,443 → NT SERVICE\httpd: PORT_BIND, which composes with
per-service SIDs so that a compromised sshd cannot take 443 even
though both are "privileged".
The kernel reads the key whole and rejects it whole. Every name must
parse, every descriptor must parse, exactly one @ must exist, and no
two selectors of equal width may overlap — the most specific match
would be ambiguous, and the kernel refuses to guess. On any failure the
load is audited and the previous table stays in force. Nested overlap,
and overlap between selectors of different widths, is how specificity
works and is accepted.
3.12.1.3 The decision #
At bind(2) on an AF_INET or AF_INET6 socket whose protocol is TCP,
UDP or UDP-Lite:
- If the requested port is 0 — ephemeral allocation — nothing is checked. Protocols no reservation covers (SCTP, raw) pass untouched.
- The most specific reservation containing
(protocol, port)is selected: the one covering the fewest ports, regardless of how many protocols it names. None → the default reservation. - The caller's effective token is evaluated against that descriptor
for
PORT_BIND, through the ordinary AccessCheck pipeline (§3.8) with PIP context, privilege-use marking and audit events — a port descriptor may carry a SACL, and its decisions surface through the same KMES stream as any other object's. - Deny →
-EACCES. Grant → the bind proceeds, and the caller's token is recorded on the socket as its binder.
An explicit bind(2) before an outbound connect(2) meets the same
rule; it is not special-cased.
Rebinding. Binding onto a port that is already bound — with
SO_REUSEADDR or SO_REUSEPORT — is still a bind(2) and still meets
the reservation. The rule beyond that is that the new binder's user SID
must equal the existing binder's, or the caller must hold
SeTcbPrivilege: rebind is a property of the current binding, not of
policy, and a right on the reservation that let one principal rebind
onto another's port is a thing nothing legitimately needs. The binder
recorded on each socket is what makes that comparison local; the
comparison itself is applied where the stack resolves bind conflicts,
and Linux already applies the same-owner half of it for SO_REUSEPORT
by comparing the sockets' projected UIDs.
3.12.1.4 Before the registry #
The table comes from the registry through the same self-configuration
path LCS uses for its own limits (§5.10): read at the first source's
registration, then watched, so a change to the key is live without a
restart. Until the first successful load — early boot, or a registry
that never becomes available — a compiled-in fallback answers:
owner and group SYSTEM, one entry granting SYSTEM PORT_BIND, nobody
else. It is stricter than the shipped seed on purpose: nothing but
system components should be claiming ports before the registry exists.
The fallback is replaced, never merged. The moment the key loads it is the sole authority; if a later load is rejected the last good table stays, not the fallback. Effective policy therefore never depends on boot timing.
3.12.1.5 What this retired #
SeBindPrivilegedPortPrivilege (bit 63) was the KACS privilege
CAP_NET_BIND_SERVICE mapped to before this object existed. A
reservation's grant is an ACE, and an ACE names a SID, not a privilege;
keeping the privilege would have been a second authorisation path
around the descriptor. It is retired and its bit is not reused. Low
ports are granted through the capability SID on the 1-1023
reservation, which reaches a token by confinement — peinit for a
service, or positive confinement from the authority at logon for a
user.
3.12.1.6 Tracing #
Every decision emits kacs:kacs_socket_bind with reason port-bind:
desired carries PORT_BIND, max_imp carries the port, and ret the
verdict. Table loads emit reason port-table. See Appendix 3.C for the audit
events an evaluated descriptor's SACL can produce.