Blob Blame History Raw
diff --git a/src/ttyname.c b/src/ttyname.c
index 32f093c..d8858f7 100644
--- a/src/ttyname.c
+++ b/src/ttyname.c
@@ -414,53 +414,80 @@ get_process_ttyname(void)
 }
 #elif defined(__linux__)
 /*
- * Return a string from ttyname() containing the tty to which the process is
- * attached or NULL if there is no tty associated with the process (or its
- * parent).  First tries field 7 in /proc/pid/stat, then /proc/ppid/stat.
- * Falls back on ttyname of std{in,out,err} if that fails.
+ * Store the name of the tty to which the process is attached in name.
+ * Returns name on success and NULL on failure, setting errno.
  */
 char *
 get_process_ttyname(void)
 {
-    char *line = NULL, *tty = NULL;
-    size_t linesize = 0;
-    ssize_t len;
-    int i;
+    const char path[] = "/proc/self/stat";
+    char *cp, buf[1024];
+    char *ret = NULL;
+    int serrno = errno;
+    ssize_t nread;
+    int fd;
     debug_decl(get_process_ttyname, SUDO_DEBUG_UTIL)
 
-    /* Try to determine the tty from tty_nr in /proc/pid/stat. */
-    for (i = 0; tty == NULL && i < 2; i++) {
-	FILE *fp;
-	char path[PATH_MAX];
-	(void)snprintf(path, sizeof(path), "/proc/%u/stat",
-	    i ? (unsigned int)getppid() : (unsigned int)getpid());
-	if ((fp = fopen(path, "r")) == NULL)
-	    continue;
-	len = getline(&line, &linesize, fp);
-	fclose(fp);
-	if (len != -1) {
+    /*
+     * Try to determine the tty from tty_nr in /proc/self/stat.
+     * Ignore /proc/self/stat if it contains embedded NUL bytes.
+     */
+    if ((fd = open(path, O_RDONLY | O_NOFOLLOW)) != -1) {
+	cp = buf;
+	while ((nread = read(fd, cp, buf + sizeof(buf) - cp)) != 0) {
+	    if (nread == -1) {
+		if (errno == EAGAIN || errno == EINTR)
+		    continue;
+		break;
+	    }
+	    cp += nread;
+	    if (cp >= buf + sizeof(buf))
+		break;
+	}
+	if (nread == 0 && memchr(buf, '\0', cp - buf) == NULL) {
 	    /*
 	     * Field 7 is the tty dev (0 if no tty).
-	     * Since the process name at field 2 "(comm)" may include spaces,
-	     * start at the last ')' found.
+	     * Since the process name at field 2 "(comm)" may include
+	     * whitespace (including newlines), start at the last ')' found.
 	     */
-	    char *cp = strrchr(line, ')');
-	    int field = 2;
-	    while (*cp != '\0') {
-		if (*cp++ == ' ') {
-		    if (++field == 7) {
-			dev_t tdev = (dev_t)atoi(cp);
-			if (tdev > 0)
-			    tty = sudo_ttyname_dev(tdev);
-			break;
+	    *cp = '\0';
+	    cp = strrchr(buf, ')');
+	    if (cp != NULL) {
+		char *ep = cp;
+		int field = 1;
+
+		while (*++ep != '\0') {
+		    if (*ep == ' ') {
+			*ep = '\0';
+			if (++field == 7) {
+			    dev_t tdev = (dev_t)strtol(cp, NULL, 10);
+			    if (tdev == 0 || errno == ERANGE || errno == EINVAL) {
+				sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+				    "%s: tty device %s: %s", path, cp, strerror(errno));
+			    }
+			    if (tdev > 0) {
+				errno = serrno;
+				ret = sudo_ttyname_dev(tdev);
+				goto done;
+			    }
+			    break;
+			}
+			cp = ep + 1;
 		    }
 		}
 	    }
 	}
     }
-    efree(line);
+    errno = ENOENT;
 
-    debug_return_str(tty);
+done:
+    if (fd != -1)
+	close(fd);
+    if (ret == NULL)
+	sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+	    "unable to resolve tty via %s", path);
+
+    debug_return_str(ret);
 }
 #else
 /*