Blame SOURCES/0133-lib-add-alternative-dd-functions-accepting-fds.patch

4b6aa8
From 16b2ebdb678b7475bacb80dd59e949055d3f856c Mon Sep 17 00:00:00 2001
4b6aa8
From: Jakub Filak <jfilak@redhat.com>
4b6aa8
Date: Fri, 24 Apr 2015 11:42:18 +0200
4b6aa8
Subject: [LIBREPORT PATCH] lib: add alternative dd functions accepting fds
4b6aa8
4b6aa8
Florian Weimer <fweimer@redhat.com>:
4b6aa8
4b6aa8
    dump_dir_accessible_by_uid() is fundamentally insecure because it
4b6aa8
    opens up a classic time-of-check-time-of-use race between this
4b6aa8
    function and and dd_opendir().  At least re-checking after
4b6aa8
    dd_opendir() with the stored file descriptor is needed.
4b6aa8
4b6aa8
Related: #1214745
4b6aa8
4b6aa8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
4b6aa8
---
4b6aa8
 src/include/dump_dir.h |  8 +++++
4b6aa8
 src/lib/dump_dir.c     | 82 +++++++++++++++++++++++++++++++++++++++++---------
4b6aa8
 2 files changed, 75 insertions(+), 15 deletions(-)
4b6aa8
4b6aa8
diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
4b6aa8
index d675362..07b119a 100644
4b6aa8
--- a/src/include/dump_dir.h
4b6aa8
+++ b/src/include/dump_dir.h
4b6aa8
@@ -68,7 +68,13 @@ struct dump_dir {
4b6aa8
 
4b6aa8
 void dd_close(struct dump_dir *dd);
4b6aa8
 
4b6aa8
+/* Opens the given path and returns the resulting file descriptor.
4b6aa8
+ */
4b6aa8
+int dd_openfd(const char *dir);
4b6aa8
 struct dump_dir *dd_opendir(const char *dir, int flags);
4b6aa8
+/* Skips dd_openfd(dir) and uses the given file descriptor instead
4b6aa8
+ */
4b6aa8
+struct dump_dir *dd_fdopendir(int dir_fd, const char *dir, int flags);
4b6aa8
 struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags);
4b6aa8
 int dd_reset_ownership(struct dump_dir *dd);
4b6aa8
 /* Pass uid = (uid_t)-1L to disable chown'ing of newly created files
4b6aa8
@@ -147,6 +153,7 @@ void delete_dump_dir(const char *dirname);
4b6aa8
  * Returns non zero if dump dir is accessible otherwise return 0 value.
4b6aa8
  */
4b6aa8
 int dump_dir_accessible_by_uid(const char *dirname, uid_t uid);
4b6aa8
+int fdump_dir_accessible_by_uid(int dir_fd, uid_t uid);
4b6aa8
 
4b6aa8
 enum {
4b6aa8
     DD_STAT_ACCESSIBLE_BY_UID = 1,
4b6aa8
@@ -161,6 +168,7 @@ enum {
4b6aa8
  * Returns negative number if error occurred otherwise returns 0 or positive number.
4b6aa8
  */
4b6aa8
 int dump_dir_stat_for_uid(const char *dirname, uid_t uid);
4b6aa8
+int fdump_dir_stat_for_uid(int dir_fd, uid_t uid);
4b6aa8
 
4b6aa8
 /* creates not_reportable file in the problem directory and saves the
4b6aa8
    reason to it, which prevents libreport from reporting the problem
4b6aa8
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
4b6aa8
index 16cd987..a0e96e4 100644
4b6aa8
--- a/src/lib/dump_dir.c
4b6aa8
+++ b/src/lib/dump_dir.c
4b6aa8
@@ -417,12 +417,8 @@ static char* rm_trailing_slashes(const char *dir)
4b6aa8
     return xstrndup(dir, len);
4b6aa8
 }
4b6aa8
 
4b6aa8
-struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
+static struct dump_dir *dd_do_open(struct dump_dir *dd, int flags)
4b6aa8
 {
4b6aa8
-    struct dump_dir *dd = dd_init();
4b6aa8
-
4b6aa8
-    dir = dd->dd_dirname = rm_trailing_slashes(dir);
4b6aa8
-    dd->dd_fd = open(dir, O_DIRECTORY | O_NOFOLLOW);
4b6aa8
     struct stat stat_buf;
4b6aa8
     if (dd->dd_fd < 0)
4b6aa8
         goto cant_access;
4b6aa8
@@ -440,6 +436,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
             /* Directory is not writable. If it seems to be readable,
4b6aa8
              * return "read only" dd, not NULL
4b6aa8
              *
4b6aa8
+             *
4b6aa8
              * Does the directory have 'x' flag?
4b6aa8
              */
4b6aa8
             if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) == 0)
