PATCH: Unix98 ptys (latest version)

C. Scott Ananian (cananian@lcs.mit.edu)
Fri, 23 Jan 1998 19:04:40 -0500 (EST)


Attached is the latest (and last) version of my Unix98 pty-mux patch. It
adds unlockpt() functionality for full security of the pty (there is
a race condition without the unlockpt() call). grantpt() and the
associated ch{own,mod}() functionality is not and will not be included in
this patch; grantpt() is best implemented in user-land. This patch adds a
new device /dev/ptmx, which has been registered on the linux device list,
and adds two new ioctls: TIOCGPTN, to implement ptsname(), and TIOCSPTLCK,
to implement unlockpt(). Zack Weinberg has written the official libc
support for these ptys.

Changes from last release: the TIOCSPTLCK ioctl has been added, and the
ioctls have been renumbered using the _IO* macros. Test programs using
the old ioctl numbering should be recompiled with this final version of
the patch.

This patch has been tested. Further test reports are, of course,
welcome. Please watch the follow-ups on any reply, as this message is
rather aggressively cross-posted.
--Scott

[attached: first, 'ptylib.c', sample code to implement the Unix98 pty
functions using the new device and ioctls. Next, 'pt_chmod.c' a sample
setuid-root helper application to implement the Unix98 grantpt() function
(this is the same method used by Solaris). Finally, the kernel patch
itself. Note that Zack Weinberg is the libc hacker -- my sample library
implementations are only quick-and-dirty guides.]
@ @
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-oOO-(_)-OOo-=-=-=-=-=
C. Scott Ananian: cananian@lcs.mit.edu / Declare the Truth boldly and
Laboratory for Computer Science/Crypto / without hindrance.
Massachusetts Institute of Technology /META-PARRESIAS AKOLUTOS:Acts 28:31
-.-. .-.. .. ..-. ..-. --- .-. -.. ... -.-. --- - - .- -. .- -. .. .- -.
PGP key available via finger and from http://www.pdos.lcs.mit.edu/~cananian

------------- begin ptylib.c, sample pty library -------------------
/* Sample implementation of Unix98 pty functions using new kernel code.
*
* C. Scott Ananian <cananian@alumni.princeton.edu>, 15-Jan-1998
*/

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include "ptylib.h"

char *ptsname(int fildes)
{
static const char pty1[]={ "pqrstuvwxyzabcde" };
static const char pty2[]={ "0123456789abcdef" };
static char buf[64];
struct stat s;
unsigned int minor;

if (ioctl(fildes, TIOCGPTN, &minor)<0) return NULL;

snprintf(buf, sizeof(buf), "/dev/pts/%u", minor);
if (stat(buf, &s)==0) return buf;

if (!(minor<256)) return NULL;
snprintf(buf, sizeof(buf), "/dev/tty%c%c",pty1[minor/16],pty2[minor%16]);
if (stat(buf, &s)==0) return buf;

return NULL;
}

int grantpt(int fildes)
{
char buf[10];
int status;
pid_t pid;

switch(pid=fork()) {
case -1: /* failure. */
errno = EACCES;
return -1;
case 0: /* child */
snprintf(buf, sizeof(buf), "%d", fildes);
execle("/usr/lib/pt_chmod","pt_chmod",buf,NULL,NULL);
errno = EACCES;
return -1;
default: /* parent */
waitpid(pid, &status, 0);
printf("pt_chmod status: %d\n", status);
switch(status) {
case 0: /* A-OK */
return 0;
case 1: /* bad file descriptor */
errno = EBADF;
return -1;
case 2: /* not a pty */
errno = EINVAL;
return -1;
default:
errno = EACCES;
return -1;
}
}
}
int unlockpt(int fildes)
{
int val=0;
return ioctl(fildes, TIOCSPTLCK, &val);
}
------------- end ptylib.c --------------

------------- begin pt_chmod.c, sample helper app -------------
/* pt_chmod.c ... securely implement grantpt in user-land.
* C. Scott Ananian <cananian@alumni.princeton.edu>
*/

