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_LOCK
viaioctl(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:36grantpt
which tells its a "leftover" and indeed alsounlockpt
seemed 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:55unlockpt
is 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 changingp
tot
, 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