]> granicus.if.org Git - sudo/commitdiff
Add directory writability checks for sudoedit.
authorTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 23 Oct 2015 20:04:35 +0000 (14:04 -0600)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 23 Oct 2015 20:04:35 +0000 (14:04 -0600)
15 files changed:
NEWS
config.h.in
configure
configure.ac
doc/sudoers.cat
doc/sudoers.man.in
doc/sudoers.mdoc.in
plugins/sudoers/def_data.c
plugins/sudoers/def_data.h
plugins/sudoers/def_data.in
plugins/sudoers/policy.c
plugins/sudoers/sudoers_version.h
src/sudo.c
src/sudo.h
src/sudo_edit.c

diff --git a/NEWS b/NEWS
index 61dbb8536112de89e32cbc5c00b39c9ef1f1f7d5..ccc052cd1438c9502e6b43260bbab9500d880108 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -74,6 +74,9 @@ What's new in Sudo 1.8.15
 
  * Fixed challenge/response style BSD authentication.
 
+ * Added a sudoers option to prevent sudoedit from editing files
+   located in a directory that is writable by the invoking user.
+
 What's new in Sudo 1.8.14p3
 
  * Fixed a bug introduced in sudo 1.8.14p2 that prevented sudo
index 241479e618ca99834d48bbe409b022b00674320d..23ab0bff65552ca028af420c0cbd8d3bbe8adcd5 100644 (file)
 /* Define to 1 if you have the `nss_search' function. */
 #undef HAVE_NSS_SEARCH
 
+/* Define to 1 if you have the `openat' function. */
+#undef HAVE_OPENAT
+
 /* Define to 1 if you have the `openpty' function. */
 #undef HAVE_OPENPTY
 
index 7ad1bf445e6c1d7816c4814dc42f9569d84e496d..1f6ecc5cbe697c1ca6c9e1270aa46de0da887992 100755 (executable)
--- a/configure
+++ b/configure
@@ -2655,6 +2655,7 @@ as_fn_append ac_func_list " nl_langinfo"
 as_fn_append ac_func_list " strftime"
 as_fn_append ac_func_list " pread"
 as_fn_append ac_func_list " pwrite"
+as_fn_append ac_func_list " openat"
 as_fn_append ac_func_list " seteuid"
 # Check that the precious variables saved in the cache have kept the same
 # value.
@@ -18075,6 +18076,8 @@ done
 
 
 
+
+
 for ac_func in getgrouplist
 do :
   ac_fn_c_check_func "$LINENO" "getgrouplist" "ac_cv_func_getgrouplist"
index 23c9158df04d13f652041175974999d10c2e6c55..7cbff15e05873eee123ce3ce3d6ebe955f1dbf9a 100644 (file)
@@ -2384,7 +2384,7 @@ dnl
 dnl Function checks
 dnl
 AC_FUNC_GETGROUPS