#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <grp.h>
#include "ptylib.h"

#ifndef TTY_GROUP
#define TTY_GROUP "tty"
#endif

void usage()
{
const char *usage_string =
"pt_chmod usage:\n"
"\t pt_chmod [file-descriptor] -- unlockpt(fd)\n"
;
fputs(usage_string, stderr);
exit(1);
}

int main(int argc, char **argv)
{
struct group * grp;
struct stat s;
char *pty, *cp;
int fd;

if (argc != 2) usage();
fd = strtol(argv[1], &cp, 10);
if (*argv[1]=='\0' || *cp=='\0' || fd<0) usage();

/* Check that fd is a valid pty master -- call ptsname() */
pty = ptsname(fd);
if (pty == NULL) {
fprintf(stderr, "File descriptor not a pty.\n");
exit(2);
}
close(fd);

/* Check that target file is a character device */
if (stat(pty, &s)!=0) {
fprintf(stderr, "Can't stat pty slave %s.\n", pty);
exit(3);
}
if (!S_ISCHR(s.st_mode)) {
fprintf(stderr, "%s is not character device.\n", pty);
exit(4);
}

grp = getgrnam(TTY_GROUP);

if (chown(pty, getuid(), grp==NULL?getgid():grp->gr_gid)!=0) {
fprintf(stderr, "chown(%s, %d, %d) unsuccessful.\n",
pty, getuid(), grp==NULL?getgid():grp->gr_gid);
exit(5);
}
if (chmod(pty, 0620)!=0) {
fprintf(stderr, "chmod(%s, 0620) unsuccessful.\n", pty);
exit(6);
}
exit(0);
}
--------------- end pt_chmod.c -------------

------- begin kernel patch 23-Jan-1998 ----------------------------------
diff -ruHp -X badboys linux-2.1.80-clean/CREDITS linux-2.1.80-pty/CREDITS
--- linux-2.1.80-clean/CREDITS Wed Jan 21 01:10:56 1998
+++ linux-2.1.80-pty/CREDITS Thu Jan 22 19:47:12 1998
@@ -33,6 +33,12 @@ S: Klaproosstraat 72 c 10
S: B-2610 Wilrijk-Antwerpen
S: Belgium

+N: C. Scott Ananian
+E: cananian@alumni.princeton.edu
+W: http://www.pdos.lcs.mit.edu/~cananian
+P: 1024/85AD9EED AD C0 49 08 91 67 DF D7 FA 04 1A EE 09 E8 44 B0
+D: pty improvements.
+
N: Erik Andersen
E: andersee@debian.org
W: http://www.inconnect.com/~andersen
diff -ruHp -X badboys linux-2.1.80-clean/drivers/char/pty.c linux-2.1.80-pty/drivers/char/pty.c
--- linux-2.1.80-clean/drivers/char/pty.c Sat Jun 28 13:31:36 1997
+++ linux-2.1.80-pty/drivers/char/pty.c Fri Jan 23 02:46:43 1998
@@ -2,6 +2,9 @@
* linux/drivers/char/pty.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Added support for a Unix98-style ptmx device.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
*/

#include <linux/errno.h>
@@ -191,6 +194,45 @@ static int pty_chars_in_buffer(struct tt
return ((count < N_TTY_BUF_SIZE/2) ? 0 : count);
}

