From: Todd C. Miller <Todd.Miller@courtesan.com>
Date: Mon, 25 Jul 2016 16:41:33 +0000 (-0600)
Subject: Repair symlink check in sudo_edit_openat_nofollow() on systems
X-Git-Tag: SUDO_1_8_18^2~99
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=cc31c2b2414548fc16f752b16204d3ad845b55c4;p=sudo

Repair symlink check in sudo_edit_openat_nofollow() on systems
without O_NOFOLLOW, it must be done relative to dfd.  Previously
the lstat() would always fail, possibly leading to a false positive.
Also add an early symlink check like in sudo_edit() while here.
---

diff --git a/src/sudo_edit.c b/src/sudo_edit.c
index 6e330cce6..3b89193c2 100644
--- a/src/sudo_edit.c
+++ b/src/sudo_edit.c
@@ -226,19 +226,51 @@ sudo_edit_is_symlink(int fd, char *path)
 static int
 sudo_edit_openat_nofollow(int dfd, char *path, int oflags, mode_t mode)
 {
-    int fd;
+    int fd = -1, odfd = -1;
+    struct stat sb;
     debug_decl(sudo_edit_openat_nofollow, SUDO_DEBUG_EDIT)
 
-    fd = openat(dfd, path, oflags, mode);
-    if (fd == -1)
+    /* Save cwd and chdir to dfd */
+    if ((odfd = open(".", O_RDONLY)) == -1)
+	debug_return_int(-1);
+    if (fchdir(dfd) == -1) {
+	close(odfd);
 	debug_return_int(-1);
+    }
+
+    /*
+     * Check if path is a symlink.  This is racey but we detect whether
+     * we lost the race in sudo_edit_is_symlink() after the open.
+     */
+    if (lstat(path, &sb) == -1 && errno != ENOENT)
+	goto done;
+    if (S_ISLNK(sb.st_mode)) {
+	errno = ELOOP;
+	goto done;
+    }
+
+    fd = open(path, oflags, mode);
+    if (fd == -1)
+	goto done;
 
+    /*
+     * Post-open symlink check.  This will leave a zero-length file if
+     * O_CREAT was specified but it is too dangerous to try and remove it.
+     */
     if (sudo_edit_is_symlink(fd, path)) {
 	close(fd);
 	fd = -1;
 	errno = ELOOP;
     }
 
+done:
+    /* Restore cwd */
+    if (odfd != -1) {
+	if (fchdir(odfd) == -1)
+	    sudo_fatal(_("unable to restore current working directory"));
+	close(odfd);
+    }
+
     debug_return_int(fd);
 }
 #endif /* O_NOFOLLOW */