4b6aa8
@@ -461,7 +458,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
              * directory when run without arguments, because its option -d DIR
4b6aa8
              * defaults to "."!
4b6aa8
              */
4b6aa8
-            error_msg("'%s' is not a problem directory", dir);
4b6aa8
+            error_msg("'%s' is not a problem directory", dd->dd_dirname);
4b6aa8
         }
4b6aa8
         else
4b6aa8
         {
4b6aa8
@@ -469,12 +466,12 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
             if (errno == ENOENT || errno == ENOTDIR)
4b6aa8
             {
4b6aa8
                 if (!(flags & DD_FAIL_QUIETLY_ENOENT))
4b6aa8
-                    error_msg("'%s' does not exist", dir);
4b6aa8
+                    error_msg("'%s' does not exist", dd->dd_dirname);
4b6aa8
             }
4b6aa8
             else
4b6aa8
             {
4b6aa8
                 if (!(flags & DD_FAIL_QUIETLY_EACCES))
4b6aa8
-                    perror_msg("Can't access '%s'", dir);
4b6aa8
+                    perror_msg("Can't access '%s'", dd->dd_dirname);
4b6aa8
             }
4b6aa8
         }
4b6aa8
         dd_close(dd);
4b6aa8
@@ -488,7 +485,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
         /* In case caller would want to create more files, he'll need uid:gid */
4b6aa8
         if (fstat(dd->dd_fd, &stat_buf) != 0)
4b6aa8
         {
4b6aa8
-            error_msg("Can't stat '%s'", dir);
4b6aa8
+            error_msg("Can't stat '%s'", dd->dd_dirname);
4b6aa8
             dd_close(dd);
4b6aa8
             return NULL;
4b6aa8
         }
4b6aa8
@@ -499,6 +496,34 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
     return dd;
4b6aa8
 }
4b6aa8
 
4b6aa8
+int dd_openfd(const char *dir)
4b6aa8
+{
4b6aa8
+    return open(dir, O_DIRECTORY | O_NOFOLLOW);
4b6aa8
+}
4b6aa8
+
4b6aa8
+struct dump_dir *dd_fdopendir(int dir_fd, const char *dir, int flags)
4b6aa8
+{
4b6aa8
+    struct dump_dir *dd = dd_init();
4b6aa8
+
4b6aa8
+    dd->dd_dirname = rm_trailing_slashes(dir);
4b6aa8
+    dd->dd_fd = dir_fd;
4b6aa8
+
4b6aa8
+    /* Do not interpret errno in dd_do_open() if dd_fd < 0 */
4b6aa8
+    errno = 0;
4b6aa8
+    return dd_do_open(dd, flags);
4b6aa8
+}
4b6aa8
+
4b6aa8
+struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
+{
4b6aa8
+    struct dump_dir *dd = dd_init();
4b6aa8
+
4b6aa8
+    dd->dd_dirname = rm_trailing_slashes(dir);
4b6aa8
+    /* dd_do_open validates dd_fd */
4b6aa8
+    dd->dd_fd = dd_openfd(dir);
4b6aa8
+
4b6aa8
+    return dd_do_open(dd, flags);
4b6aa8
+}
4b6aa8
+
4b6aa8
 /* Create a fresh empty debug dump dir which is owned bu the calling user. If
4b6aa8
  * you want to create the directory with meaningful ownership you should
4b6aa8
  * consider using dd_create() function or you can modify the ownership
4b6aa8
@@ -936,7 +961,7 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid)
4b6aa8
         error_msg_and_die("dump_dir is not opened"); /* bug */