+/*
+ * Return the minor device number of a given pty. This lets us
+ * open a master pty with the multi-headed ptmx device, then
+ * find out which one we got after it is open, with an ioctl.
+ */
+static int pty_get_device_minor(struct tty_struct *tty, unsigned int *value)
+{
+ unsigned int result = MINOR(tty->device);
+ return put_user(result, value);
+}
+/* Set the lock flag on a pty */
+static int pty_set_lock(struct tty_struct *tty, int * arg)
+{
+ int val;
+ if (get_user(val,arg))
+ return -EFAULT;
+ if (val)
+ set_bit(TTY_PTY_LOCK, &tty->flags);
+ else
+ clear_bit(TTY_PTY_LOCK, &tty->flags);
+ return 0;
+}
+
+static int pty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ if (!tty) {
+ printk("pty_ioctl called with NULL tty!\n");
+ return -EIO;
+ }
+ switch(cmd) {
+ case TIOCGPTN: /* Get PT Number */
+ return pty_get_device_minor(tty, (unsigned int *)arg);
+ case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
+ return pty_set_lock(tty, (int *) arg);
+ }
+ return -ENOIOCTLCMD;
+}
+
static void pty_flush_buffer(struct tty_struct *tty)
{
struct tty_struct *to = tty->link;
@@ -225,6 +264,8 @@ static int pty_open(struct tty_struct *t
retval = -EIO;
if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
goto out;
+ if (test_bit(TTY_PTY_LOCK, &tty->link->flags))
+ goto out;
if (tty->link->count != 1)
goto out;

@@ -304,6 +345,12 @@ __initfunc(int pty_init(void))
old_pty_slave_driver.minor_start = 192;
old_pty_slave_driver.num = (NR_PTYS > 64) ? 64 : NR_PTYS;
old_pty_slave_driver.other = &old_pty_driver;
+
+ /* only the master pty gets this ioctl (which is why we
+ * assign it here, instead of up with the rest of the
+ * pty_driver initialization. <cananian@alumni.princeton.edu>
+ */
+ pty_driver.ioctl = pty_ioctl;

if (tty_register_driver(&pty_driver))
panic("Couldn't register pty driver");
diff -ruHp -X badboys linux-2.1.80-clean/drivers/char/tty_io.c linux-2.1.80-pty/drivers/char/tty_io.c
--- linux-2.1.80-clean/drivers/char/tty_io.c Wed Dec 31 19:40:08 1997
+++ linux-2.1.80-pty/drivers/char/tty_io.c Fri Jan 23 02:35:59 1998
@@ -51,6 +51,9 @@
*
* Rewrote init_dev and release_dev to eliminate races.
* -- Bill Hawes <whawes@star.net>, June 97
+ *
+ * Added support for a Unix98-style ptmx device.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
*/

#include <linux/config.h>
@@ -91,6 +94,7 @@
#define CONSOLE_DEV MKDEV(TTY_MAJOR,0)
#define TTY_DEV MKDEV(TTYAUX_MAJOR,0)
#define SYSCONS_DEV MKDEV(TTYAUX_MAJOR,1)
+#define PTMX_DEV MKDEV(TTYAUX_MAJOR,2)

#undef TTY_DEBUG_HANGUP

@@ -1171,7 +1175,6 @@ static void release_dev(struct file * fi
static int tty_open(struct inode * inode, struct file * filp)
{
struct tty_struct *tty;
- int minor;
int noctty, retval;
kdev_t device;
unsigned short saved_flags;
@@ -1203,12 +1206,39 @@ retry_open:
device = c->device(c);
noctty = 1;
}
- minor = MINOR(device);
-
+ if (device == PTMX_DEV) {
+ /* find a free pty. */
+ struct tty_driver *driver = tty_drivers;
+ int minor;
+
+ /* find the pty driver */
+ for (driver=tty_drivers; driver; driver=driver->next)
+ if (driver->major == PTY_MASTER_MAJOR)
+ break;
+ if (!driver) return -ENODEV;
+
+ /* find a minor device that is not in use. */
+ for (minor=driver->minor_start;
+ minor<driver->minor_start+driver->num;
+ minor++) {
+ device = MKDEV(driver->major, minor);
+ retval = init_dev(device, &tty);
+ if (retval==0) break; /* success! */
+ }
+ if (minor==driver->minor_start+driver->num) /* no success */
+ return -EIO; /* no free ptys */
+
+ set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
+ noctty = 1;
+ goto init_dev_done;
+ }
+
retval = init_dev(device, &tty);
if (retval)
return retval;
+
/* N.B. this error exit may leave filp->f_flags with O_NONBLOCK set */
+init_dev_done:
filp->private_data = tty;
check_tty_count(tty, "tty_open");
if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
@@ -1932,7 +1962,7 @@ long console_init(long kmem_start, long
}

static struct tty_driver dev_tty_driver, dev_console_driver,
- dev_syscons_driver;
+ dev_syscons_driver, dev_ptmx_driver;

/*
* Ok, now we can initialize the rest of the tty devices and can count
@@ -1973,6 +2003,17 @@ __initfunc(int tty_init(void))

if (tty_register_driver(&dev_syscons_driver))
panic("Couldn't register /dev/console driver\n");
+
+ dev_ptmx_driver = dev_tty_driver;
+ dev_ptmx_driver.driver_name = "/dev/ptmx";
+ dev_ptmx_driver.name = dev_ptmx_driver.driver_name + 5;
+ dev_ptmx_driver.major= MAJOR(PTMX_DEV);
+ dev_ptmx_driver.minor_start = MINOR(PTMX_DEV);
+ dev_ptmx_driver.type = TTY_DRIVER_TYPE_SYSTEM;
+ dev_ptmx_driver.subtype = SYSTEM_TYPE_SYSPTMX;
+
+ if (tty_register_driver(&dev_ptmx_driver))
+ panic("Couldn't register /dev/ptmx driver\n");

#ifdef CONFIG_VT
dev_console_driver = dev_tty_driver;
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-alpha/ioctls.h linux-2.1.80-pty/include/asm-alpha/ioctls.h
--- linux-2.1.80-clean/include/asm-alpha/ioctls.h Thu Dec 4 16:09:01 1997
+++ linux-2.1.80-pty/include/asm-alpha/ioctls.h Fri Jan 23 00:02:47 1998
@@ -86,6 +86,8 @@
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
+#define TIOCGPTN _IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
+#define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */

#define TIOCSERCONFIG 0x5453
#define TIOCSERGWILD 0x5454
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-arm/ioctls.h linux-2.1.80-pty/include/asm-arm/ioctls.h
--- linux-2.1.80-clean/include/asm-arm/ioctls.h Tue Jan 20 19:39:42 1998
+++ linux-2.1.80-pty/include/asm-arm/ioctls.h Fri Jan 23 00:02:23 1998
@@ -47,6 +47,8 @@
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
+#define TIOCGPTN _IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
+#define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */

#define FIONCLEX 0x5450 /* these numbers need to be adjusted. */
#define FIOCLEX 0x5451
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-i386/ioctls.h linux-2.1.80-pty/include/asm-i386/ioctls.h
--- linux-2.1.80-clean/include/asm-i386/ioctls.h Thu Dec 4 16:09:01 1997
+++ linux-2.1.80-pty/include/asm-i386/ioctls.h Fri Jan 23 00:02:06 1998
@@ -47,6 +47,8 @@
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
+#define TIOCGPTN _IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
+#define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */

#define FIONCLEX 0x5450 /* these numbers need to be adjusted. */
#define FIOCLEX 0x5451
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-m68k/ioctls.h linux-2.1.80-pty/include/asm-m68k/ioctls.h
--- linux-2.1.80-clean/include/asm-m68k/ioctls.h Thu Dec 4 16:09:01 1997
+++ linux-2.1.80-pty/include/asm-m68k/ioctls.h Fri Jan 23 00:01:41 1998
@@ -47,6 +47,8 @@
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
+#define TIOCGPTN _IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
+#define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */

#define FIONCLEX 0x5450 /* these numbers need to be adjusted. */
#define FIOCLEX 0x5451
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-mips/ioctls.h linux-2.1.80-pty/include/asm-mips/ioctls.h
--- linux-2.1.80-clean/include/asm-mips/ioctls.h Thu Jan 22 19:39:23 1998
+++ linux-2.1.80-pty/include/asm-mips/ioctls.h Fri Jan 23 00:00:55 1998
@@ -99,6 +99,8 @@
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
+#define TIOCGPTN _IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
+#define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */

#define TIOCSERCONFIG 0x5488
#define TIOCSERGWILD 0x5489
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-ppc/ioctls.h linux-2.1.80-pty/include/asm-ppc/ioctls.h
--- linux-2.1.80-clean/include/asm-ppc/ioctls.h Thu Dec 4 16:09:01 1997
+++ linux-2.1.80-pty/include/asm-ppc/ioctls.h Fri Jan 23 00:04:17 1998
@@ -86,6 +86,8 @@
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
+#define TIOCGPTN _IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
+#define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */

#define TIOCSERCONFIG 0x5453
#define TIOCSERGWILD 0x5454
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-sparc/ioctls.h linux-2.1.80-pty/include/asm-sparc/ioctls.h
--- linux-2.1.80-clean/include/asm-sparc/ioctls.h Thu Dec 4 16:09:01 1997
+++ linux-2.1.80-pty/include/asm-sparc/ioctls.h Thu Jan 22 23:58:38 1998
@@ -75,6 +75,9 @@
#define TIOCGPGRP _IOR('t', 131, int)
#define TIOCSCTTY _IO('t', 132)
#define TIOCGSID _IOR('t', 133, int)
+/* Get minor device of a pty master's FD -- Solaris equiv is ISPTM */
+#define TIOCGPTN _IOR('t', 134, unsigned int) /* Get Pty Number */
+#define TIOCSPTLCK _IOW('t', 135, int) /* Lock/unlock PTY */

/* Little f */
#define FIOCLEX _IO('f', 1)
diff -ruHp -X badboys linux-2.1.80-clean/include/asm-sparc64/ioctls.h linux-2.1.80-pty/include/asm-sparc64/ioctls.h
--- linux-2.1.80-clean/include/asm-sparc64/ioctls.h Mon Jan 12 18:15:58 1998
+++ linux-2.1.80-pty/include/asm-sparc64/ioctls.h Fri Jan 23 00:04:19 1998
@@ -76,6 +76,9 @@
#define TIOCGPGRP _IOR('t', 131, int)
#define TIOCSCTTY _IO('t', 132)
#define TIOCGSID _IOR('t', 133, int)
+/* Get minor device of a pty master's FD -- Solaris equiv is ISPTM */
+#define TIOCGPTN _IOR('t', 134, unsigned int) /* Get Pty Number */
+#define TIOCSPTLCK _IOW('t', 135, int) /* Lock/unlock PTY */

/* Little f */
#define FIOCLEX _IO('f', 1)
diff -ruHp -X badboys linux-2.1.80-clean/include/linux/tty.h linux-2.1.80-pty/include/linux/tty.h
--- linux-2.1.80-clean/include/linux/tty.h Tue Jan 20 19:51:56 1998
+++ linux-2.1.80-pty/include/linux/tty.h Thu Jan 22 23:54:46 1998
@@ -282,6 +282,7 @@ struct tty_struct {
#define TTY_CLOSING 7
#define TTY_HW_COOK_OUT 14
#define TTY_HW_COOK_IN 15
+#define TTY_PTY_LOCK 16

#define TTY_WRITE_FLUSH(tty) tty_write_flush((tty))

diff -ruHp -X badboys linux-2.1.80-clean/include/linux/tty_driver.h linux-2.1.80-pty/include/linux/tty_driver.h
--- linux-2.1.80-clean/include/linux/tty_driver.h Tue Jan 20 19:51:56 1998
+++ linux-2.1.80-pty/include/linux/tty_driver.h Thu Jan 22 19:47:13 1998
@@ -213,6 +213,7 @@ struct tty_driver {
#define SYSTEM_TYPE_TTY 0x0001
#define SYSTEM_TYPE_CONSOLE 0x0002
#define SYSTEM_TYPE_SYSCONS 0x0003
+#define SYSTEM_TYPE_SYSPTMX 0x0004

/* pty subtypes (magic, used by tty_io.c) */
#define PTY_TYPE_MASTER 0x0001

---------------- end kernel patch ----------------