Blame 0076-lib-add-alternative-dd-functions-accepting-fds.patch

3a204fb
From 200edbbf40d980ad987fafa753f1c14c8778d8e1 Mon Sep 17 00:00:00 2001
3a204fb
From: Jakub Filak <jfilak@redhat.com>
3a204fb
Date: Fri, 24 Apr 2015 11:42:18 +0200
3a204fb
Subject: [PATCH] lib: add alternative dd functions accepting fds
3a204fb
3a204fb
Florian Weimer <fweimer@redhat.com>:
3a204fb
3a204fb
    dump_dir_accessible_by_uid() is fundamentally insecure because it
3a204fb
    opens up a classic time-of-check-time-of-use race between this
3a204fb
    function and and dd_opendir().  At least re-checking after
3a204fb
    dd_opendir() with the stored file descriptor is needed.
3a204fb
3a204fb
Related: #1214745
3a204fb
3a204fb
Signed-off-by: Jakub Filak <jfilak@redhat.com>
3a204fb
---
3a204fb
 src/include/dump_dir.h |  29 +++++++++++
3a204fb
 src/lib/dump_dir.c     | 130 ++++++++++++++++++++++++++++++++-----------------
3a204fb
 2 files changed, 114 insertions(+), 45 deletions(-)
3a204fb
3a204fb
diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
3a204fb
index 669f7fd..b613e0b 100644
3a204fb
--- a/src/include/dump_dir.h
3a204fb
+++ b/src/include/dump_dir.h
3a204fb
@@ -51,6 +51,11 @@ enum {
3a204fb
     DD_DONT_WAIT_FOR_LOCK = (1 << 5),
3a204fb
     /* Create the new dump directory with parent directories (mkdir -p)*/
3a204fb
     DD_CREATE_PARENTS = (1 << 6),
3a204fb
+    /* Initializes internal data, opens file descriptors and returns the
3a204fb
+     * structure. This flag is useful for testing whether a directory
3a204fb
+     * exists and to perform stat operations.
3a204fb
+     */
3a204fb
+    DD_OPEN_FD_ONLY = (1 << 7),
3a204fb
 };
3a204fb
 
