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 /dev.
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/ptm device. /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 pts/ directory.
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 grantpt() and 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 unlockpt().
Its "new" 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 /dev/pts.
- 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 devtmpfs and 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"
devpts the 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 pts/ptmx.
- 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 grantpt().
Further reading
unlockpt()is a NOP on *BSD (it's just checking if the passed fd is a tty), and on linux it's just clearing a flag that prevents opening, nothing more. (TTY_PTY_LOCKviaioctl(TIOCSPTLCK)). So any portable program cannot rely onunlockpt()to do anything at all. It's probably just a leftover from the old-style (non-multiplexed) pseudo-tty implementation (just likegrantpt()). – Oct 23 '18 at 12:36grantptwhich tells its a "leftover" and indeed alsounlockptseemed somewhat of an cargo-cult anoyance. So it might be obsolete, sure and I have no insight or knowledge about the "old-style"(non-multiplexed) ptty you mention. However given it some more though, a process offering a ptty might be interested that only certain other processes can access this resource (e.g. because input from the ptty slave is evaluated with difference priveledges). This thinking made me change the title to reflect this aspect. – humanityANDpeace Oct 23 '18 at 12:55unlockptis only left over "cargo-cult" stuff that is not anymore necessary on BSDs. Have to reread it again to get if that is true for Linux kernels too. I would like to link to your answer, but I do not really feel that the specific question here is a duplicate of the downvoted one your answer is attached to. if you feel like giving a succinct answer here I would appreciate that :) – humanityANDpeace Oct 23 '18 at 14:36/dev/ptmx, but some 50/dev/ptyp0,/dev/ttyp0,/dev/ptyp1, etc. pairs of device nodes (I hope I remember the names right), and you had to try the masters (/dev/ptypXX) until you found one that was free, open it, get the name of the slave from it by changingptot, set the right perms on the slave (which was usually done by forking and exec'ing a setuid program and wait for it to terminate), and "unlock" the tty after all this had succeeded. Thegrantpt()andunlockpt()were convenient wrappers around that. – Oct 23 '18 at 17:38