David van Moolenbroek da21d85025 Add PTYFS, Unix98 pseudo terminal support
This patch adds support for Unix98 pseudo terminals, that is,
posix_openpt(3), grantpt(3), unlockpt(3), /dev/ptmx, and /dev/pts/.
The latter is implemented with a new pseudo file system, PTYFS.

In effect, this patch adds secure support for unprivileged pseudo
terminal allocation, allowing programs such as tmux(1) to be used by
non-root users as well.  Test77 has been extended with new tests, and
no longer needs to run as root.

The new functionality is optional.  To revert to the old behavior,
remove the "ptyfs" entry from /etc/fstab.

Technical nodes:

o The reason for not implementing the NetBSD /dev/ptm approach is that
  implementing the corresponding ioctl (TIOCPTMGET) would require
  adding a number of extremely hairy exceptions to VFS, including the
  PTY driver having to create new file descriptors for its own device
  nodes.

o PTYFS is required for Unix98 PTYs in order to avoid that the PTY
  driver has to be aware of old-style PTY naming schemes and even has
  to call chmod(2) on a disk-backed file system.  PTY cannot be its
  own PTYFS since a character driver may currently not also be a file
  system.  However, PTYFS may be subsumed into a DEVFS in the future.

o The Unix98 PTY behavior differs somewhat from NetBSD's, in that
  slave nodes are created on ptyfs only upon the first call to
  grantpt(3).  This approach obviates the need to revoke access as
  part of the grantpt(3) call.

o Shutting down PTY may leave slave nodes on PTYFS, but once PTY is
  restarted, these leftover slave nodes will be removed before they
  create a security risk.  Unmounting PTYFS will make existing PTY
  slaves permanently unavailable, and absence of PTYFS will block
  allocation of new Unix98 PTYs until PTYFS is (re)mounted.

Change-Id: I822b43ba32707c8815fd0f7d5bb7a438f51421c1
2015-06-23 17:43:46 +00:00

600 lines
14 KiB
C

