From cc31c2b2414548fc16f752b16204d3ad845b55c4 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Mon, 25 Jul 2016 10:41:33 -0600 Subject: [PATCH] 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. --- src/sudo_edit.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) 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 */ -- 2.40.0