3a204fb
 struct dump_dir {
3a204fb
@@ -74,7 +79,21 @@ struct dump_dir {
3a204fb
 
3a204fb
 void dd_close(struct dump_dir *dd);
3a204fb
 
3a204fb
+/* Opens the given path
3a204fb
+ */
3a204fb
 struct dump_dir *dd_opendir(const char *dir, int flags);
3a204fb
+
3a204fb
+/* Re-opens a dump_dir opened with DD_OPEN_FD_ONLY.
3a204fb
+ *
3a204fb
+ * The passed dump_dir must not be used any more and the return value must be
3a204fb
+ * used instead.
3a204fb
+ *
3a204fb
+ * The passed flags must not contain DD_OPEN_FD_ONLY.
3a204fb
+ *
3a204fb
+ * The passed dump_dir must not be already locked.
3a204fb
+ */
3a204fb
+struct dump_dir *dd_fdopendir(struct dump_dir *dd, int flags);
3a204fb
+
3a204fb
 struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags);
3a204fb
 int dd_reset_ownership(struct dump_dir *dd);
3a204fb
 /* Pass uid = (uid_t)-1L to disable chown'ing of newly created files
3a204fb
@@ -155,6 +174,11 @@ void delete_dump_dir(const char *dirname);
3a204fb
  * Returns non zero if dump dir is accessible otherwise return 0 value.
3a204fb
  */
3a204fb
 int dump_dir_accessible_by_uid(const char *dirname, uid_t uid);
3a204fb
+/* Returns the same information as dump_dir_accessible_by_uid
3a204fb
+ *
3a204fb
+ * The passed dump_dir can be opened with DD_OPEN_FD_ONLY
3a204fb
+ */
3a204fb
+int dd_accessible_by_uid(struct dump_dir *dd, uid_t uid);
3a204fb
 
3a204fb
 enum {
3a204fb
     DD_STAT_ACCESSIBLE_BY_UID = 1,
3a204fb
@@ -169,6 +193,11 @@ enum {
3a204fb
  * Returns negative number if error occurred otherwise returns 0 or positive number.
3a204fb
  */
3a204fb
 int dump_dir_stat_for_uid(const char *dirname, uid_t uid);
3a204fb
+/* Returns the same information as dump_dir_stat_for_uid
3a204fb
+ *
3a204fb
+ * The passed dump_dir can be opened with DD_OPEN_FD_ONLY
3a204fb
+ */
3a204fb
+int dd_stat_for_uid(struct dump_dir *dd, uid_t uid);
3a204fb
 
3a204fb
 /* creates not_reportable file in the problem directory and saves the
3a204fb
    reason to it, which prevents libreport from reporting the problem
3a204fb
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
3a204fb
index 7cc918a..4dcfa66 100644
3a204fb
--- a/src/lib/dump_dir.c
3a204fb
+++ b/src/lib/dump_dir.c
3a204fb
@@ -438,20 +438,41 @@ static char* rm_trailing_slashes(const char *dir)
3a204fb
     return xstrndup(dir, len);
3a204fb
 }
3a204fb
 
3a204fb
-struct dump_dir *dd_opendir(const char *dir, int flags)
3a204fb
+static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int flags)
3a204fb
 {
3a204fb
-    struct dump_dir *dd = dd_init();
3a204fb
+    if (dir != NULL)
3a204fb
+    {
3a204fb
+        dd->dd_dirname = rm_trailing_slashes(dir);
3a204fb
+        /* dd_do_open validates dd_fd */
3a204fb
+        dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);
3a204fb
 
3a204fb
-    dir = dd->dd_dirname = rm_trailing_slashes(dir);
3a204fb
-    dd->dd_fd = open(dir, O_DIRECTORY | O_NOFOLLOW);
3a204fb
-    struct stat stat_buf;
3a204fb
-    if (dd->dd_fd < 0)
3a204fb
-        goto cant_access;
3a204fb
-    if (fstat(dd->dd_fd, &stat_buf) != 0)
3a204fb
-        goto cant_access;
3a204fb
+        struct stat stat_buf;
3a204fb
+        if (dd->dd_fd < 0)
3a204fb
+            goto cant_access;
3a204fb
+        if (fstat(dd->dd_fd, &stat_buf) != 0)
3a204fb
+            goto cant_access;
3a204fb
+
3a204fb
+        /* & 0666 should remove the executable bit */
3a204fb
+        dd->mode = (stat_buf.st_mode & 0666);
3a204fb
+
3a204fb
+        dd->dd_uid = (uid_t)-1L;
3a204fb
+        dd->dd_gid = (gid_t)-1L;
3a204fb
+        if (geteuid() == 0)
3a204fb
+        {
3a204fb
+            /* In case caller would want to create more files, he'll need uid:gid */
3a204fb
+            if (fstat(dd->dd_fd, &stat_buf) != 0)
3a204fb
+            {
3a204fb
+                error_msg("Can't stat '%s'", dd->dd_dirname);
3a204fb
+                dd_close(dd);
3a204fb
+                return NULL;
3a204fb
+            }
3a204fb
+            dd->dd_uid = stat_buf.st_uid;
3a204fb
+            dd->dd_gid = stat_buf.st_gid;
3a204fb
+        }
3a204fb
 
3a204fb
-    /* & 0666 should remove the executable bit */
3a204fb
-    dd->mode = (stat_buf.st_mode & 0666);
3a204fb
+        if ((flags & DD_OPEN_FD_ONLY))
3a204fb
+            return dd;
3a204fb
+    }
3a204fb
 
3a204fb
     errno = 0;
3a204fb
     if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0)
3a204fb
@@ -482,7 +503,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
3a204fb
              * directory when run without arguments, because its option -d DIR
3a204fb
              * defaults to "."!
3a204fb
              */
3a204fb
-            error_msg("'%s' is not a problem directory", dir);
3a204fb
+            error_msg("'%s' is not a problem directory", dd->dd_dirname);
3a204fb
         }
3a204fb
         else
3a204fb
         {
3a204fb
@@ -490,36 +511,41 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
3a204fb
             if (errno == ENOENT || errno == ENOTDIR)
3a204fb
             {
3a204fb
                 if (!(flags & DD_FAIL_QUIETLY_ENOENT))
3a204fb
-                    error_msg("'%s' does not exist", dir);
3a204fb
+                    error_msg("'%s' does not exist", dd->dd_dirname);
3a204fb
             }
3a204fb
             else
3a204fb
             {
3a204fb
                 if (!(flags & DD_FAIL_QUIETLY_EACCES))
3a204fb
-                    perror_msg("Can't access '%s'", dir);
3a204fb
+                    perror_msg("Can't access '%s'", dd->dd_dirname);
3a204fb
             }
3a204fb
         }
3a204fb
         dd_close(dd);
3a204fb
         return NULL;
3a204fb
     }
3a204fb
 
3a204fb
-    dd->dd_uid = (uid_t)-1L;
3a204fb
-    dd->dd_gid = (gid_t)-1L;
3a204fb
-    if (geteuid() == 0)
3a204fb
-    {
3a204fb
-        /* In case caller would want to create more files, he'll need uid:gid */
3a204fb
-        if (fstat(dd->dd_fd, &stat_buf) != 0)
3a204fb
-        {
3a204fb
-            error_msg("Can't stat '%s'", dir);
3a204fb
-            dd_close(dd);
3a204fb
-            return NULL;
3a204fb
-        }
3a204fb
-        dd->dd_uid = stat_buf.st_uid;
3a204fb
-        dd->dd_gid = stat_buf.st_gid;
3a204fb
-    }
3a204fb
-
3a204fb
     return dd;