4b6aa8
 
4b6aa8
     struct stat statbuf;
4b6aa8
-    if (!(stat(dd->dd_dirname, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)))
4b6aa8
+    if (!fstat(dd->dd_fd, &statbuf) == 0)
4b6aa8
     {
4b6aa8
         perror_msg("stat('%s')", dd->dd_dirname);
4b6aa8
         return 1;
4b6aa8
@@ -1352,12 +1377,12 @@ static bool uid_in_group(uid_t uid, gid_t gid)
4b6aa8
 }
4b6aa8
 #endif
4b6aa8
 
4b6aa8
-int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
4b6aa8
+int fdump_dir_stat_for_uid(int dir_fd, uid_t uid)
4b6aa8
 {
4b6aa8
     struct stat statbuf;
4b6aa8
-    if (stat(dirname, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
4b6aa8
+    if (fstat(dir_fd, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
4b6aa8
     {
4b6aa8
-        log_debug("can't get stat of '%s': not a problem directory", dirname);
4b6aa8
+        log_debug("can't get stat: not a problem directory");
4b6aa8
         errno = ENOTDIR;
4b6aa8
         return -1;
4b6aa8
     }
4b6aa8
@@ -1367,7 +1392,7 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
4b6aa8
     int ddstat = 0;
4b6aa8
     if (uid == 0 || (statbuf.st_mode & S_IROTH))
4b6aa8
     {
4b6aa8
-        log_debug("directory '%s' is accessible by %ld uid", dirname, (long)uid);
4b6aa8
+        log_debug("directory is accessible by %ld uid", (long)uid);
4b6aa8
         ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
4b6aa8
     }
4b6aa8
 
4b6aa8
@@ -1377,7 +1402,7 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
4b6aa8
     if (uid_in_group(uid, statbuf.st_gid))
4b6aa8
 #endif
4b6aa8
     {
4b6aa8
-        log_debug("%ld uid owns directory '%s'", (long)uid, dirname);
4b6aa8
+        log_debug("%ld uid owns directory", (long)uid);
4b6aa8
         ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
4b6aa8
         ddstat |= DD_STAT_OWNED_BY_UID;
4b6aa8
     }
4b6aa8
@@ -1385,6 +1410,33 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
4b6aa8
     return ddstat;
4b6aa8
 }
4b6aa8
 
4b6aa8
+int dump_dir_stat_for_uid(const char *dirname, uid_t uid)
4b6aa8
+{
4b6aa8
+    int dir_fd = open(dirname, O_DIRECTORY | O_NOFOLLOW);
4b6aa8
+    if (dir_fd < 0)
4b6aa8
+    {
4b6aa8
+        log_debug("can't open '%s': not a problem directory", dirname);
4b6aa8
+        errno = ENOTDIR;
4b6aa8
+        return -1;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    int r = fdump_dir_stat_for_uid(dir_fd, uid);
4b6aa8
+    close(dir_fd);
4b6aa8
+    return r;
4b6aa8
+}
4b6aa8
+
4b6aa8
+int fdump_dir_accessible_by_uid(int dir_fd, uid_t uid)
4b6aa8
+{
4b6aa8
+    int ddstat = fdump_dir_stat_for_uid(dir_fd, uid);
4b6aa8
+
4b6aa8
+    if (ddstat >= 0)
4b6aa8
+        return ddstat & DD_STAT_ACCESSIBLE_BY_UID;
4b6aa8
+
4b6aa8
+    VERB3 pwarn_msg("can't determine accessibility for %ld uid", (long)uid);
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+
4b6aa8
 int dump_dir_accessible_by_uid(const char *dirname, uid_t uid)
4b6aa8
 {
4b6aa8
     int ddstat = dump_dir_stat_for_uid(dirname, uid);
4b6aa8
-- 
4b6aa8
1.8.3.1
4b6aa8