-AC_CHECK_FUNCS_ONCE([killpg nl_langinfo strftime pread pwrite])
+AC_CHECK_FUNCS_ONCE([killpg nl_langinfo strftime pread pwrite openat])
 AC_CHECK_FUNCS([getgrouplist], [], [
     case "$host_os" in
     aix*)
index 201d2a4664e70b98ed1bf37e67e8645f66b456a9..ce77649c93ee038f8be014e70db2921e2bc25e85 100644 (file)
@@ -1267,6 +1267,14 @@ S\bSU\bUD\bDO\bOE\bER\bRS\bS O\bOP\bPT\bTI\bIO\bON\bNS\bS
                        that support either the setreuid(2) or setresuid(2)
                        system call.  This flag is _\bo_\bf_\bf by default.
 
+     sudoedit_checkdir
+                       If set, s\bsu\bud\bdo\boe\bed\bdi\bit\bt 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
+                       _\bo_\bf_\bf by default.
+
      sudoedit_follow   By default, s\bsu\bud\bdo\boe\bed\bdi\bit\bt will not follow symbolic links
                        when opening files.  The _\bs_\bu_\bd_\bo_\be_\bd_\bi_\bt_\b__\bf_\bo_\bl_\bl_\bo_\bw option can be
                        enabled to allow s\bsu\bud\bdo\boe\bed\bdi\bit\bt to open symbolic links.  It
@@ -2464,4 +2472,4 @@ D\bDI\bIS\bSC\bCL\bLA\bAI\bIM\bME\bER\bR
      file distributed with s\bsu\bud\bdo\bo or http://www.sudo.ws/license.html for
      complete details.
 
-Sudo 1.8.15                   September 25, 2015                   Sudo 1.8.15
+Sudo 1.8.15                    October 23, 2015                    Sudo 1.8.15
index 251d9a2c68250a566fe98f23fc4f464a82b0804d..88908cd81759bff9070323e7fd1a9461ba518f8c 100644 (file)
@@ -21,7 +21,7 @@
 .\" Agency (DARPA) and Air Force Research Laboratory, Air Force
 .\" Materiel Command, USAF, under agreement number F39502-99-1-0512.
 .\"
-.TH "SUDOERS" "5" "September 25, 2015" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
+.TH "SUDOERS" "5" "October 23, 2015" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
 .nh
 .if n .ad l
 .SH "NAME"
@@ -2700,6 +2700,18 @@ This flag is
 \fIoff\fR
 by default.
 .TP 18n
+sudoedit_checkdir
+.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.
+This flag is
+\fIoff\fR
+by default.
+.TP 18n
 sudoedit_follow
 By default,
 \fBsudoedit\fR
index fbfe577345288013775b60c4765f6c9383e777c1..bf88dcc4119c467c3c3178bd7333dde6e401613c 100644 (file)
@@ -19,7 +19,7 @@
 .\" Agency (DARPA) and Air Force Research Laboratory, Air Force
 .\" Materiel Command, USAF, under agreement number F39502-99-1-0512.
 .\"
-.Dd September 25, 2015
+.Dd October 23, 2015
 .Dt SUDOERS @mansectform@
 .Os Sudo @PACKAGE_VERSION@
 .Sh NAME
@@ -2536,6 +2536,16 @@ system call.
 This flag is
 .Em off
 by default.
+.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.
+This flag is
+.Em off
+by default.
 .It sudoedit_follow
 By default,
 .Nm sudoedit
index 40c509a59b84f11d6632e4c8050fcbd32ca3bf29..6d75d3907726f30583a137bb0118a0e2682bab47 100644 (file)
@@ -386,6 +386,10 @@ struct sudo_defs_types sudo_defs_table[] = {
        "use_netgroups", T_FLAG,
        N_("Enable sudoers netgroup support"),
        NULL,
+    }, {
+       "sudoedit_checkdir", T_FLAG,
+       N_("Check the parent directory for writability when editing files with sudoedit"),
+       NULL,
     }, {
        "sudoedit_follow", T_FLAG,
        N_("Follow symbolic links when editing files with sudoedit"),
index 7fbe1d80408140b3ce866997ed2a20ae7d78b792..6faa8f075500d4d654e7744c200971aa226cc67a 100644 (file)
 #define I_MAXSEQ                89
 #define def_use_netgroups       (sudo_defs_table[90].sd_un.flag)
 #define I_USE_NETGROUPS         90
-#define def_sudoedit_follow     (sudo_defs_table[91].sd_un.flag)
-#define I_SUDOEDIT_FOLLOW       91
+#define def_sudoedit_checkdir   (sudo_defs_table[91].sd_un.flag)
+#define I_SUDOEDIT_CHECKDIR     91
+#define def_sudoedit_follow     (sudo_defs_table[92].sd_un.flag)
+#define I_SUDOEDIT_FOLLOW       92
 
 enum def_tuple {
        never,
index 55a2126ad7cde001db0cd55729d1209016bb42f9..72d35df50e785a1a899b582a0502e6301474cf2a 100644 (file)
@@ -286,6 +286,9 @@ maxseq
 use_netgroups
        T_FLAG
        "Enable sudoers netgroup support"
+sudoedit_checkdir
+       T_FLAG
+       "Check the parent directory for writability when editing files with sudoedit"
 sudoedit_follow
        T_FLAG
        "Follow symbolic links when editing files with sudoedit"
index 4b8e6539ae914d66a8b2258fd0dcf0627ec288ab..06a1b571379b288e9ee42360cece2efc558ddcf2 100644 (file)
@@ -438,6 +438,10 @@ sudoers_policy_exec_setup(char *argv[], char *envp[], mode_t cmnd_umask,
     if (ISSET(sudo_mode, MODE_EDIT)) {
        if ((command_info[info_len++] = strdup("sudoedit=true")) == NULL)
            goto oom;
+       if (def_sudoedit_checkdir) {
+           if ((command_info[info_len++] = strdup("sudoedit_checkdir=true")) == NULL)
+               goto oom;
+       }
        if (def_sudoedit_follow) {
            if ((command_info[info_len++] = strdup("sudoedit_follow=true")) == NULL)
                goto oom;
index ce953941642d91e17e3e14f8ccf8eb60b6f71949..37e708f02ecbdab5a481aad9c4232bfe1f6b9277 100644 (file)
@@ -63,7 +63,7 @@
  * 42  sudo 1.8.6, Support for empty Runas_List (with or without a colon) to mean the invoking user.  Support for Solaris Privilege Sets (PRIVS= and LIMITPRIVS=).
  * 43  sudo 1.8.7, Support for specifying a digest along with the command.
  * 44  sudo 1.8.13, added MAIL/NOMAIL tags.
- * 45  sudo 1.8.15, added FOLLOW/NOFOLLOW tags and sudoedit_follow Default.
+ * 45  sudo 1.8.15, added FOLLOW/NOFOLLOW tags as well as sudoedit_follow and sudoedit_checkdir Defaults.
 */
 
 #ifndef SUDOERS_VERSION_H
index c8ae36517ffd5c42750c5556c2acf930b11b0694..578642beab4620aa4bccb4d29e4059920e0c687f 100644 (file)
@@ -727,6 +727,11 @@ command_info_to_details(char * const info[], struct command_details *details)
                        SET(details->flags, CD_SUDOEDIT);
                    break;
                }
+               if (strncmp("sudoedit_checkdir=", info[i], sizeof("sudoedit_checkdir=") - 1) == 0) {
+                   if (sudo_strtobool(info[i] + sizeof("sudoedit_checkdir=") - 1) == true)
+                       SET(details->flags, CD_SUDOEDIT_CHECKDIR);
+                   break;
+               }
                if (strncmp("sudoedit_follow=", info[i], sizeof("sudoedit_follow=") - 1) == 0) {
                    if (sudo_strtobool(info[i] + sizeof("sudoedit_follow=") - 1) == true)
                        SET(details->flags, CD_SUDOEDIT_FOLLOW);
index c1692d7be03a38317ae9602acf1216506c766f24..1c00b1bfc9c739f71562df67d904ebd3fcecec47 100644 (file)
@@ -128,6 +128,7 @@ struct user_details {
 #define CD_EXEC_BG             0x04000
 #define CD_SUDOEDIT_COPY       0x08000
 #define CD_SUDOEDIT_FOLLOW     0x10000
+#define CD_SUDOEDIT_CHECKDIR   0x20000
 
 struct preserved_fd {
     TAILQ_ENTRY(preserved_fd) entries;
index f6cfa338b1d7ce6d781b5d0f46c26336cd65aec0..ca8442103ec851e368b3f97e4ba3e221a0d01fcb 100644 (file)
@@ -31,6 +31,7 @@
 #endif /* HAVE_STRINGS_H */
 #include <unistd.h>
 #include <ctype.h>
+#include <errno.h>
 #include <grp.h>
 #include <pwd.h>
 #include <signal.h>
@@ -154,28 +155,180 @@ sudo_edit_mktemp(const char *ofile, char **tfile)
     debug_return_int(tfd);
 }
 
+static bool
+group_matches(gid_t target, gid_t gid, int ngroups, GETGROUPS_T *groups)
+{
+    int i;
+    debug_decl(group_matches, SUDO_DEBUG_EDIT)
+
+    if (target == gid) {
+       sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+           "user gid %u matches directory gid %u", (unsigned int)gid,
+           (unsigned int)target);
+       debug_return_bool(true);
+    }
+    for (i = 0; i < ngroups; i++) {
+       if (target == groups[i]) {
+           sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+               "user gid %u matches directory gid %u", (unsigned int)gid,
+               (unsigned int)target);
+           debug_return_bool(true);
+       }
+    }
+    debug_return_bool(false);
+}
+
+#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)
+
+    /* Save cwd */
+    if ((odfd = open(".", O_RDONLY)) == -1)
+       debug_return_int(-1);
+
+    if (fchdir(dfd) == -1) {
+       close(odfd);
+       debug_return_int(-1);
+    }
+
+    fd = open(path, flags, mode);
+
+    /* Restore cwd */
+    if (fchdir(odfd) == -1)
+       sudo_fatal(_("unable to restore current working directory"));
+    close(odfd);
+
+    debug_return_int(fd);
+}
+#define openat sudo_openat
+#endif /* HAVE_OPENAT */
+
+/*
+ * Returns true if the directory described by sb is writable
+ * by the user.  We treat directories with the sticky bit as
+ * unwritable unless they are owned by the user.
+ */
+static bool
+dir_is_writable(struct stat *sb, uid_t uid, gid_t gid, int ngroups,
+    GETGROUPS_T *groups)
+{
+    debug_decl(dir_is_writable, SUDO_DEBUG_EDIT)
+
+    /* If the user owns the dir we always consider it writable. */
+    if (sb->st_uid == uid) {
+       sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+           "user uid %u matches directory uid %u", (unsigned int)uid,
+           (unsigned int)sb->st_uid);
+       debug_return_bool(true);
+    }
+
+    /* Other writable? */
+    if (ISSET(sb->st_mode, S_IWOTH)) {
+       sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+           "directory is writable by other");
+       debug_return_bool(true);
+    }
+
+    /* Group writable? */
+    if (ISSET(sb->st_mode, S_IWGRP)) {
+       if (group_matches(sb->st_gid, gid, ngroups, groups)) {
+           sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+               "directory is writable by one of the user's groups");
+           debug_return_bool(true);
+       }
+    }
+
+    debug_return_bool(false);
+}
+
+static int
+sudo_edit_open_nonwritable(char *path, int oflags, mode_t mode)
+{
+    char *base, *dir;
+    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;
+    } else {
+       base = path;
+       dir = ".";
+    }
+#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)) {
+       close(dfd);
+       errno = ENOTDIR;
+       debug_return_int(-1);
+    }
+
+    fd = openat(dfd, path, oflags, mode);
+    close(dfd);
+    debug_return_int(fd);
+}
+
 #ifdef O_NOFOLLOW
 static int
