The old AT&T System 5 mechanism for pseudo-terminal slave devices was that they were ordinary persistent character device nodes under
/dev. There was a multiplexor master device at
/dev/ptmx. The old 4.3BSD mechanism for pseudo-terminal devices had parallel pairs of ordinary persistent master and slave device nodes under
In both cases, this meant that the slave device files retained their last ownership and permissions after last file descriptor closure. Hence the evolution of the
grantpt() function to fix up the ownership and permissions of the slave device file after a (re-used) pseudo-terminal had been (re-)allocated.
This in turn meant that there was a window when a program was setting up a re-used pseudo-terminal between the
open() and the
grantpt() where whoever had owned the slave device beforehand could sneak in and open it as well, potentially gaining access to someone else's terminal. Hence the idea of pseudo-terminal slave character devices starting in a locked state where they could not be opened and being unlocked by
unlockpt() after the
grantpt() had been successfully performed.
Over the years, it turned out that this was unnecessary.
Nowadays, the slave device files are not persistent, because the kernel makes and destroys things in
/dev itself. The act of opening the master device either resets the slave device permissions and ownership, or outright creates the slave device file afresh (in the latter case with the slave device file disappearing again when all open file descriptors are closed), in either case atomically in the same system call.
- On OpenBSD, this is part of the
PTMGET I/O control's functionality on the
/dev is still a disc volume, and the kernel internally issues the relevant calls to create new device nodes there and reset their ownerships and permissions.
- On FreeBSD, this is done by the
posix_openpt() system call.
/dev is not a disc volume at all. It is a
devfs filesystem. It contains no "multiplexor" device nor master device files, because
posix_openpt() is an outright system call, not an wrapped
ioctl() on an open file descriptor. Slave devices appear in the
devfs filesystem under its
The kernel thus ensures that they have the right permissions and ownership ab initio, and there is no window of opportunity where they have stale ones. Thus the
unlockpt() library functions are essentially no-ops, whose sole remaining functionality is to check their passed file descriptor and set
EINVAL if it isn't the master side of a pseudo-terminal, because programs might be doing daft things like passing non-pseudo-terminal file descriptors to these functions and expecting them to return errors.
For a while on Linux, pseudo-terminal slave devices were persistent device nodes. The GNU C library's
grantpt() wasn't a system call. Rather, it forked and executed a set-UID helper program named
pt_chown, much to the dismay of the no set-UID executables crowd. (
grantpt() has to allow an unprivileged user to change the ownership and permissions of a special device file that it does not necessarily own, remember.) So there was still the window of opportunity, and Linux still had to maintain a lock for
devpts filesystem (where "new" means introduced quite a few years ago, now) almost permits the same way of doing things as on FreeBSD with
devfs, however. There are some differences.
- There is still a "multiplexor" device.
- In the older "new"
devpts system, this was a
ptmx device in a different
devtmpfs filesystem, with the
devpts filesystem containing only the automatically created/destroyed slave device files. Conventionally the setup was
/dev/ptmx and an accompanying
devpts mount at
- But Linux people wanted to have multiple wholly independent instances of the
devpts filesystem, for containers and the like, and it turned out to be quite hard synchronizing the (correct) two filesystems when there were many
devpts filesystems. So in the newer "new"
devpts system all of the devices, multiplexor and slave, are in the one filesystem. For backwards compatibility, the default was for the new
ptmx node to be inaccessible unless one set a new
ptmxmode mount option.
- In the even newer still "new"
ptmx device file in the
devpts filesystem is now the primary multiplexor, and the
ptmx in the
devtmpfs is either a shim provided by the kernel that tries to mimic a symbolic link, a bind mount, or a plain old actual symbolic link to
- The kernel does not always set up the ownership and permissions as
grantpt() should. Setting the wrong mount options, either a
gid other than the
tty GID or a
mode other than 0620, triggers fallback behaviour in the GNU C library. In order to reduce
grantpt() to a no-operation in the GNU C library as desired, the kernel must not assign the group of the opening process (i.e. there must be an explicit
gid setting), the group assigned must be the
tty group, and the
mode of newly created slave devices must be exactly 0620.
Not switching on
/dev/pts/ptmx by default and the GNU C library not wholly reducing
grantpt() to a no-op are both because the kernel and the C library are not maintained in lockstep. Each had to operate with older versions of the other. Linux still had to provide an older
/dev/ptmx. The GNU C library still has to fall back to running
pt_chown if there's not a new
devpts filesystem with the correct mount options in place.
The window of opportunity thus still exists for
unlockpt() to guard against on Linux, if the
devpts mount options are wrong and the GNU C library consequently has to fall back to actually doing something in