|
Message-ID: <20170907151829.lff5etqqtwceqvq4@perpetual.pseudorandom.co.uk> Date: Thu, 7 Sep 2017 16:27:25 +0100 From: Simon McVittie <smcv@...ian.org> To: oss-security@...ts.openwall.com Subject: Re: CVE-2017-12847: nagios-core privilege escalation via PID file manipulation On Thu, 07 Sep 2017 at 08:38:23 -0400, Michael Orlitzky wrote: > supervised: > > 4. Daemon runs in the foreground, and does nothing special If other services can depend on this daemon (for example situations like "NetworkManager must start after dbus-daemon is ready" or "ntpd must start after syslogd is ready"), then it frequently does need to do something special, to tell larger infrastructure when it is ready to satisfy dependencies. systemd implements several mechanisms for this: the daemon can fork (as below; Type=forking in systemd jargon), or it can request a D-Bus name (Type=dbus), or it can send a systemd-specific message over a Unix socket (Type=notify). For simplicity, Type=dbus and Type=notify daemons should normally "stay in the foreground" and not fork, although I don't think that's actually required (systemd can supervise them regardless). systemd units (configuration/integration files) typically set a desired Type, then run the daemon with appropriate command-line options to make it behave in a way that matches the Type. Other init/rc systems can implement whatever mechanisms they want to for readiness notification, but they typically have *something*. I've seen some (runit) that advocate having dependent services just fail and exit when their dependencies are not satisfied, using a retry loop to get them started successfully, and not telling the service manager anything about the dependency tree; but that seems like the exception rather than the rule. > Forking, > > 1. Daemon forks > 2. Daemon writes a PID file > 3. Daemon drops privileges Forking is not just for backgrounding, it's also a form of semi-explicit readiness-notification. In a world of sysv-style shell init scripts, an easy way to implement the common semantics "wait for daemon to be ready to accept requests and satisfy other daemons' dependencies, then return" is to have the daemon use the forking pattern like daemon(3). Then the shell script does something like: #!/bin/sh case "$1" in (start) dbus-daemon --system --fork \ --address=unix:/var/run/dbus/system_bus_socket exit 0 ;; ... esac which means it won't exit until the initial process of the daemon has exited, for example because it has called daemon(3) or equivalent. A well-behaved daemon that implements this pattern will make sure not to call daemon(3) until it is ready to accept requests and satisfy other daemons' dependencies. For example, in the dbus-daemon invocation in my shell script fragment above, dbus-daemon has already called bind() and listen() on /var/run/dbus/system_bus_socket before it daemonizes (double-forks), so as soon as it daemonizes, clients can rely on being able to connect() to that address without a race condition causing them to fail. Some service supervisors (in particular I'm aware of systemd and Upstart) identify and track this forking pattern as a way to know when dependencies on this daemon can be assumed to be satisfied. This is orthogonal to whether they supervise the daemon - systemd will certainly do so whether the daemon forks or not, and I think Upstart does too. Ideally, the sequence of events would be something that ensures that the pid file already exists by the time readiness has been announced, like this pseudocode: have the necessary privileges to write a pid file fork if (parent) { write child pid to pid file exit /* tells supervisor we are ready */ } else /* child */ { drop privileges while (1) { process request } } Regards, smcv
Powered by blists - more mailing lists
Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.