]> granicus.if.org Git - sudo/commitdiff
Repair symlink check in sudo_edit_openat_nofollow() on systems
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 25 Jul 2016 16:41:33 +0000 (10:41 -0600)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 25 Jul 2016 16:41:33 +0000 (10:41 -0600)
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.

src/sudo_edit.c

index 6e330cce665fa7780f40a3e6fd471dca74f60ebd..3b89193c24b5467844b2643f2bcb8516d255d569 100644 (file)
@@ -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 */