#include "inc.h"
#include <dev/pci/pciio.h>
#include <minix/fb.h>
#include <minix/i2c.h>
#include <minix/keymap.h>
#include <minix/sound.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/kbdio.h>
#include <sys/termios.h>
#include <sys/time.h>
const char *
char_ioctl_name(unsigned long req)
{
switch (req) {
NAME(MINIX_I2C_IOCTL_EXEC);
NAME(FBIOGET_VSCREENINFO);
NAME(FBIOPUT_VSCREENINFO);
NAME(FBIOGET_FSCREENINFO); /* TODO: print argument */
NAME(FBIOPAN_DISPLAY);
NAME(DSPIORATE);
NAME(DSPIOSTEREO);
NAME(DSPIOSIZE);
NAME(DSPIOBITS);
NAME(DSPIOSIGN);
NAME(DSPIOMAX);
NAME(DSPIORESET); /* no argument */
NAME(DSPIOFREEBUF);
NAME(DSPIOSAMPLESINBUF);
NAME(DSPIOPAUSE); /* no argument */
NAME(DSPIORESUME); /* no argument */
NAME(MIXIOGETVOLUME);
NAME(MIXIOGETINPUTLEFT);
NAME(MIXIOGETINPUTRIGHT);
NAME(MIXIOGETOUTPUT);
NAME(MIXIOSETVOLUME);
NAME(MIXIOSETINPUTLEFT);
NAME(MIXIOSETINPUTRIGHT);
NAME(MIXIOSETOUTPUT);
NAME(TIOCEXCL); /* no argument */
NAME(TIOCNXCL); /* no argument */
NAME(TIOCFLUSH);
NAME(TIOCGETA);
NAME(TIOCSETA);
NAME(TIOCSETAW);
NAME(TIOCSETAF);
NAME(TIOCGETD);
NAME(TIOCSETD);
NAME(TIOCGLINED);
NAME(TIOCSLINED);
NAME(TIOCSBRK); /* no argument */
NAME(TIOCCBRK); /* no argument */
NAME(TIOCSDTR); /* no argument */
NAME(TIOCCDTR); /* no argument */
NAME(TIOCGPGRP);
NAME(TIOCSPGRP);
NAME(TIOCOUTQ);
NAME(TIOCSTI);
NAME(TIOCNOTTY); /* no argument */
NAME(TIOCPKT);
NAME(TIOCSTOP); /* no argument */
NAME(TIOCSTART); /* no argument */
NAME(TIOCMSET); /* TODO: print argument */
NAME(TIOCMBIS); /* TODO: print argument */
NAME(TIOCMBIC); /* TODO: print argument */
NAME(TIOCMGET); /* TODO: print argument */
NAME(TIOCREMOTE);
NAME(TIOCGWINSZ);
NAME(TIOCSWINSZ);
NAME(TIOCUCNTL);
NAME(TIOCSTAT);
NAME(TIOCGSID);
NAME(TIOCCONS);
NAME(TIOCSCTTY); /* no argument */
NAME(TIOCEXT);
NAME(TIOCSIG); /* no argument */
NAME(TIOCDRAIN); /* no argument */
NAME(TIOCGFLAGS); /* TODO: print argument */
NAME(TIOCSFLAGS); /* TODO: print argument */
NAME(TIOCDCDTIMESTAMP); /* TODO: print argument */
NAME(TIOCRCVFRAME); /* TODO: print argument */
NAME(TIOCXMTFRAME); /* TODO: print argument */
NAME(TIOCPTMGET); /* TODO: print argument */
NAME(TIOCGRANTPT); /* no argument */
NAME(TIOCPTSNAME); /* TODO: print argument */
NAME(TIOCSQSIZE);
NAME(TIOCGQSIZE);
NAME(TIOCSFON); /* big IOCTL, not printing argument */
NAME(KIOCBELL);
NAME(KIOCSLEDS);
NAME(KIOCSMAP); /* not worth interpreting */
NAME(PCI_IOC_CFGREAD);
NAME(PCI_IOC_CFGWRITE);
NAME(PCI_IOC_BDF_CFGREAD);
NAME(PCI_IOC_BDF_CFGWRITE);
NAME(PCI_IOC_BUSINFO);
NAME(PCI_IOC_MAP);
NAME(PCI_IOC_UNMAP);
NAME(PCI_IOC_RESERVE);
NAME(PCI_IOC_RELEASE);
}
return NULL;
}
static void
put_i2c_op(struct trace_proc * proc, const char *name, i2c_op_t op)
{
const char *text = NULL;
if (!valuesonly) {
switch (op) {
TEXT(I2C_OP_READ);
TEXT(I2C_OP_READ_WITH_STOP);
TEXT(I2C_OP_WRITE);
TEXT(I2C_OP_WRITE_WITH_STOP);
TEXT(I2C_OP_READ_BLOCK);
TEXT(I2C_OP_WRITE_BLOCK);
}
}
if (text != NULL)
put_field(proc, name, text);
else
put_value(proc, name, "%d", op);
}
static void
put_sound_device(struct trace_proc * proc, const char * name, int device)
{
const char *text = NULL;
if (!valuesonly) {
switch (device) {
TEXT(Master);
TEXT(Dac);
TEXT(Fm);
TEXT(Cd);
TEXT(Line);
TEXT(Mic);
TEXT(Speaker);
TEXT(Treble);
TEXT(Bass);
}
}
if (text != NULL)
put_field(proc, name, text);
else
put_value(proc, name, "%d", device);
}
static void
put_sound_state(struct trace_proc * proc, const char * name, int state)
{
if (!valuesonly && state == ON)
put_field(proc, name, "ON");
else if (!valuesonly && state == OFF)
put_field(proc, name, "OFF");
else
put_value(proc, name, "%d", state);
}
static const struct flags flush_flags[] = {
FLAG(FREAD),
FLAG(FWRITE),
};
static const struct flags tc_iflags[] = {
FLAG(IGNBRK),
FLAG(BRKINT),
FLAG(IGNPAR),
FLAG(PARMRK),
FLAG(INPCK),
FLAG(ISTRIP),
FLAG(INLCR),
FLAG(IGNCR),
FLAG(ICRNL),
FLAG(IXON),
FLAG(IXOFF),
FLAG(IXANY),
FLAG(IMAXBEL),
};
static const struct flags tc_oflags[] = {
FLAG(OPOST),
FLAG(ONLCR),
FLAG(OXTABS),
FLAG(ONOEOT),
FLAG(OCRNL),
FLAG(ONOCR),
FLAG(ONLRET),
};
static const struct flags tc_cflags[] = {
FLAG(CIGNORE),
FLAG_MASK(CSIZE, CS5),
FLAG_MASK(CSIZE, CS6),
FLAG_MASK(CSIZE, CS7),
FLAG_MASK(CSIZE, CS8),
FLAG(CSTOPB),
FLAG(CREAD),
FLAG(PARENB),
FLAG(PARODD),
FLAG(HUPCL),
FLAG(CLOCAL),
FLAG(CRTSCTS),
FLAG(CDTRCTS),
FLAG(MDMBUF),
};
static const struct flags tc_lflags[] = {
FLAG(ECHOKE),
FLAG(ECHOE),
FLAG(ECHOK),
FLAG(ECHO),
FLAG(ECHONL),
FLAG(ECHOPRT),
FLAG(ECHOCTL),
FLAG(ISIG),
FLAG(ICANON),
FLAG(ALTWERASE),
FLAG(IEXTEN),
FLAG(EXTPROC),
FLAG(TOSTOP),
FLAG(FLUSHO),
FLAG(NOKERNINFO),
FLAG(PENDIN),
FLAG(NOFLSH),
};
static void
put_tty_disc(struct trace_proc * proc, const char * name, int disc)
{
const char *text = NULL;
if (!valuesonly) {
switch (disc) {
TEXT(TTYDISC);
TEXT(TABLDISC);
TEXT(SLIPDISC);
TEXT(PPPDISC);
TEXT(STRIPDISC);
TEXT(HDLCDISC);
}
}
if (text != NULL)
put_field(proc, name, text);
else
put_value(proc, name, "%d", disc);
}
static const struct flags kbd_leds[] = {
FLAG(KBD_LEDS_NUM),
FLAG(KBD_LEDS_CAPS),
FLAG(KBD_LEDS_SCROLL),
};
int
char_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr,
int dir)
{
minix_i2c_ioctl_exec_t *iie;
struct fb_var_screeninfo *fbvs;
struct volume_level *level;
struct inout_ctrl *inout;
struct termios *tc;
struct ptmget *pm;
struct winsize *ws;
struct kio_bell *bell;
struct kio_leds *leds;
struct pciio_cfgreg *pci_cfgreg;
struct pciio_bdf_cfgreg *pci_bdf_cfgreg;
struct pciio_businfo *pci_businfo;
struct pciio_map *pci_iomap;
struct pciio_acl *pci_acl;
switch (req) {
case MINIX_I2C_IOCTL_EXEC:
if ((iie = (minix_i2c_ioctl_exec_t *)ptr) == NULL)
return IF_OUT; /* we print only the request for now */
put_i2c_op(proc, "iie_op", iie->iie_op);
put_value(proc, "iie_addr", "0x%04x", iie->iie_addr);
return 0; /* TODO: print command/data/result */
case FBIOGET_VSCREENINFO:
if ((fbvs = (struct fb_var_screeninfo *)ptr) == NULL)
return IF_IN;
put_value(proc, "xres", "%"PRIu32, fbvs->xres);
put_value(proc, "yres", "%"PRIu32, fbvs->yres);
put_value(proc, "xres_virtual", "%"PRIu32, fbvs->xres_virtual);
put_value(proc, "yres_virtual", "%"PRIu32, fbvs->yres_virtual);
put_value(proc, "xoffset", "%"PRIu32, fbvs->xoffset);
put_value(proc, "yoffset", "%"PRIu32, fbvs->yoffset);
put_value(proc, "bits_per_pixel", "%"PRIu32,
fbvs->bits_per_pixel);
return 0;
case FBIOPUT_VSCREENINFO:
case FBIOPAN_DISPLAY:
if ((fbvs = (struct fb_var_screeninfo *)ptr) == NULL)
return IF_OUT;
put_value(proc, "xoffset", "%"PRIu32, fbvs->xoffset);
put_value(proc, "yoffset", "%"PRIu32, fbvs->yoffset);
return 0;
case DSPIORATE:
case DSPIOSTEREO:
case DSPIOSIZE:
case DSPIOBITS:
case DSPIOSIGN:
case DSPIOMAX:
case DSPIOFREEBUF:
case DSPIOSAMPLESINBUF:
if (ptr == NULL)
return dir;
put_value(proc, NULL, "%u", *(unsigned int *)ptr);
return IF_ALL;
case MIXIOGETVOLUME:
if ((level = (struct volume_level *)ptr) == NULL)
return dir;
if (dir == IF_OUT)
put_sound_device(proc, "device", level->device);
else {
put_value(proc, "left", "%d", level->left);
put_value(proc, "right", "%d", level->right);
}
return IF_ALL;
case MIXIOSETVOLUME:
/* Print the corrected volume levels only with verbosity on. */
if ((level = (struct volume_level *)ptr) == NULL)
return IF_OUT | ((verbose > 0) ? IF_IN : 0);
if (dir == IF_OUT)
put_sound_device(proc, "device", level->device);
put_value(proc, "left", "%d", level->left);
put_value(proc, "right", "%d", level->right);
return IF_ALL;
case MIXIOGETINPUTLEFT:
case MIXIOGETINPUTRIGHT:
case MIXIOGETOUTPUT:
if ((inout = (struct inout_ctrl *)ptr) == NULL)
return dir;
if (dir == IF_OUT)
put_sound_device(proc, "device", inout->device);
else {
put_sound_state(proc, "left", inout->left);
put_sound_state(proc, "right", inout->right);
}
return IF_ALL;
case MIXIOSETINPUTLEFT:
case MIXIOSETINPUTRIGHT:
case MIXIOSETOUTPUT:
if ((inout = (struct inout_ctrl *)ptr) == NULL)
return IF_OUT;
put_sound_device(proc, "device", inout->device);
put_sound_state(proc, "left", inout->left);
put_sound_state(proc, "right", inout->right);
return IF_ALL;
case TIOCFLUSH:
if (ptr == NULL)
return IF_OUT;
put_flags(proc, NULL, flush_flags, COUNT(flush_flags), "0x%x",
*(int *)ptr);
return IF_ALL;
case TIOCGETA:
case TIOCSETA:
case TIOCSETAW:
case TIOCSETAF:
if ((tc = (struct termios *)ptr) == NULL)
return dir;
/*
* These are fairly common IOCTLs, so printing everything by
* default would create a lot of noise. By default we limit
* ourselves to printing the field that contains what I
* consider to be the most important flag: ICANON.
* TODO: see if we can come up with a decent format for
* selectively printing (relatively important) flags.
*/
if (verbose > 0) {
put_flags(proc, "c_iflag", tc_iflags, COUNT(tc_iflags),
"0x%x", tc->c_iflag);
put_flags(proc, "c_oflag", tc_oflags, COUNT(tc_oflags),
"0x%x", tc->c_oflag);
put_flags(proc, "c_cflag", tc_cflags, COUNT(tc_cflags),
"0x%x", tc->c_cflag);
}
put_flags(proc, "c_lflag", tc_lflags, COUNT(tc_lflags), "0x%x",
tc->c_lflag);
if (verbose > 0) {
put_value(proc, "c_ispeed", "%d", tc->c_ispeed);
put_value(proc, "c_ospeed", "%d", tc->c_ospeed);
}
return 0; /* TODO: print the c_cc fields */
case TIOCGETD:
case TIOCSETD:
if (ptr == NULL)
return dir;
put_tty_disc(proc, NULL, *(int *)ptr);
return IF_ALL;
case TIOCGLINED:
case TIOCSLINED:
if (ptr == NULL)
return dir;
put_buf(proc, NULL, PF_LOCADDR | PF_STRING, (vir_bytes)ptr,
sizeof(linedn_t));
return IF_ALL;
case TIOCGPGRP:
case TIOCSPGRP:
case TIOCOUTQ:
case TIOCPKT:
case TIOCREMOTE:
case TIOCUCNTL:
case TIOCSTAT: /* argument seems unused? */
case TIOCGSID:
case TIOCCONS: /* argument seems unused? */
case TIOCEXT:
case TIOCSQSIZE:
case TIOCGQSIZE:
/* Print a simple integer. */
if (ptr == NULL)
return dir;
put_value(proc, NULL, "%d", *(int *)ptr);
return IF_ALL;
case TIOCPTSNAME:
if ((pm = (struct ptmget *)ptr) == NULL)
return IF_IN;
put_buf(proc, "sn", PF_LOCADDR | PF_STRING, (vir_bytes)pm->sn,
sizeof(pm->sn));
return IF_ALL;
case TIOCSTI:
if (ptr == NULL)
return dir;
if (!valuesonly)
put_value(proc, NULL, "'%s'",
get_escape(*(char *)ptr));
else
put_value(proc, NULL, "%u", *(char *)ptr);
return IF_ALL;
case TIOCGWINSZ:
case TIOCSWINSZ:
if ((ws = (struct winsize *)ptr) == NULL)
return dir;
/* This is a stupid order, but we follow the struct layout. */
put_value(proc, "ws_row", "%u", ws->ws_row);
put_value(proc, "ws_col", "%u", ws->ws_col);
if (verbose > 0) {
put_value(proc, "ws_xpixel", "%u", ws->ws_xpixel);
put_value(proc, "ws_ypixel", "%u", ws->ws_ypixel);
}
return (verbose > 0) ? IF_ALL : 0;
case KIOCBELL:
if ((bell = (struct kio_bell *)ptr) == NULL)
return IF_OUT;
put_value(proc, "kb_pitch", "%u", bell->kb_pitch);
put_value(proc, "kb_volume", "%lu", bell->kb_volume);
put_struct_timeval(proc, "kb_duration", PF_LOCADDR,
(vir_bytes)&bell->kb_duration);
return IF_ALL;
case KIOCSLEDS:
if ((leds = (struct kio_leds *)ptr) == NULL)
return IF_OUT;
put_flags(proc, "kl_bits", kbd_leds, COUNT(kbd_leds), "0x%x",
leds->kl_bits);
return IF_ALL;
case PCI_IOC_CFGREAD:
if ((pci_cfgreg = (struct pciio_cfgreg *)ptr) == NULL)
return IF_IN;
put_ptr(proc, "reg", (vir_bytes)pci_cfgreg->reg);
put_value(proc, "val", "%08x", pci_cfgreg->val);
return IF_ALL;
case PCI_IOC_CFGWRITE:
if ((pci_cfgreg = (struct pciio_cfgreg *)ptr) == NULL)
return IF_OUT;
put_ptr(proc, "reg", (vir_bytes)pci_cfgreg->reg);
put_value(proc, "val", "%08x", pci_cfgreg->val);
return IF_ALL;
case PCI_IOC_BDF_CFGREAD:
if ((pci_bdf_cfgreg = (struct pciio_bdf_cfgreg *)ptr) == NULL)
return IF_IN;
put_value(proc, "bus", "%u", pci_bdf_cfgreg->bus);
put_value(proc, "device", "%u", pci_bdf_cfgreg->device);
put_value(proc, "function", "%u", pci_bdf_cfgreg->function);
put_ptr(proc, "cfgreg.reg", (vir_bytes)pci_bdf_cfgreg->cfgreg.reg);
put_value(proc, "cfgreg.val", "%08x", pci_bdf_cfgreg->cfgreg.val);
return IF_ALL;
case PCI_IOC_BDF_CFGWRITE:
if ((pci_bdf_cfgreg = (struct pciio_bdf_cfgreg *)ptr) == NULL)
return IF_OUT;
put_value(proc, "bus", "%u", pci_bdf_cfgreg->bus);
put_value(proc, "device", "%u", pci_bdf_cfgreg->device);
put_value(proc, "function", "%u", pci_bdf_cfgreg->function);
put_ptr(proc, "cfgreg.reg", (vir_bytes)pci_bdf_cfgreg->cfgreg.reg);
put_value(proc, "cfgreg.val", "%08x", pci_bdf_cfgreg->cfgreg.val);
return IF_ALL;
case PCI_IOC_BUSINFO:
if ((pci_businfo = (struct pciio_businfo *)ptr) == NULL)
return IF_IN;
put_value(proc, "busno", "%u", pci_businfo->busno);
put_value(proc, "maxdevs", "%u", pci_businfo->maxdevs);
return IF_ALL;
case PCI_IOC_MAP:
if ((pci_iomap = (struct pciio_map *)ptr) == NULL)
return IF_OUT|IF_IN;
put_value(proc, "flags", "%x", pci_iomap->flags);
put_value(proc, "phys_offset", "%08x", pci_iomap->phys_offset);
put_value(proc, "size", "%zu", pci_iomap->size);
put_value(proc, "readonly", "%x", pci_iomap->readonly);
if (IF_IN == dir)
put_ptr(proc, "vaddr_ret", (vir_bytes)pci_iomap->vaddr_ret);
return IF_ALL;
case PCI_IOC_UNMAP:
if ((pci_iomap = (struct pciio_map *)ptr) == NULL)
return IF_OUT;
put_ptr(proc, "vaddr", (vir_bytes)pci_iomap->vaddr);
return IF_ALL;
case PCI_IOC_RESERVE:
if ((pci_acl = (struct pciio_acl *)ptr) == NULL)
return IF_OUT;
put_value(proc, "domain", "%u", pci_acl->domain);
put_value(proc, "bus", "%u", pci_acl->bus);
put_value(proc, "device", "%u", pci_acl->device);
put_value(proc, "function", "%u", pci_acl->function);
return IF_ALL;
case PCI_IOC_RELEASE:
if ((pci_acl = (struct pciio_acl *)ptr) == NULL)
return IF_OUT;
put_value(proc, "domain", "%u", pci_acl->domain);
put_value(proc, "bus", "%u", pci_acl->bus);
put_value(proc, "device", "%u", pci_acl->device);
put_value(proc, "function", "%u", pci_acl->function);
return IF_ALL;
default:
return 0;
}
}