██████╗ ██████╗ ██╗ ██╗ █████╗ ╚════██╗██╔═══██╗██║ ██║██╔══██╗ █████╔╝██║ ██║███████║███████║ ╚═══██╗██║ ██║██╔══██║██╔══██║ ██████╔╝╚██████╔╝██║ ██║██║ ██║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
Welcome to 3OHA, a place for random notes, thoughts, and factoids that I want to share or remember
11 April 2022
Some UNIX implants are expected to effectively run as daemons. This is how Stevens teaches us to daemonize a process:
void daemonize (const char *cmd) { /* * Some missing stuff here */ [...] if ((pid = fork()) < 0) err(EXIT_FAILURE, "fork fail"); else if (pid != 0) /* parent */ exit(0) /* child A */ setsid() if ((pid = fork()) < 0) err(EXIT_FAILURE, "fork fail"); else if (pid != 0) /* child A * / exit(0) /* child B (grandchild) */ do_daemon_stuff(); }
You might wonder
fork()
, andsetsid()
in the first child.I assume you are familiar with some fundamental concepts of job control in UNIX:
fork()
, the child inherits the PGID of its parent. One key goal of process groups is that all members of a group can be signalled at once.
An important attribute of a process is its controlling terminal. Controlling terminals are, in fact, associated with sessions: each session can have at most one controlling terminal, and a controlling terminal can be associated with at most one session. When you create a process with fork()
, the child process inherits the controlling terminal from its parent. Thus, all the processes in a session inherit the controlling terminal from the session leader.
Controlling terminals are important because they can receive signals from, and send signals to, the process group in the session which is associated with the controlling terminal.
A process can detach from its controlling terminal by creating a new session with the setsid()
function. The remaining processes in the old session continue having the old controlling terminal. A critical situation happens when a process which has no controlling terminal opens a terminal device file, such as /dev/tty
or /dev/console
. Section 11.1.3 of the POSIX.1-2008 standard gives the answer:
If a session leader has no controlling terminal, and opens a terminal device file that is not already associated with a session without using theThe last sentence is the key to the double-fork technique.O_NOCTTY
option (seeopen()
), it is implementation-defined whether the terminal becomes the controlling terminal of the session leader. If a process which is not a session leader opens a terminal file, or theO_NOCTTY
option is used onopen()
, then that terminal shall not become the controlling terminal of the calling process.
Daemons should not have controlling terminals. If a daemon has a controlling terminal, it can receive signals from it that might cause it to halt or exit unexpectedly. The setsid()
call in the code shown at the beginning of this post makes child A become the leader of a new session that only contains one process (itself). Child A also becomes the group leader of a new group and, more importantly, it will have no controlling terminal. If the parent process had one (for example, because the daemon was launched from an interactive shell), the setsid()
call will break this association.
At this point you might wonder why to fork()
in the first place instead of just calling setsid()
and do the daemon stuff in the parent process. The reason is that setsid()
will fail if the calling process is a process group leader. By calling fork()
, we guarantee that the newly created process inherits the PGID from its parent and, therefore, is not a process leader.
The reason for the second fork()
derives from the discussion above about whether the daemon could regain control of a controlling terminal. After the setsid()
call, the newly created process (child A) is now a session leader and the POSIX standard leaves the door open for child A to reacquire a controlling terminal. The second fork()
ensures that child B (the grandchild), where the daemon code will run, is not a session leader. Thus, any call to open()
that could be manipulated to point it to a terminal file will not result in the daemon regaining a controlling terminal.
Some programmers believe that the double-fork technique is unreasonably paranoid. The daemon might not have a single call to open()
. Even if it does, it really depends on implementation-defined specifics. Yet, if you want to be absolutely sure that your daemon cannot be tricked into acquiring a controlling terminal, the double-fork technique will give you some extra guarantees. This discussion applies to systems that follow System V semantics, such as AIX, HP-UX, and Solaris. Linux, despite not being a SysV variant, behaves like SysV for this particular case.
daemon()
function
The daemon()
function present in current Linux systems is for programs that want to detach from the controlling terminal and run as system daemons. The glibc implementation of this function is based on the function with the same name that has been present in BSD since 4.4BSD. Neither the Linux nor the BSD variants implement the double fork. In Linux, this means that a process that has been daemonized using daemon()
might regain a controlling terminal.
Traditional SysV daemons should execute a number of initial steps to prevent unwanted interactions with the environment. Chapter 13 of Stevens provides a more thorough discussion on this topic:
umask()
to set the file mode creation mask to a value that turns off whatever permissions you want to deny in files created by the daemon.chdir()
to change the current working directory for the daemon to either the root directory or any other specific location where it will do its stuff.The daemon(7) Linux man page lists some extra steps, including:
/dev/null
.minb
— a minimal bind shellI wrote a minimal bind shell that illustrates the double-fork technique. It also performs some (not all, for a reason) of the extra steps that you should do on a simple implant like this. This example was actually the main reason for writing this post. When I gave it to some of my students, I realized many of them did not understand how to do robust daemonization.