summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Yu <tlyu@mit.edu>2001-05-16 02:55:51 +0000
committerTom Yu <tlyu@mit.edu>2001-05-16 02:55:51 +0000
commit5e22fd9f35aa131be0c146cff9d577d9e0cf84ad (patch)
tree8a296b5113fd2270817d696ef959a8728927f976
parentd56d6ad6f4e5a5a598224416f7e9a06e2a511d37 (diff)
downloadkrb5-5e22fd9f35aa131be0c146cff9d577d9e0cf84ad.tar.gz
krb5-5e22fd9f35aa131be0c146cff9d577d9e0cf84ad.tar.xz
krb5-5e22fd9f35aa131be0c146cff9d577d9e0cf84ad.zip
* 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. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@13243 dc483132-0cff-0310-8789-dd5450dbe970
-rw-r--r--src/util/pty/ChangeLog18
-rw-r--r--src/util/pty/getpty.c14
-rw-r--r--src/util/pty/pty-int.h1
-rw-r--r--src/util/pty/pty_paranoia.c382
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);
}