1

In this talk on privilege separation, Theo de Raadt explains that OpenBSD's ntpd has a master process which calls settimeofday(), a DNS process responsible for querying DNS servers, and an NTP protocol process which is responsible for speaking UDP to NTP servers. He has this to say about the DNS process:

The other thing we've got is a separate DNS servicer process, cause we wanted to be able to do DNS, but we didn't want the master process to do it because DNS libraries sometimes have bugs, and we didn't want the internet speaker to do it because... it's... it just didn't make sense.

Let's assume that pledge doesn't exist (this design predates it, as I understand it). Obviously this design is pretty pointless on its own, as if the network-facing service is compromised the attacker has root on the system. Therefore, the master process will fork both subprocesses, which will immediately drop privileges to nobody or something. That way, if there's a security bug in either the DNS library or the NTP code, the attacker doesn't have write access to system file, home directories, etc.

That all makes sense to me. However, what I don't understand is why the separation between the DNS and UDP processes is useful. It doesn't matter which one is compromised; the attacker is running as the same user either way. You could put the DNS process in e.g. a nobody2 user, but even then, nobody2 will be just as unprivileged as nobody.

With pledge, the benefit is obvious since e.g. the DNS process can pledge that it will only make DNS queries, which will be pretty damn useless for an attacker. However we're assuming that pledge isn't a factor since this design was introduced before pledge.

Is there any security benefit to the three-process ntpd design that I'm missing? Or was it just a design decision unrelated to security (perhaps considering the possibility of a future pledge-like system)?

strugee
  • 688
  • 1
  • 6
  • 16

2 Answers2

3

(Note that I know nothing about the design of OpenBSD ntpd, I'm just answering the generic question about separating processes.)

From your quote, I gather that the separation the UDP process from the DNS process was not motivated by security concerns, but by architectural concerns: “because... it's... it just didn't make sense”. The UDP process and the DNS process react to completely different events. If they were in the same process, they'd use separate threads (either explicitly, or implicitly with an event loop around a select call). Since the UDP process and the DNS process operate independently, putting them in the same process would complicate the design, hence by default they are in distinct processes.

If the NTP server had been designed as a single process then the design would have had to accommodate the different interaction patterns of the subsystems. Then threads would have made sense. But if there's process separation anyway, then grouping unrelated subsystems in the same process complicates the design.

Nonetheless, there can be security benefits from having separate processes. One benefit is that not all exploits allow the attacker to execute arbitrary code. Some exploits only allow localized data corruption, or the exposure of some secret data, or can cause a crash but always in a controlled way (e.g. only a null pointer dereference and not an arbitrary pointer dereference). Even if a vulnerability does allow arbitrary code execution, sometimes writing an exploit is complicated, and making exploits more complicated buys the defender time to fix the bug and deploy the updates.

Even if a vulnerability allows arbitrary code execution in a process, OS-level mechanisms might confine the process. Even before pledge, OpenBSD allowed an OS-wide restriction on ptrace, which should be enabled on a production server. A process running as nobody can't directly harm other processes if it can't ptrace them. The attacker would have to find some indirect means, and while opportunities for DoS abound, a breach of the DNS would not allow the attacker to generate fake NTP traffic, for instance (it wouldn't be able to bind the port).

Gilles 'SO- stop being evil'
  • 50,912
  • 13
  • 120
  • 179
2

Let's assume that pledge doesn't exist (this design predates it, as I understand it). Obviously this design is pretty pointless on its own, as if the network-facing service is compromised the attacker has root on the system.

Even without pledge the design isn't pointless. If the network-facing service is compromised the attacker does not have root on the system - that's the point.

The master process spawns the NTPD child process, which opens the UDP socket to be an NTP server (port 123), and then the child process drops privileges.

In this case, it means that the child process changes its real, effective and saved user IDs to be that of the _ntp user.

The master process remains as root so that it can call the adjtime(2) system call when necessary.

The network-facing process is no longer root, exactly eliminating the risk you mention. The process that is root has a tightly-controlled interface (via the IMSG system) to only allow it to call adjtime(2). So an attacker who compromises the child process has _ntp privileges on the system. And can only issue adjtime(2) calls on the host - must lower capabilities than from a full root compromise.

See this slide in a talk about security controls in OpenBSD for an early version of the design before pledge(2):

Early NTPD privilege-separated architecture

user7761803
  • 127
  • 3