Many applications rely on a runtime directory: a dedicated directory which serves as a rendezvous point for different components and for runtime files.
The full documentation for it is in the XDG base directory specification.
Prior art
- dumb_runtime_dir: PAM module with less than 100 lines of C code.
- pam_rundir: PAM module, functionally equivalent to the above, over 400 lines of code.
- pam_systemd: Tied to systemd. Relies on PAM too.
- pam_elogind: Tied to
elogind, an unmaintained fork ofsystemd-logind. mkdir /tmp/run-$UID: simple, but insecure (see below).- mkrundir: Small binary written by myself, less than 40 lines, but requires setuid (and I’d like to address this).
Requirements
Concurrent sessions for the same user should share the same runtime directory. For example, when logging in via a physical seat on a host, and then starting an SSH session, they both need to use the same runtime directory path.
A native approach might use /tmp/run-$USER, but this is vulnerable to another
user account creating a directory in that location, inadvertently controlling
ownership of sensitive files. Likewise, another user simply creating a file
there, blocking creating of the runtime dir. Using a deterministic location in a
world-writeable location is insecure.
Approaches
All possible approaches have their caveats and compromises. We can only pick which compromise we prefer.
- PAM model: requires installing and relying on PAM, a huge amount of
complexity and attack surface for something so trivial. They also wouldn’t
work when simply using
logingon a tty. - setuid/setgid binary: probably the simplest approach, but usage of setuid/setgid is frowned upon from a security point of view.
- Using a deterministic location in
/tmpis vulnerable to races/hijacking. - Somewhere in
$HOME: survives reboots. This lacks any proper clean-up mechanism, but can also result in stale files and break application expectations. - Dedicated daemon: expose a socket to a specific group, and have the daemon create the directory on request. Works, but having an additional daemon for this seems like an overkill.
- Randomised location in
/tmp: requires coordination across concurrent sessions.
Additionally, PAM, setuid or a dedicated daemon all require root involvement in configuring or initialising a user session after user login. This kind of initialisation should be handled inside the user session: the root user should only be involved if strictly necessary.
Randomised location in /tmp
Of all the above approaches, I opted for using a non-deterministic (i.e.,
unpredictable) location in /tmp.
A “name file” somewhere in $HOME keeps the location of the current runtime
directory. Upon login, a helper called via ~/.zprofile checks this file. If it
contains the location of a directory owned by the current user with the right
permission, it is used as the current $XDG_RUNTIME_DIR. Otherwise, a lock is
taken, a new directory is created, and the name file is replaced. The lock
ensures that two concurrent executions don’t overstep each other when replacing
the name file.
This is simple enough, and can be implemented in a small, simple binary.