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>:

RightGrants
KACS_PORT_BIND (0x00000001)bind(2) to any port the selector contains
READ_CONTROLreading 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_EXECUTEPORT_BIND, GENERIC_READREAD_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:

  1. If the requested port is 0 — ephemeral allocation — nothing is checked. Protocols no reservation covers (SCTP, raw) pass untouched.
  2. 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.
  3. 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.
  4. 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.

Edit this page