guix/gnu/packages/patches/sudo-CVE-2015-5602.patch
Mark H Weaver 16aa649117 gnu: sudo: Add fix for CVE-2015-5602.
* gnu/packages/patches/sudo-CVE-2015-5602.patch: New file.
* gnu-system.am (dist_patch_DATA): Add it.
* gnu/packages/admin.scm (sudo)[source]: Add patch.
2016-01-12 11:23:06 -05:00

373 lines
11 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Based on the patch from https://www.sudo.ws/repos/sudo/raw-rev/c2e36a80a279
Backported to 1.8.15 by Mark H Weaver <mhw@netris.org>
# HG changeset patch
# User Todd C. Miller <Todd.Miller@courtesan.com>
# Date 1452475889 25200
# Node ID c2e36a80a27927c32cba55afae78b8dc830cddc3
# Parent 94ffd6b18431fa4b9ed0a0c3f0b7b9582a4f6bde
Rewritten sudoedit_checkdir support that checks all the dirs in the
path and refuses to follow symlinks in writable directories.
This is a better fix for CVE-2015-5602.
Adapted from a diff by Ben Hutchings. Bug #707
diff -r 94ffd6b18431 -r c2e36a80a279 doc/CONTRIBUTORS
--- a/doc/CONTRIBUTORS Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/CONTRIBUTORS Sun Jan 10 18:31:29 2016 -0700
@@ -58,6 +58,7 @@
Holloway, Nick
Hoover, Adam
Hunter, Michael T.
+ Hutchings, Ben
Irrgang, Eric
Jackson, Brian
Jackson, John R.
diff -r 94ffd6b18431 -r c2e36a80a279 doc/UPGRADE
--- a/doc/UPGRADE Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/UPGRADE Sun Jan 10 18:31:29 2016 -0700
@@ -1,6 +1,15 @@
Notes on upgrading from an older release
========================================
+o Upgrading from a version prior to the post-1.8.15 fix for CVE-2015-5602.
+
+ The meaning of the sudoedit_checkdir sudoers option has changed.
+ Previously, it would only check the parent directory
+ of the file to be edited. After the CVE fix, all directories
+ in the path to be edited are checked and sudoedit will refuse
+ to follow a symbolic link in a directory that is writable by
+ the invoking user.
+
o Upgrading from a version prior to 1.8.15:
Prior to version 1.8.15, when env_reset was enabled (the default)
diff -r 94ffd6b18431 -r c2e36a80a279 doc/sudoers.cat
--- a/doc/sudoers.cat Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/sudoers.cat Sun Jan 10 18:31:29 2016 -0700
@@ -1275,12 +1275,15 @@
system call. This flag is _o_f_f by default.
sudoedit_checkdir
- If set, ssuuddooeeddiitt will refuse to edit files located in a
- directory that is writable by the invoking user unless
- it is run by root. On many systems, this option
- requires that the parent directory of the file to be
- edited be readable by the target user. This flag is
- _o_f_f by default.
+ If set, ssuuddooeeddiitt will check directories in the path to
+ be edited for writability by the invoking user.
+ Symbolic links will not be followed in writable
+ directories and ssuuddooeeddiitt will also refuse to edit a
+ file located in a writable directory. Theses
+ restrictions are not enforced when ssuuddooeeddiitt is invoked
+ as root. On many systems, this option requires that
+ all directories in the path to be edited be readable by
+ the target user. This flag is _o_f_f by default.
sudoedit_follow By default, ssuuddooeeddiitt will not follow symbolic links
when opening files. The _s_u_d_o_e_d_i_t___f_o_l_l_o_w option can be
diff -r 94ffd6b18431 -r c2e36a80a279 doc/sudoers.man.in
--- a/doc/sudoers.man.in Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/sudoers.man.in Sun Jan 10 18:31:29 2016 -0700
@@ -2715,10 +2715,16 @@
.br
If set,
\fBsudoedit\fR
-will refuse to edit files located in a directory that is writable
-by the invoking user unless it is run by root.
-On many systems, this option requires that the parent directory
-of the file to be edited be readable by the target user.
+will check directories in the path to be edited for writability
+by the invoking user.
+Symbolic links will not be followed in writable directories and
+\fBsudoedit\fR
+will also refuse to edit a file located in a writable directory.
+Theses restrictions are not enforced when
+\fBsudoedit\fR
+is invoked as root.
+On many systems, this option requires that all directories
+in the path to be edited be readable by the target user.
This flag is
\fIoff\fR
by default.
diff -r 94ffd6b18431 -r c2e36a80a279 doc/sudoers.mdoc.in
--- a/doc/sudoers.mdoc.in Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/sudoers.mdoc.in Sun Jan 10 18:31:29 2016 -0700
@@ -2549,10 +2549,16 @@
.It sudoedit_checkdir
If set,
.Nm sudoedit
-will refuse to edit files located in a directory that is writable
-by the invoking user unless it is run by root.
-On many systems, this option requires that the parent directory
-of the file to be edited be readable by the target user.
+will check directories in the path to be edited for writability
+by the invoking user.
+Symbolic links will not be followed in writable directories and
+.Nm sudoedit
+will also refuse to edit a file located in a writable directory.
+Theses restrictions are not enforced when
+.Nm sudoedit
+is invoked as root.
+On many systems, this option requires that all directories
+in the path to be edited be readable by the target user.
This flag is
.Em off
by default.
diff -r 94ffd6b18431 -r c2e36a80a279 include/sudo_compat.h
--- a/include/sudo_compat.h Mon Jan 04 10:47:11 2016 -0700
+++ b/include/sudo_compat.h Sun Jan 10 18:31:29 2016 -0700
@@ -182,6 +182,8 @@
# ifndef UTIME_NOW
# define UTIME_NOW -2L
# endif
+#endif
+#if !defined(HAVE_OPENAT) || (!defined(HAVE_FUTIMENS) && !defined(HAVE_UTIMENSAT))
# ifndef AT_FDCWD
# define AT_FDCWD -100
# endif
diff -r 94ffd6b18431 -r c2e36a80a279 src/sudo_edit.c
--- a/src/sudo_edit.c Mon Jan 04 10:47:11 2016 -0700
+++ b/src/sudo_edit.c Sun Jan 10 18:31:29 2016 -0700
@@ -179,13 +179,15 @@
}
#ifndef HAVE_OPENAT
-/* This does not support AT_FDCWD... */
static int
sudo_openat(int dfd, const char *path, int flags, mode_t mode)
{
int fd, odfd;
debug_decl(sudo_openat, SUDO_DEBUG_EDIT)
+ if (dfd == AT_FDCWD)
+ debug_return_int(open(path, flags, mode));
+
/* Save cwd */
if ((odfd = open(".", O_RDONLY)) == -1)
debug_return_int(-1);
@@ -207,6 +209,64 @@
#define openat sudo_openat
#endif /* HAVE_OPENAT */
+#ifdef O_NOFOLLOW
+static int
+sudo_edit_openat_nofollow(int dfd, char *path, int oflags, mode_t mode)
+{
+ debug_decl(sudo_edit_open_nofollow, SUDO_DEBUG_EDIT)
+
+ debug_return_int(openat(dfd, path, oflags|O_NOFOLLOW, mode));
+}
+#else
+/*
+ * Returns true if fd and path don't match or path is a symlink.
+ * Used on older systems without O_NOFOLLOW.
+ */
+static bool
+sudo_edit_is_symlink(int fd, char *path)
+{
+ struct stat sb1, sb2;
+ debug_decl(sudo_edit_is_symlink, SUDO_DEBUG_EDIT)
+
+ /*
+ * Treat [fl]stat() failure like there was a symlink.
+ */
+ if (fstat(fd, &sb1) == -1 || lstat(path, &sb2) == -1)
+ debug_return_bool(true);
+
+ /*
+ * Make sure we did not open a link and that what we opened
+ * matches what is currently on the file system.
+ */
+ if (S_ISLNK(sb2.st_mode) ||
+ sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
+ debug_return_bool(true);
+ }
+
+ debug_return_bool(false);
+}
+
+static int
+sudo_edit_openat_nofollow(char *path, int oflags, mode_t mode)
+{
+ struct stat sb1, sb2;
+ int fd;
+ debug_decl(sudo_edit_openat_nofollow, SUDO_DEBUG_EDIT)
+
+ fd = openat(dfd, path, oflags, mode);
+ if (fd == -1)
+ debug_return_int(-1);
+
+ if (sudo_edit_is_symlink(fd, path)) {
+ close(fd);
+ fd = -1;
+ errno = ELOOP;
+ }
+
+ debug_return_int(fd);
+}
+#endif /* O_NOFOLLOW */
+
/*
* Returns true if the directory described by sb is writable
* by the user. We treat directories with the sticky bit as
@@ -245,49 +305,94 @@
debug_return_bool(false);
}
+/*
+ * Directory open flags for use with openat(2) and fstat(2).
+ * Use O_PATH and O_DIRECTORY where possible.
+ */
+#if defined(O_PATH) && defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS (O_PATH|O_DIRECTORY)
+#elif defined(O_PATH) && !defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS O_PATH
+#elif !defined(O_PATH) && defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS (O_RDONLY|O_DIRECTORY)
+#else
+# define DIR_OPEN_FLAGS (O_RDONLY|O_NONBLOCK)
+#endif
+
static int
sudo_edit_open_nonwritable(char *path, int oflags, mode_t mode)
{
- char *base, *dir;
+ int dfd, fd, dflags = DIR_OPEN_FLAGS;
+#if defined(__linux__) && defined(O_PATH)
+ char *opath = path;
+#endif
+ bool is_writable;
struct stat sb;
- int dfd, fd;
debug_decl(sudo_edit_open_nonwritable, SUDO_DEBUG_EDIT)
- base = strrchr(path, '/');
- if (base != NULL) {
- *base++ = '\0';
- dir = path;
+#if defined(__linux__) && defined(O_PATH)
+restart:
+#endif
+ if (path[0] == '/') {
+ dfd = open("/", dflags);
+ path++;
} else {
- base = path;
- dir = ".";
+ dfd = open(".", dflags);
+ if (path[0] == '.' && path[1] == '/')
+ path += 2;
}
-#ifdef O_PATH
- if ((dfd = open(dir, O_PATH)) != -1) {
- /* Linux kernels < 3.6 can't do fstat on O_PATH fds. */
- if (fstat(dfd, &sb) == -1) {
- close(dfd);
- dfd = open(dir, O_RDONLY);
- if (fstat(dfd, &sb) == -1) {
- close(dfd);
- dfd = -1;
- }
- }
- }
-#else
- if ((dfd = open(dir, O_RDONLY)) != -1) {
- if (fstat(dfd, &sb) == -1) {
- close(dfd);
- dfd = -1;
- }
- }
-#endif
- if (base != path)
- base[-1] = '/'; /* restore path */
if (dfd == -1)
debug_return_int(-1);
- if (dir_is_writable(&sb, user_details.uid, user_details.gid,
- user_details.ngroups, user_details.groups)) {
+ for (;;) {
+ char *slash;
+ int subdfd;
+
+ /*
+ * Look up one component at a time, avoiding symbolic links in
+ * writable directories.
+ */
+ if (fstat(dfd, &sb) == -1) {
+ close(dfd);
+#if defined(__linux__) && defined(O_PATH)
+ /* Linux prior to 3.6 can't fstat an O_PATH fd */
+ if (ISSET(dflags, O_PATH)) {
+ CLR(dflags, O_PATH);
+ path = opath;
+ goto restart;
+ }
+#endif
+ debug_return_int(-1);
+ }
+#ifndef O_DIRECTORY
+ if (!S_ISDIR(sb.st_mode)) {
+ close(dfd);
+ errno = ENOTDIR;
+ debug_return_int(-1);
+ }
+#endif
+ is_writable = dir_is_writable(&sb, user_details.uid, user_details.gid,
+ user_details.ngroups, user_details.groups);
+
+ while (path[0] == '/')
+ path++;
+ slash = strchr(path, '/');
+ if (slash == NULL)
+ break;
+ *slash = '\0';
+ if (is_writable)
+ subdfd = sudo_edit_openat_nofollow(dfd, path, dflags, 0);
+ else
+ subdfd = openat(dfd, path, dflags, 0);
+ *slash = '/'; /* restore path */
+ close(dfd);
+ if (subdfd == -1)
+ debug_return_int(-1);
+ path = slash + 1;
+ dfd = subdfd;
+ }
+
+ if (is_writable) {
close(dfd);
errno = EISDIR;
debug_return_int(-1);
@@ -332,27 +437,10 @@
if (!ISSET(oflags, O_NONBLOCK))
(void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
- /*
- * Treat [fl]stat() failure like an open() failure.
- */
- if (fstat(fd, &sb1) == -1 || lstat(path, &sb2) == -1) {
- const int serrno = errno;
+ if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW) && sudo_edit_is_symlink(fd, path)) {
close(fd);
- errno = serrno;
- debug_return_int(-1);
- }
-
- /*
- * Make sure we did not open a link and that what we opened
- * matches what is currently on the file system.
- */
- if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW)) {
- if (S_ISLNK(sb2.st_mode) ||
- sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
- close(fd);
- errno = ELOOP;
- debug_return_int(-1);
- }
+ fd = -1;
+ errno = ELOOP;
}
debug_return_int(fd);