-sudo_edit_open(const char *path, int oflags, mode_t mode, int sflags)
+sudo_edit_open(char *path, int oflags, mode_t mode, int sflags)
 {
     int fd;
+    debug_decl(sudo_edit_open, SUDO_DEBUG_EDIT)
 
     if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW))
        oflags |= O_NOFOLLOW;
-    fd = open(path, oflags|O_NONBLOCK, mode);
+    if (ISSET(sflags, CD_SUDOEDIT_CHECKDIR) && user_details.uid != 0)
+       fd = sudo_edit_open_nonwritable(path, oflags|O_NONBLOCK, mode);
+    else
+       fd = open(path, oflags|O_NONBLOCK, mode);
     if (fd != -1 && !ISSET(oflags, O_NONBLOCK))
        (void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
-    return fd;
+    debug_return_int(fd);
 }
 #else
 static int
-sudo_edit_open(const char *path, int oflags, mode_t mode, int sflags)
+sudo_edit_open(char *path, int oflags, mode_t mode, int sflags)
 {
     struct stat sb1, sb2;
     int fd;
+    debug_decl(sudo_edit_open, SUDO_DEBUG_EDIT)
 
-    if ((fd = open(path, oflags|O_NONBLOCK, mode)) == -1)
-       return -1;
+    if (ISSET(sflags, CD_SUDOEDIT_CHECKDIR) && user_details.uid != 0)
+       fd = sudo_edit_open_nonwritable(path, oflags|O_NONBLOCK, mode);
+    else
+       fd = open(path, oflags|O_NONBLOCK, mode);
+    if (fd == -1)
+       debug_return_int(-1);
     if (!ISSET(oflags, O_NONBLOCK))
        (void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
 
@@ -186,7 +339,7 @@ sudo_edit_open(const char *path, int oflags, mode_t mode, int sflags)
        const int serrno = errno;
        close(fd);
        errno = serrno;
-       return -1;
+       debug_return_int(-1);
     }
 
     /*
@@ -198,11 +351,11 @@ sudo_edit_open(const char *path, int oflags, mode_t mode, int sflags)
            sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
            close(fd);
            errno = ELOOP;
-           return -1;
+           debug_return_int(-1);
        }
     }
 
-    return fd;
+    debug_return_int(fd);
 }
 #endif /* O_NOFOLLOW */
 
@@ -214,7 +367,7 @@ sudo_edit_open(const char *path, int oflags, mode_t mode, int sflags)
  */
 static int
 sudo_edit_create_tfiles(struct command_details *command_details,
-    struct tempfile *tf, char * const files[], int nfiles)
+    struct tempfile *tf, char *files[], int nfiles)
 {
     int i, j, tfd, ofd, rc;
     char buf[BUFSIZ];
@@ -252,6 +405,9 @@ sudo_edit_create_tfiles(struct command_details *command_details,
            if (ofd == -1 && errno == ELOOP) {
                sudo_warnx(U_("%s: editing symbolic links is not permitted"),
                    files[i]);
+           } else if (ofd == -1 && errno == ENOTDIR) {
+               sudo_warnx(U_("%s: editing files in a writable directory is not permitted"),
+                   files[i]);
            } else {
                sudo_warn("%s", files[i]);
            }
@@ -406,7 +562,7 @@ sudo_edit_copy_tfiles(struct command_details *command_details,
 #ifdef HAVE_SELINUX
 static int
 selinux_edit_create_tfiles(struct command_details *command_details,
-    struct tempfile *tf, char * const files[], int nfiles)
+    struct tempfile *tf, char *files[], int nfiles)
 {
     char **sesh_args, **sesh_ap;
     int i, rc, sesh_nargs;