skip to content

On a RHEL host with SELinux enforcing, a daemon that starts fine on its default port fails to bind after you reconfigure it to listen on port 8081. Why would SELinux be involved in a port bind at all, and what is the correct fix?

level: middleimportance: nice to knowfreq 40%

answer

  1. ports are labelled objects too
  2. tclass is not file here
  3. name_bind on a tcp_socket
  4. semanage port, not a new module
  5. below 1024 is a different problem

basics

~20 s

SELinux labels TCP and UDP port numbers with types, just as it labels files, and policy only lets a domain bind ports of the types it is allowed. Port 8081 carries a different type than the daemon's default port, so the bind is refused until you add the label with semanage port.

solid answer

~50 s

SELinux does not only label files — network ports are labelled object classes too. The policy maps port numbers to types, and a confined daemon is allowed to bind only ports whose type its domain is permitted to use. Its default port already carries the right type; port 8081 does not, so the bind is refused and you get an AVC denial with `tclass=tcp_socket` and a permission such as `name_bind`. The fix is to label the port, not to relax the daemon: `semanage port -l` shows the current mapping, and `semanage port -a -t http_port_t -p tcp 8081` adds the new port to the set the web-server domain may bind (use `-m` instead of `-a` if the port is already listed under another type). This is a good illustration that SELinux mediates a much wider set of object classes than files — sockets, ports, IPC and capabilities all carry labels the policy reasons about.

code

bash · 11 lines
bash
# the denial names a socket class, not a file
ausearch -m AVC -ts recent | grep -E 'tclass=tcp_socket|name_bind'

# inspect the current port-to-type mapping
semanage port -l | grep -E '8081|http_port_t'

# label the new port so the confined domain may bind it
semanage port -a -t http_port_t -p tcp 8081

# if the port is already mapped to another type, modify instead of add
semanage port -m -t http_port_t -p tcp 8081

go deeper

for a junior

Know that SELinux can block more than file access, and that moving a service to a non-standard port on a RHEL host may need an extra step beyond editing its config.

for a middle

Explain that SELinux labels port numbers with types, that a confined domain may bind only permitted types, and that semanage port adds or modifies the mapping persistently.

for a senior

Read the object class in the denial to route the fix, choose the narrowest change over a generated module, and rule out the privileged-port case before assuming labelling is the cause.

for a principal

Treat non-default ports as a deployment-time policy input: capture the semanage port entries in configuration management so a rebuilt host binds correctly rather than relying on someone remembering the one-off command.

## Labels are not only for files The usual introduction to SELinux is about files, so the first time a policy blocks something that has no path at all it looks like a different system entirely. It is not. SELinux assigns a security context to many *object classes*, and TCP and UDP port numbers are one of them. The policy maintains a mapping from port numbers and protocols to types, and its allow rules are written over those types just as they are for files. So when a confined daemon calls `bind()`, the kernel asks whether that domain is allowed the `name_bind` permission on a socket whose port carries type X. Its default port is already mapped to the type its domain is permitted to bind. An arbitrary alternative port is not — it will fall under some other type, or under a generic one — and the bind is refused. ## The symptom and the record The application reports something like "permission denied" or "could not bind to address", and the mode bits offer no explanation because there are no mode bits involved. The audit record is the giveaway: `tclass=tcp_socket` and a permission of `name_bind` (or `name_connect`, when the problem is a confined process reaching *out* to an unusual port), with `tcontext` naming the port's type rather than a file type. This is also the reason to read `tclass` on every denial rather than skipping to the fix. `tclass=file` sends you to labelling and `restorecon`; `tclass=tcp_socket` sends you here; `tclass=capability` says the application is asking for a privilege and no relabelling will help. ## The correct fix Inspect and change the mapping with `semanage port`: ```bash # what is 8081 currently, and what does this domain's type cover? semanage port -l | grep -E '8081|http_port_t' # add a port to an existing type semanage port -a -t http_port_t -p tcp 8081 # modify a port that is already mapped elsewhere semanage port -m -t http_port_t -p tcp 8081 # and remove a local addition when it is no longer needed semanage port -d -t http_port_t -p tcp 8081 ``` The change is a local policy modification: it persists across reboots, survives policy package updates, and is exactly as narrow as the problem — one port, one protocol, one type. Note that `-a` fails if the port is already mapped, which is what `-m` is for, and that some ports are already covered by broad type definitions, in which case nothing needs doing at all. ## What not to do The two shortcuts both cost more than they save. Switching the machine to permissive removes containment from every confined service on the host because one port number is not in a list. Writing a policy module from the denial grants the domain `name_bind` on that port *type*, which is usually broader than adding your one port to the right type and is certainly harder for the next person to understand. There is also a distinct failure that looks similar and is not SELinux at all: binding a port below 1024 requires privilege, historically root and now more precisely a capability the process must hold. If the port in question is low, rule that out first — the denial record will name a capability rather than a socket class, and no amount of `semanage port` will help. ## Why this question is worth knowing It is the cheapest possible demonstration that a candidate understands SELinux as a labelling system rather than as "the thing that breaks file permissions". Anyone who has moved a service off its default port on a RHEL-family host has met it; anyone who has only read about SELinux usually has not. It also sets up the more general habit that matters in production: read the object class in the denial, and let it choose which of the several possible fixes is the right one.

  • How does the AVC record tell you this is a port problem rather than a file-labelling problem?
    By its object class. The record carries `tclass=tcp_socket` with a permission such as `name_bind`, and the target context names a port type rather than a file type. A file-labelling problem shows `tclass=file` or `tclass=dir`. Reading the class first is what stops you running `restorecon` against a problem that has no file in it.
  • Why prefer semanage port over generating a policy module with audit2allow for this failure?
    Because `semanage port` adds one port, one protocol, one type — the narrowest possible change, and one the next administrator can read straight out of `semanage port -l`. A generated module grants the domain bind permission on a whole port type and lives as an installed binary module that nobody will think to inspect. Same outcome, much wider blast radius and far worse discoverability.
  • A daemon fails to bind port 80 as a non-root user on a host with SELinux enforcing. Is port labelling the likely cause?
    Probably not. Ports below 1024 are privileged, so an unprivileged process is refused by the kernel independently of SELinux, and the denial record would name a capability rather than a socket class. Check that first: relabelling a port cannot grant the privilege that binding a low port requires.

saying these in an interview costs you the question

  • SELinux only labels files, so a port bind cannot be denied
  • Switch to permissive since it is just one port number
  • Write a policy module with audit2allow for every port change
  • semanage port -a works even when the port is already mapped
  • Any bind failure under SELinux is a port-label problem

context