Bind permission denied even as root | Unbound

I’m using the unbound package[1]. What is causing the below behavior? How can I bind to other privileged ports?

error: can't bind socket: Permission denied for ::1 port 453
  1. It can bind to port 53 and 853.
  2. It cannot bind to any other privileged port (tried a bunch). EDIT: even as root!
  3. ausearch -m avc returns nothing. (If port >1024 then it has denial as expected.)
  4. getcap /usr/sbin/unbound returns nothing.
  5. unbound.service has no user specified (root by default).

The systemd service starts as root. I thought Unbound binds to privileged ports before dropping the privilege to become user. But if that’s not the case, what makes it able to bind to 53 and 853? It’s not SELinux denying other ports, nor capabilities on the binary enabling the ports. What is happening??

unbound.conf
server:
	num-threads: 4
	interface: ::1@53
	interface: enabcm6e4ei0@53
	interface: ::1@453
	interface: enabcm6e4ei0@853
	access-control: 0.0.0.0/0 allow
	access-control: ::/0 allow
	cache-min-ttl: 3600
	edns-tcp-keepalive: yes
	do-daemonize: no
# Change to root here and it still fails
	username: "unbound"
	chroot: ""
	directory: "/etc/unbound"
	pidfile: "/run/unbound.pid"
	prefetch: yes
	prefetch-key: yes
	ede: yes
	ede-serve-expired: yes
	auto-trust-anchor-file: "/var/lib/unbound/root.key"
	tls-system-cert: yes
	tls-service-key: "/etc/pki/acme.sh/some.key"
	tls-service-pem: "/etc/pki/acme.sh/some/fullchain.cer"
	tls-session-ticket-keys: "/etc/unbound/session-ticket-key.dat"

	# This is backend
	https-port: 453
	http-notls-downstream: yes

	# Fedora/RHEL: use system-wide crypto policies
	tls-ciphers: "PROFILE=SYSTEM"

	# Only ephemeral ports are allowed by SElinux
	outgoing-port-avoid: 0-32767
	outgoing-port-avoid: 61000-65535

	# Zone signers must produce zones that allow this feature to work, but sometimes they do not.
	# harden-algo-downgrade: yes

	# Not RFC, but there is a new draft.
	harden-referral-path: yes

	# We consider to enable this by default in later releases.
	private-address: 10.0.0.0/8
	private-address: 172.16.0.0/12
	private-address: 192.168.0.0/16
	private-address: 169.254.0.0/16
	private-address: fd00::/8
	private-address: fe80::/10
	private-address: ::ffff:0:0/96

	# No public IPs yet at boot. Cannot assign otherwise.
	ip-freebind: yes

  1. ↩︎

It is SELinux. The message is hidden. ausearch -m avc is not enough[1].
The message shows after setting to permissive mode. Though semodule -DB probably would do the same as suggested in the Docs.

type=AVC msg=audit(1701160944.081:922): avc:  denied  { name_bind } for  pid=3062 comm="unbound" src=453 scontext=system_u:system_r:named_t:s0 tcontext=system_u:object_r:reserved_port_t:s0 tclass=tcp_socket permissive=1

EDIT: Permissive mode getting more message might be a coincident (see below). Use semodule -DB instead.


  1. ↩︎

p.s.
I also have this piece of arcane nonsense that goes away after permissive mode…

error: can't bind socket: Permission denied for ::1 port 443

It should be allowed already, and there is no denial message even in permissive mode.

allow named_t http_port_t:tcp_socket name_bind; [ named_tcp_bind_http_port ]:True

Though I do have port 443 occupied, but then message should be (as is in permissive mode):

error: bind: address already in use

Though I don’t really use it that way so I’ll just leave this…

EDIT: Nope I got it.
named_tcp_bind_http_port is off by default. It seems setenforce 0 flips it on, and causes it to not complain…
EDIT2: The 2nd half of EDIT is incorrect, see below.

[root@P1 ~]# getsebool named_tcp_bind_http_port
named_tcp_bind_http_port --> off
[root@P1 ~]# setenforce 0
[root@P1 ~]# getsebool named_tcp_bind_http_port
named_tcp_bind_http_port --> off
[root@P1 ~]# setenforce 1
[root@P1 ~]# getsebool named_tcp_bind_http_port
` --> off

setenforce 0 does not flip any selinux boolean. From man selinux:

The permissive option enables the SELinux code, but causes it to operate in a mode where accesses that would be denied by policy are permitted but audited.

If you need to switch permanently named_tcp_bind_http_port to on, you need to run: setsebool -P named_tcp_bind_http_port on

Also there seems to be some kind of typo in this thread. The original post is related to 453 port while later posts mention port 443

Going off topic a bit, but I think people will have trouble enabling DNS over HTTPS on Unbound.

By default only 53 and 853 are allowed.
Enabling named_tcp_bind_http_port will only allow tcp_socket class, i.e. only TCP.
Unless I’m missing something, interface: in Unbound will always try to bind both TCP and UDP, and there is no fine grained control for each interface. (do-udp: yes probably turns UDP off entirely.)
Failing to bind UDP just causes Unbound to quit. I’m not sure there is a way to enable DoH on 443 (no reverse proxy).

My workaround is to put DoH backend on 53 (reverse_proxy), but that disables the regular TCP DNS, which is not really compliant.

The ports enabled implicitly or explicitly via tls-port: and https-port: do not provide normal DNS TCP service.

EDIT: Never mind. Adding custom port to dns_port_t is better.

I see, perhaps it was a coincidence that I got more audit message from permissive mode…

Yeah it’s intentional. My setup is putting Unbound behind reverse proxy, 453 is the backend port I wanted to use. Since it didn’t work and I didn’t know why, I thought to just try the regular DoH 443 port.