Does systemd not protect processes against acquiring a controlling terminal?

man 7 daemon

When a traditional SysV daemon starts, it should execute the following steps as part of the initialization. Note that these steps are
unnecessary for new-style daemons (see below), and should only be implemented if compatibility with SysV is essential.

[…]

6. In the child, call setsid() to detach from any terminal and create an independent session.

7. In the child, call fork() again, to ensure that the daemon can never re-acquire a terminal again.

but compare this to processes started with no vestige of SysV compatibility:

$ ps -efj
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
root         1     0     1     1  0 May10 ?        00:06:44 /sbin/init
...
root       185     1   185   185  0 May10 ?        00:09:48 /lib/systemd/systemd-journald
root     16434     1 16434 16434  0 May26 ?        00:00:11 /usr/sbin/rsyslogd -n

The processes for both rsyslog.service and systemd-journal.service are session leaders (SID = PID).

It seems that if such programs were configured to log to a TTY, they would gain the TTY as a controlling terminal, and receive an unwanted / fatal signal when the TTY is hung up / receives Ctrl+C, respectively. Unless they remember to set O_NOCTTY when opening the TTY file.

It seems this is a little pitfall when writing or converting a program to run as a systemd service w/o any SysV compatibility, if your program supports writing messages to custom files. It does not seem to be pointed out by this doc which advocates the systemd style. The doc rather implies the opposite, by insisting that double-fork is necessary to avoid this on SysV, and then not mentioning this as an issue when describing the steps a native systemd service would use.

Is that correct? Does systemd provide some protection against this I have overlooked, or is the issue pointed out elsewhere in the systemd doc?

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

Does systemd provide some protection against this […]?

You are assuming that it should. On the contrary, consider settings like TTYPath and services like [email protected]. The ability to gain a controlling terminal is actually necessary, in order that service management can encompass TTY login services, which need to do precisely that.

What actually protects against it is the move away from automatic allocation of a controlling terminal at open(), and discarding the old semantics. Or would protect against it. It is not the case on Linux, but on FreeBSD, NetBSD, OpenBSD, and Hurd nowadays the O_NOCTTY flag to open() is entirely superfluous. The only way to acquire a controlling terminal is by explicitly demanding it, with ioctl(…TIOSCTTY). This has actually been the case for approaching a quarter of a century, since the days of 4.4BSD.

In the meantime, the habit to get into on Linux is the habit that has also been the case for a long time, long before systemd: O_NOCTTY everywhere. ☺

(Yes, the GNU and musl C libraries do not give this to you for fopen(). This is one of several reasons that fdopen() is still a useful mechanism.)

Service management with the nosh toolset’s service-manager takes a slightly different tack on this. Rather than always make dæmon processes into session leaders, each service being allocated its own kernel session object that then sees no use, only specific services also chain through setsid explicitly; such as [email protected]* services that use open-controlling-tty, [email protected]* services where of course agetty is setting the controlling terminal, and [email protected]* services. (As noted in the service source, mgetty calls setsid() itself.)

Further reading

Solution 2

systemd does not protect service programs against acquiring a controlling terminal. They have to protect themselves when opening user-specified log files, by using the O_NOCTTY flag.

$ rpm -q systemd
systemd-238-8.git0e0aa59.fc28.x86_64

$ systemctl cat test
# /etc/systemd/system/test.service
[Service]
Type=simple
ExecStart=/bin/sh -c "exec cat </dev/tty10 >/dev/tty10"

$ systemctl status test
● test.service
   Loaded: loaded (/etc/systemd/system/test.service; static; vendor preset: disabled)
   Active: active (running) since Fri 2018-06-01 11:28:41 BST; 1min 35s ago
 Main PID: 12173 (cat)
    Tasks: 1 (limit: 4915)
   Memory: 180.0K
   CGroup: /system.slice/test.service
           └─12173 cat

Jun 01 11:28:41 alan-laptop systemd[1]: Started test.service.

$ ps -ejf
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
...
root     12173     1 12173 12173  0 11:28 tty10    00:00:00 cat

I also confirmed that switching to tty10 and pressing Ctrl+C stops the cat process.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply