diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/util/pty/ChangeLog | 18 | ||||
| -rw-r--r-- | src/util/pty/getpty.c | 14 | ||||
| -rw-r--r-- | src/util/pty/pty-int.h | 1 | ||||
| -rw-r--r-- | src/util/pty/pty_paranoia.c | 382 |
4 files changed, 379 insertions, 36 deletions
diff --git a/src/util/pty/ChangeLog b/src/util/pty/ChangeLog index eda91e588..7b21d6d03 100644 --- a/src/util/pty/ChangeLog +++ b/src/util/pty/ChangeLog @@ -1,3 +1,21 @@ +2001-05-15 Tom Yu <tlyu@mit.edu> + + * getpty.c: Make pty_getpty() into ptyint_getpty_ext(), which has + an extra argument that determines whether to call grantpt() and + unlockpt() on systems that support it. The new pty_getpty() will + simply call the extended version. This is to support some + wackiness needed by pty_paranoia.c tests. + + * pty-int.h: Add prototype for ptyint_getpty_ext(). + + * pty_paranoia.c: Add rant about ptys and quirks therein. Needs + to be updated somewhat. Add some more paranoia for the case where + we actually succeed in opening the slave of a closed master and + then succeed in opening the same master. This program will get + rewritten at some point to actually see what things result in EOFs + and under what conditions data will actually get passed between + master and slave. + 2001-05-10 Tom Yu <tlyu@mit.edu> * pty_paranoia.c: New file; do many paranoid checks about ctty diff --git a/src/util/pty/getpty.c b/src/util/pty/getpty.c index f02b87ee8..0e86514b6 100644 --- a/src/util/pty/getpty.c +++ b/src/util/pty/getpty.c @@ -24,9 +24,8 @@ #include "libpty.h" #include "pty-int.h" -long pty_getpty (fd, slave, slavelength) - int slavelength; - int *fd; char *slave; +long +ptyint_getpty_ext(int *fd, char *slave, int slavelength, int do_grantpt) { #if !defined(HAVE__GETPTY) && !defined(HAVE_OPENPTY) char *cp; @@ -79,7 +78,8 @@ long pty_getpty (fd, slave, slavelength) if (*fd >= 0) { #if defined(HAVE_GRANTPT)&&defined(HAVE_STREAMS) - if (grantpt(*fd) || unlockpt(*fd)) return PTY_GETPTY_STREAMS; + if (do_grantpt) + if (grantpt(*fd) || unlockpt(*fd)) return PTY_GETPTY_STREAMS; #endif #ifdef HAVE_PTSNAME @@ -142,3 +142,9 @@ long pty_getpty (fd, slave, slavelength) #endif /*HAVE__GETPTY*/ #endif /* HAVE_OPENPTY */ } + +long +pty_getpty(int *fd, char *slave, int slavelength) +{ + return ptyint_getpty_ext(fd, slave, slavelength, 1); +} diff --git a/src/util/pty/pty-int.h b/src/util/pty/pty-int.h index 3f9d2cea3..b3c6146a6 100644 --- a/src/util/pty/pty-int.h +++ b/src/util/pty/pty-int.h @@ -102,6 +102,7 @@ extern void getutmpx (const struct utmp *, struct utmpx *); /* Internal functions */ long ptyint_void_association(void); long ptyint_open_ctty (char *slave, int *fd); +long ptyint_getpty_ext(int *, char *, int, int); #ifdef HAVE_SETUTXENT long ptyint_update_wtmpx(struct utmpx *utx); #endif diff --git a/src/util/pty/pty_paranoia.c b/src/util/pty/pty_paranoia.c index e07e0ff33..7311e0834 100644 --- a/src/util/pty/pty_paranoia.c +++ b/src/util/pty/pty_paranoia.c @@ -17,7 +17,125 @@ */ /* - * This bears some explanation. + * A rant on the nature of pseudo-terminals: + * ----------------------------------------- + * + * Controlling terminals and job control: + * + * First, some explanation of job control and controlling terminals is + * necessary for background. This discussion applies to hardwired + * terminals as well as ptys. On most modern systems, all processes + * belong to a process group. A process whose process group id (pgid) + * is the sames as its pid is the process group leader of its process + * group. Process groups belong to sessions. On a modern system, a + * process that is not currently a process group leader may create a + * new session by calling setsid(), which makes it a session leader as + * well as a process group leader, and also removes any existing + * controlling terminal (ctty) association. Only a session leader may + * acquire a ctty. It's not clear how systems that don't have + * setsid() handle ctty acquisition, though probably any process group + * leader that doesn't have a ctty may acquire one that way. + * + * A terminal that is a ctty has an associated foreground process + * group, which is a member of the terminal's associated session. + * This process group gets read/write access to the terminal and will + * receive terminal-generated signals (e.g. SIGINT, SIGTSTP). Process + * groups belonging to the session but not in the foreground may get + * signals that suspend them if they try to read/write from the ctty, + * depending on various terminal settings. + * + * On many systems, the controlling process (the session leader + * associated with a ctty) exiting will cause the session to lose its + * ctty, even though some processes may continue to have open file + * descriptors on the former ctty. It is possible for a process to + * have no file descriptors open on its controlling tty, but to + * reacquire such by opening /dev/tty, as long as its session still + * has a ctty. + * + * On ptys in general: + * + * Ptys have a slave side and a master side. The slave side looks + * like a hardwired serial line to the application that opens it; + * usually, telnetd or rlogind, etc. opens the slave and hands it to + * the login program as stdin/stdout/stderr. The master side usually + * gets the actual network traffic written to/from it. Roughly, the + * master and slave are two ends of a bidirectional pair of FIFOs, + * though this can get complicated by other things. + * + * The master side of a pty is theoretically a single-open device. + * This MUST be true on systems that have BSD-style ptys, since there + * is usually no way to allocate an unused pty except by attempting to + * open all the master pty nodes in the system. + * + * Often, but not always, the last close of a slave device will cause + * the master to get an EOF. Closing the master device will sometimes + * cause the foreground process group of the slave to get a SIGHUP, + * but that may depend on terminal settings. + * + * BSD ptys: + * + * On a BSD-derived system, the master nodes are named like + * /dev/ptyp0, and the slave nodes are named like /dev/ttyp0. The + * last two characters are the variable ones, and a shell-glob type + * pattern for a slave device is usually of the form + * /dev/tty[p-z][0-9a-f], though variants are known to exist. + * + * System V cloning ptys: + * + * There is a cloning master device (usually /dev/ptmx, but the name + * can vary) that gets opened. Each open of the cloning master + * results in an open file descriptor of a unique master device. The + * application calls ptsname() to find the pathname to the slave node. + * + * In theory, the slave side of the pty is locked out until the + * process opening the master calls grantpt() to adjust permissions + * and unlockpt() to unlock the slave. It turns out that Unix98 + * doesn't require that the slave actually get locked out, or that + * unlockpt() actually do anything on such systems. At least AIX + * allows the slave to be opened prior to calling unlockpt(), but most + * other SysV-ish systems seem to actually lock out the slave. + * + * Pty security: + * + * It's not guaranteed on a BSD-ish system that a slave can't be + * opened when the master isn't open. It's even possible to acquire + * the slave as a ctty (!) if the open is done as non-blocking. It's + * possible to open the master corresponding to an open slave, which + * creates some security issues: once this master is open, data + * written to the slave will actually pass to the master. + * + * On a SysV-ish system, the close of the master will invalidate any + * open file descriptors on the slave. + * + * In general, there are two functions that can be used to "clean" a + * pty slave, revoke() and vhangup(). revoke() will invalidate all + * file descriptors open on a particular pathname (often this only + * works on terminal devices), usually by invalidating the underlying + * vnode. vhangup() will send a SIGHUP to the foreground process + * group of the control terminal. On many systems, it also has + * revoke() semantics. + * + * If a process acquires a controlling terminal in order to perform a + * vhangup(), the reopen of the controlling terminal after the + * vhangup() call should be done prior to the close of the file + * descriptor used to initially acquire the controlling terminal, + * since that will likely prevent the process on the master side from + * reading a spurious EOF due to all file descriptors to the slave + * being closed. + * + * Known quirks of various OSes: + * + * AIX 4.3.3: + * + * If the environment variable XPG_SUS_ENV is not equal to "ON", then + * it's possible to open the slave prior to calling unlockpt(). + */ + +/* + * NOTE: this program will get reworked at some point to actually test + * passing of data between master and slave, and to do general cleanup. + * + * This is rather complex, so it bears some explanation. * * There are multiple child processes and a parent process. These * communicate via pipes (which we assume here to be unidirectional). @@ -59,6 +177,11 @@ * pp1. It will then check if it has a ctty and then attempt to * reopen the slave. This should fail. It will then write to the * parent via syncpipe p1p and exit. + * + * If this doesn't fail, child3 will attempt to write to the open + * slave fd. This should fail unless a prior call to revoke(), + * etc. failed due to lack of permissions, e.g. NetBSD when running as + * non-root. */ #include <com_err.h> @@ -78,6 +201,8 @@ void handler(int); void rdsync(int, int *, const char *); void wrsync(int, int, const char *); void testctty(const char *); +void testex(int, const char *); +void testwr(int, const char *); void child1(void); void child2(void); void child3(void); @@ -96,9 +221,13 @@ rdsync(int fd, int *status, const char *caller) int n; char c; +#if 0 + printf("rdsync: %s: starting\n", caller); + fflush(stdout); +#endif while ((n = read(fd, &c, 1)) < 0) { if (errno != EINTR) { - fprintf(stderr, "wrsync: %s", caller); + fprintf(stderr, "rdsync: %s", caller); perror(""); exit(1); } else { @@ -144,7 +273,7 @@ testctty(const char *caller) { int fd; - fd = open("/dev/tty", O_RDWR); + fd = open("/dev/tty", O_RDWR|O_NONBLOCK); if (fd < 0) { printf("%s: no ctty\n", caller); } else { @@ -153,44 +282,147 @@ testctty(const char *caller) } void +testex(int fd, const char *caller) +{ + fd_set rfds, xfds; + struct timeval timeout; + int n; + char c; + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + FD_ZERO(&rfds); + FD_ZERO(&xfds); + FD_SET(fd, &rfds); + FD_SET(fd, &xfds); + + n = select(fd + 1, &rfds, NULL, &xfds, &timeout); + if (n < 0) { + fprintf(stderr, "testex: %s: ", caller); + perror("select"); + } + if (n) { + if (FD_ISSET(fd, &rfds) || FD_ISSET(fd, &xfds)) { + n = read(fd, &c, 1); + if (!n) { + printf("testex: %s: got EOF\n", caller); + fflush(stdout); + return; + } else if (n == -1) { + printf("testex: %s: got errno=%ld (%s)\n", + caller, (long)errno, strerror(errno)); + } else { + printf("testex: %s: read 1 byte!?\n", caller); + } + } + } else { + printf("testex: %s: no exceptions or readable fds\n", caller); + } +} + +void +testwr(int fd, const char *caller) +{ + fd_set wfds; + struct timeval timeout; + int n; + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + FD_ZERO(&wfds); + FD_SET(fd, &wfds); + + n = select(fd + 1, NULL, &wfds, NULL, &timeout); + if (n < 0) { + fprintf(stderr, "testwr: %s: ", caller); + perror("select"); + } + if (n) { + if (FD_ISSET(fd, &wfds)) { + printf("testwr: %s: is writable\n", caller); + fflush(stdout); + } + } +} + + +void child3(void) { + int n; ptyint_void_association(); - slavefd = open(slave, O_RDWR); + slavefd = open(slave, O_RDWR|O_NONBLOCK); if (slavefd < 0) { + wrsync(p1p[1], 1, "[02] child3->parent"); printf("child3: failed reopen of slave\n"); fflush(stdout); - exit(0); + exit(1); } -#ifdef TIOCSTTY - ioctl(slavefd, TIOCSTTY, 0); +#ifdef TIOCSCTTY + ioctl(slavefd, TIOCSCTTY, 0); #endif printf("child3: reopened slave\n"); testctty("child3: after reopen of slave"); + testwr(slavefd, "child3: after reopen of slave"); + testex(slavefd, "child3: after reopen of slave"); close(slavefd); testctty("child3: after close of slave"); /* * Sync for parent to close master. */ - wrsync(p1p[1], 0, "child3->parent"); - rdsync(pp1[0], NULL, "parent->child3"); + wrsync(p1p[1], 0, "[02] child3->parent"); + rdsync(pp1[0], NULL, "[03] parent->child3"); testctty("child3: after close of master"); - slavefd = open(slave, O_RDWR); + printf("child3: attempting reopen of slave\n"); + fflush(stdout); + slavefd = open(slave, O_RDWR|O_NONBLOCK); if (slavefd < 0) { - printf("child3: failed reopen of slave after master close " + printf("child3: failed reopen of slave after master close: " "errno=%ld (%s)\n", (long)errno, strerror(errno)); - wrsync(p1p[1], 0, "child3->parent"); + wrsync(p1p[1], 0, "[04] child3->parent"); fflush(stdout); exit(0); } + if (fcntl(slavefd, F_SETFL, 0) == -1) { + perror("child3: fcntl"); + wrsync(p1p[1], 2, "[04] child3->parent"); + exit(1); + } +#ifdef TIOCSCTTY + ioctl(slavefd, TIOCSCTTY, 0); +#endif printf("child3: reopened slave after master close\n"); - testctty("child3: after reopen of slave after master close\n"); - wrsync(p1p[1], 1, "child3->parent"); + testctty("child3: after reopen of slave after master close"); + testwr(slavefd, "child3: after reopen of slave after master close"); + testex(slavefd, "child3: after reopen of slave after master close"); + n = write(slavefd, "foo", 4); + if (n < 0) { + printf("child3: writing to slave of closed master: errno=%ld (%s)\n", + (long)errno, strerror(errno)); + wrsync(p1p[1], 1, "[04] child3->parent"); + } else { + printf("child3: wrote %d byes to slave of closed master\n", n); + fflush(stdout); + wrsync(p1p[1], 2, "[04] child3->parent"); + } + rdsync(pp1[0], NULL, "[05] parent->child3"); + testex(slavefd, "child3: after parent reopen of master"); + testwr(slavefd, "child3: after parent reopen of master"); fflush(stdout); + n = write(slavefd, "bar", 4); + if (n < 0) { + perror("child3: writing to slave"); + } else { + printf("child3: wrote %d bytes to slave\n", n); + fflush(stdout); + } + wrsync(p1p[1], 0, "[06] child3->parent"); + rdsync(pp1[0], NULL, "[07] parent->child3"); + wrsync(p1p[1], 0, "[08] child3->parent"); exit(0); } @@ -205,23 +437,32 @@ child2(void) sigemptyset(&sa.sa_mask); sa.sa_handler = handler; if (sigaction(SIGHUP, &sa, NULL) < 0) { + wrsync(p21[1], 1, "[00] child2->child1"); perror("child2: sigaction"); fflush(stdout); exit(1); } printf("child2: set up signal handler\n"); testctty("child2: after start"); - wrsync(p21[1], 0, "child2->child1"); - rdsync(pp1[0], NULL, "parent->child2"); + testwr(slavefd, "child2: after start"); + wrsync(p21[1], 0, "[00] child2->child1"); + rdsync(pp1[0], NULL, "[01] parent->child2"); + testctty("child2: after child1 exit"); + testex(slavefd, "child2: after child1 exit"); + testwr(slavefd, "child2: after child1 exit"); close(slavefd); testctty("child2: after close of slavefd"); - slavefd = open(slave, O_RDWR); + slavefd = open(slave, O_RDWR|O_NONBLOCK); if (slavefd < 0) { + wrsync(p1p[1], 1, "[02] child2->parent"); printf("child2: failed reopen of slave\n"); fflush(stdout); - exit(0); + exit(1); } +#ifdef TIOCSCTTY + ioctl(slavefd, TIOCSCTTY, 0); +#endif printf("child2: reopened slave\n"); testctty("child2: after reopen of slave"); fflush(stdout); @@ -230,30 +471,34 @@ child2(void) if (!pid3) { child3(); } else if (pid3 == -1) { + wrsync(p1p[1], 1, "[02] child2->parent"); perror("child2: fork of child3"); exit(1); } printf("child2: forked child3=%ld\n", (long)pid3); fflush(stdout); exit(0); - } void child1(void) { + int status; +#if 0 + setuid(1); +#endif close(pp1[1]); close(p1p[0]); close(masterfd); ptyint_void_association(); - slavefd = open(slave, O_RDWR); + slavefd = open(slave, O_RDWR|O_NONBLOCK); if (slavefd < 0) { perror("child1: open slave"); exit(1); } -#ifdef TIOCSTTY - ioctl(slavefd, TIOCSTTY, 0); +#ifdef TIOCSCTTY + ioctl(slavefd, TIOCSCTTY, 0); #endif printf("child1: opened slave\n"); @@ -273,8 +518,8 @@ child1(void) close(p21[1]); printf("child1: forked child2=%ld\n", (long)pid2); fflush(stdout); - rdsync(p21[0], NULL, "child2->child1"); - exit(0); + rdsync(p21[0], &status, "[00] child2->child1"); + exit(status); } int @@ -282,18 +527,49 @@ main(int argc, char *argv[]) { long retval; int status; + char buf[4]; + int n; prog = argv[0]; - retval = pty_getpty(&masterfd, slave, sizeof(slave)); + printf("parent: pid=%ld\n", (long)getpid()); + + retval = ptyint_getpty_ext(&masterfd, slave, sizeof(slave), 0); if (retval) { com_err(prog, retval, "open master"); exit(1); } +#if 0 + chown(slave, 1, -1); +#endif printf("parent: master opened; slave=%s\n", slave); fflush(stdout); +#if defined(HAVE_GRANTPT) && defined(HAVE_STREAMS) +#ifdef O_NOCTTY + printf("parent: attempting to open slave before unlockpt\n"); + fflush(stdout); + slavefd = open(slave, O_RDWR|O_NONBLOCK|O_NOCTTY); + if (slavefd < 0) { + printf("parent: failed slave open before unlockpt errno=%ld (%s)\n", + (long)errno, strerror(errno)); + } else { + printf("parent: WARNING: " + "succeeded in opening slave before unlockpt\n"); + } + close(slavefd); +#endif + if (grantpt(masterfd) < 0) { + perror("parent: grantpt"); + exit(1); + } + if (unlockpt(masterfd) < 0) { + perror("parent: unlockpt"); + exit(1); + } +#endif /* HAVE_GRANTPT && HAVE_STREAMS */ + if (pipe(pp1) < 0) { perror("pipe parent->child1"); exit(1); @@ -317,16 +593,58 @@ main(int argc, char *argv[]) exit(1); } printf("parent: child1 exited, status=%d\n", status1); - wrsync(pp1[1], 0, "parent->child2"); - rdsync(p1p[0], NULL, "child3->parent"); + if (status1) + exit(status1); + + wrsync(pp1[1], 0, "[01] parent->child2"); + rdsync(p1p[0], &status, "[02] child3->parent"); + if (status) { + fprintf(stderr, "child2 or child3 got an error\n"); + exit(1); + } + printf("parent: closing master\n"); fflush(stdout); close(masterfd); + chmod(slave, 0666); printf("parent: closed master\n"); - wrsync(pp1[1], 0, "parent->child3"); - rdsync(p1p[0], &status, "child3->parent"); - if (status) { - fprintf(stderr, "got status %d\n", status); + wrsync(pp1[1], 0, "[03] parent->child3"); + + rdsync(p1p[0], &status, "[04] child3->parent"); + switch (status) { + case 1: + break; + case 0: + exit(0); + default: + fprintf(stderr, "child3 got an error\n"); + fflush(stdout); + exit(1); } - exit(status); + + retval = pty_getpty(&masterfd, slave2, sizeof(slave2)); + printf("parent: new master opened; slave=%s\n", slave2); +#if 0 +#ifdef HAVE_REVOKE + printf("parent: revoking\n"); + revoke(slave2); +#endif +#endif + fflush(stdout); + wrsync(pp1[1], 0, "[05] parent->child3"); + rdsync(p1p[0], NULL, "[06] child3->parent"); + + n = read(masterfd, buf, 4); + if (n < 0) { + perror("parent: reading from master"); + } else { + printf("parent: read %d bytes (%.*s) from master\n", n, n, buf); + fflush(stdout); + } + chmod(slave2, 0666); + close(masterfd); + wrsync(pp1[1], 0, "[07] parent->child3"); + rdsync(p1p[0], NULL, "[08] child3->parent"); + fflush(stdout); + exit(0); } |