3a204fb
 }
3a204fb
 
3a204fb
+struct dump_dir *dd_fdopendir(struct dump_dir *dd, int flags)
3a204fb
+{
3a204fb
+    if ((flags & DD_OPEN_FD_ONLY))
3a204fb
+        error_msg_and_die("the passed flags must not contain DD_OPEN_FD_ONLY");
3a204fb
+
3a204fb
+    if (dd->dd_fd < 0)
3a204fb
+        error_msg_and_die("the dump directory was not initialized yet");
3a204fb
+
3a204fb
+    if (dd->locked)
3a204fb
+        error_msg_and_die("the dump directory is already locked");
3a204fb
+
3a204fb
+    return dd_do_open(dd, NULL, flags);
3a204fb
+}
3a204fb
+
3a204fb
+struct dump_dir *dd_opendir(const char *dir, int flags)
3a204fb
+{
3a204fb
+    struct dump_dir *dd = dd_init();
3a204fb
+    return dd_do_open(dd, dir, flags);
3a204fb
+}
3a204fb
+
3a204fb
 /* Create a fresh empty debug dump dir which is owned bu the calling user. If
3a204fb
  * you want to create the directory with meaningful ownership you should
3a204fb
  * consider using dd_create() function or you can modify the ownership
3a204fb
@@ -1384,32 +1410,22 @@ static bool uid_in_group(uid_t uid, gid_t gid)
3a204fb
 }
3a204fb
 #endif
3a204fb
 
3a204fb
-int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
3a204fb
+int dd_stat_for_uid(struct dump_dir *dd, uid_t uid)
3a204fb
 {
3a204fb
-    struct stat statbuf;
3a204fb
-    if (stat(dirname, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
3a204fb
-    {
3a204fb
-        log_debug("can't get stat of '%s': not a problem directory", dirname);
3a204fb
-        errno = ENOTDIR;
3a204fb
-        return -1;
3a204fb
-    }
3a204fb
-
3a204fb
-    errno = 0;
3a204fb
-
3a204fb
     int ddstat = 0;
3a204fb
-    if (uid == 0 || (statbuf.st_mode & S_IROTH))
3a204fb
+    if (uid == 0 || (dd->mode & S_IROTH))
3a204fb
     {
3a204fb
-        log_debug("directory '%s' is accessible by %ld uid", dirname, (long)uid);
3a204fb
+        log_debug("directory is accessible by %ld uid", (long)uid);
3a204fb
         ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
     }
3a204fb
 
3a204fb
 #if DUMP_DIR_OWNED_BY_USER > 0
3a204fb
-    if (uid == statbuf.st_uid)
3a204fb
+    if (uid == dd->dd_uid)
3a204fb
 #else
3a204fb
-    if (uid_in_group(uid, statbuf.st_gid))
3a204fb
+    if (uid_in_group(uid, dd->dd_gid))
3a204fb
 #endif
3a204fb
     {
3a204fb
-        log_debug("%ld uid owns directory '%s'", (long)uid, dirname);
3a204fb
+        log_debug("%ld uid owns directory", (long)uid);
3a204fb
         ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
         ddstat |= DD_STAT_OWNED_BY_UID;
3a204fb
     }
3a204fb
@@ -1417,6 +1433,30 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
3a204fb
     return ddstat;
3a204fb
 }
3a204fb
 
3a204fb
+int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
3a204fb
+{
3a204fb
+    struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_FD_ONLY);
3a204fb
+    if (dd == NULL)
3a204fb
+        return -1;
3a204fb
+
3a204fb
+    int r = dd_stat_for_uid(dd, uid);
3a204fb
+    dd_close(dd);
3a204fb
+
3a204fb
+    return r;
3a204fb
+}
3a204fb
+
3a204fb
+int dd_accessible_by_uid(struct dump_dir *dd, uid_t uid)
3a204fb
+{
3a204fb
+    int ddstat = dd_stat_for_uid(dd, uid);
3a204fb
+
3a204fb
+    if (ddstat >= 0)
3a204fb
+        return ddstat & DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
+
3a204fb
+    VERB3 pwarn_msg("can't determine accessibility for %ld uid", (long)uid);
3a204fb
+
3a204fb
+    return 0;
3a204fb
+}
3a204fb
+
3a204fb
 int dump_dir_accessible_by_uid(const char *dirname, uid_t uid)
3a204fb
 {
3a204fb
     int ddstat = dump_dir_stat_for_uid(dirname, uid);
3a204fb
-- 
3a204fb
2.1.0
3a204fb