Blame 0096-dbus-avoid-race-conditions-in-tests-for-dum-dir-avai.patch

fa19501
From 0bfb03ea68eb745172feccfc0f01b2ad13ff33bb Mon Sep 17 00:00:00 2001
fa19501
From: Jakub Filak <jfilak@redhat.com>
fa19501
Date: Fri, 24 Apr 2015 13:48:32 +0200
fa19501
Subject: [PATCH] dbus: avoid race-conditions in tests for dum dir availability
fa19501
fa19501
Florian Weimer <fweimer@redhat.com>
fa19501
fa19501
    dump_dir_accessible_by_uid() is fundamentally insecure because it
fa19501
    opens up a classic time-of-check-time-of-use race between this
fa19501
    function and and dd_opendir().
fa19501
fa19501
Related: #1214745
fa19501
fa19501
Signed-off-by: Jakub Filak <jfilak@redhat.com>
fa19501
---
fa19501
 src/dbus/abrt-dbus.c  | 225 +++++++++++++++++++++-----------------------------
fa19501
 src/lib/problem_api.c |  21 +++--
fa19501
 2 files changed, 109 insertions(+), 137 deletions(-)
fa19501
fa19501
diff --git a/src/dbus/abrt-dbus.c b/src/dbus/abrt-dbus.c
fa19501
index 585fcd7..0f7ac2d 100644
fa19501
--- a/src/dbus/abrt-dbus.c
fa19501
+++ b/src/dbus/abrt-dbus.c
fa19501
@@ -201,6 +201,69 @@ static void return_InvalidProblemDir_error(GDBusMethodInvocation *invocation, co
fa19501
     free(msg);
fa19501
 }
fa19501
 
fa19501
+enum {
fa19501
+    OPEN_FAIL_NO_REPLY = 1 << 0,
fa19501
+    OPEN_AUTH_ASK      = 1 << 1,
fa19501
+    OPEN_AUTH_FAIL     = 1 << 2,
fa19501
+};
fa19501
+
fa19501
+static struct dump_dir *open_dump_directory(GDBusMethodInvocation *invocation,
fa19501
+    const gchar *caller, uid_t caller_uid, const char *problem_dir, int dd_flags, int flags)
fa19501
+{
fa19501
+    if (!allowed_problem_dir(problem_dir))
fa19501
+    {
fa19501
+        log("UID=%d attempted to access not allowed problem directory '%s'",
fa19501
+                caller_uid, problem_dir);
fa19501
+        if (!(flags & OPEN_FAIL_NO_REPLY))
fa19501
+            return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
+        return NULL;
fa19501
+    }
fa19501
+
fa19501
+    struct dump_dir *dd = dd_opendir(problem_dir, DD_OPEN_FD_ONLY);
fa19501
+    if (dd == NULL)
fa19501
+    {
fa19501
+        perror_msg("can't open problem directory '%s'", problem_dir);
fa19501
+        if (!(flags & OPEN_FAIL_NO_REPLY))
fa19501
+            return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
+        return NULL;
fa19501
+    }
fa19501
+
fa19501
+    if (!dd_accessible_by_uid(dd, caller_uid))
fa19501
+    {
fa19501
+        if (errno == ENOTDIR)
fa19501
+        {
fa19501
+            log_notice("Requested directory does not exist '%s'", problem_dir);
fa19501
+            if (!(flags & OPEN_FAIL_NO_REPLY))
fa19501
+                return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
+            dd_close(dd);
fa19501
+            return NULL;
fa19501
+        }
fa19501
+
fa19501
+        if (   !(flags & OPEN_AUTH_ASK)
fa19501
+            || polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
fa19501
+        {
fa19501
+            log_notice("not authorized");
fa19501
+            if (!(flags & OPEN_FAIL_NO_REPLY))
fa19501
+                g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
+                                              "org.freedesktop.problems.AuthFailure",
fa19501
+                                              _("Not Authorized"));
fa19501
+            dd_close(dd);
fa19501
+            return NULL;
fa19501
+        }
fa19501
+    }
fa19501
+
fa19501
+    dd = dd_fdopendir(dd, dd_flags);
fa19501
+    if (dd == NULL)
fa19501
+    {
fa19501
+        log_notice("Can't open the problem '%s' with flags %x0", problem_dir, dd_flags);
fa19501
+        if (!(flags & OPEN_FAIL_NO_REPLY))
fa19501
+            g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
+                                "org.freedesktop.problems.Failure",
fa19501
+                                _("Can't open the problem"));
fa19501
+    }
fa19501
+    return dd;
fa19501
+}
fa19501
+
fa19501
 /*
fa19501
  * Checks element's rights and does not open directory if element is protected.
fa19501
  * Checks problem's rights and does not open directory if user hasn't got
fa19501
@@ -237,35 +300,8 @@ static struct dump_dir *open_directory_for_modification_of_element(
fa19501
         }
fa19501
     }
fa19501
 
fa19501
-    if (!dump_dir_accessible_by_uid(problem_id, caller_uid))
fa19501
-    {
fa19501
-        if (errno == ENOTDIR)
fa19501
-        {
fa19501
-            log_notice("'%s' is not a valid problem directory", problem_id);
fa19501
-            return_InvalidProblemDir_error(invocation, problem_id);
fa19501
-        }
fa19501
-        else
fa19501
-        {
fa19501
-            log_notice("UID(%d) is not Authorized to access '%s'", caller_uid, problem_id);
fa19501
-            g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                "org.freedesktop.problems.AuthFailure",
fa19501
-                                _("Not Authorized"));
fa19501
-        }
fa19501
-
fa19501
-        return NULL;
fa19501
-    }
fa19501
-
fa19501
-    struct dump_dir *dd = dd_opendir(problem_id, /* flags : */ 0);
fa19501
-    if (!dd)
fa19501
-    {   /* This should not happen because of the access check above */
fa19501
-        log_notice("Can't access the problem '%s' for modification", problem_id);
fa19501
-        g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                "org.freedesktop.problems.Failure",
fa19501
-                                _("Can't access the problem for modification"));
fa19501
-        return NULL;
fa19501
-    }
fa19501
-
fa19501
-    return dd;
fa19501
+    return open_dump_directory(invocation, /*caller*/NULL, caller_uid, problem_id, /*Read/Write*/0,
fa19501
+                               OPEN_AUTH_FAIL);
fa19501
 }
fa19501
 
fa19501
 
fa19501
@@ -421,7 +457,15 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
             return;
fa19501
         }
fa19501
 
fa19501
-        int ddstat = dump_dir_stat_for_uid(problem_dir, caller_uid);
fa19501
+        struct dump_dir *dd = dd_opendir(problem_dir, DD_OPEN_FD_ONLY);
fa19501
+        if (dd == NULL)
fa19501
+        {
fa19501
+            perror_msg("can't open problem directory '%s'", problem_dir);
fa19501
+            return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
+            return;
fa19501
+        }
fa19501
+
fa19501
+        int ddstat = dd_stat_for_uid(dd, caller_uid);
fa19501
         if (ddstat < 0)
fa19501
         {
fa19501
             if (errno == ENOTDIR)
fa19501
@@ -435,6 +479,7 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
 
fa19501
             return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
 
fa19501
+            dd_close(dd);
fa19501
             return;
fa19501
         }
fa19501
 
fa19501
@@ -442,6 +487,7 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
         {   //caller seems to be in group with access to this dir, so no action needed
fa19501
             log_notice("caller has access to the requested directory %s", problem_dir);
fa19501
             g_dbus_method_invocation_return_value(invocation, NULL);
fa19501
+            dd_close(dd);
fa19501
             return;
fa19501
         }
fa19501
 
fa19501
@@ -452,10 +498,11 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
             g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
                                               "org.freedesktop.problems.AuthFailure",
fa19501
                                               _("Not Authorized"));
fa19501
+            dd_close(dd);
fa19501
             return;
fa19501
         }
fa19501
 
fa19501
-        struct dump_dir *dd = dd_opendir(problem_dir, DD_OPEN_READONLY | DD_FAIL_QUIETLY_EACCES);
fa19501
+        dd = dd_fdopendir(dd, DD_OPEN_READONLY | DD_FAIL_QUIETLY_EACCES);
fa19501
         if (!dd)
fa19501
         {
fa19501
             return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
@@ -483,37 +530,10 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
         g_variant_get_child(parameters, 0, "&s", &problem_dir);
fa19501
         log_notice("problem_dir:'%s'", problem_dir);
fa19501
 
fa19501
-        if (!allowed_problem_dir(problem_dir))
fa19501
-        {
fa19501
-            return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
-        if (!dump_dir_accessible_by_uid(problem_dir, caller_uid))
fa19501
-        {
fa19501
-            if (errno == ENOTDIR)
fa19501
-            {
fa19501
-                log_notice("Requested directory does not exist '%s'", problem_dir);
fa19501
-                return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
-                return;
fa19501
-            }
fa19501
-
fa19501
-            if (polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
fa19501
-            {
fa19501
-                log_notice("not authorized");
fa19501
-                g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                                  "org.freedesktop.problems.AuthFailure",
fa19501
-                                                  _("Not Authorized"));
fa19501
-                return;
fa19501
-            }
fa19501
-        }
fa19501
-
fa19501
-        struct dump_dir *dd = dd_opendir(problem_dir, DD_OPEN_READONLY | DD_FAIL_QUIETLY_EACCES);
fa19501
+        struct dump_dir *dd = open_dump_directory(invocation, caller, caller_uid,
fa19501
+                problem_dir, DD_OPEN_READONLY | DD_FAIL_QUIETLY_EACCES , OPEN_AUTH_ASK);
fa19501
         if (!dd)
fa19501
-        {
fa19501
-            return_InvalidProblemDir_error(invocation, problem_dir);
fa19501
             return;
fa19501
-        }
fa19501
 
fa19501
 	/* Get 2nd param - vector of element names */
fa19501
         GVariant *array = g_variant_get_child_value(parameters, 1);
fa19501
@@ -560,32 +580,10 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
 
fa19501
         g_variant_get(parameters, "(&s)", &problem_id);
fa19501
 
fa19501
-        if (!allowed_problem_dir(problem_id))
fa19501
-        {
fa19501
-            return_InvalidProblemDir_error(invocation, problem_id);
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
-        int ddstat = dump_dir_stat_for_uid(problem_id, caller_uid);
fa19501
-        if ((ddstat & DD_STAT_ACCESSIBLE_BY_UID) == 0 &&
fa19501
-                polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
fa19501
-        {
fa19501
-            log_notice("Unauthorized access : '%s'", problem_id);
fa19501
-            g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                              "org.freedesktop.problems.AuthFailure",
fa19501
-                                              _("Not Authorized"));
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
-        struct dump_dir *dd = dd_opendir(problem_id, DD_OPEN_READONLY);
fa19501
-        if (dd == NULL)
fa19501
-        {
fa19501
-            log_notice("Can't access the problem '%s' for reading", problem_id);
fa19501
-            g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                    "org.freedesktop.problems.Failure",
fa19501
-                                    _("Can't access the problem for reading"));
fa19501
+        struct dump_dir *dd = open_dump_directory(invocation, caller, caller_uid,
fa19501
+                    problem_id, DD_OPEN_READONLY, OPEN_AUTH_ASK);
fa19501
+        if (!dd)
fa19501
             return;
fa19501
-        }
fa19501
 
fa19501
         problem_data_t *pd = create_problem_data_from_dump_dir(dd);
fa19501
         dd_close(dd);
fa19501
@@ -629,12 +627,6 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
 
fa19501
         g_variant_get(parameters, "(&s&s&s)", &problem_id, &element, &value);
fa19501
 
fa19501
-        if (!allowed_problem_dir(problem_id))
fa19501
-        {
fa19501
-            return_InvalidProblemDir_error(invocation, problem_id);
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
         if (element == NULL || element[0] == '\0' || strlen(element) > 64)
fa19501
         {
fa19501
             log_notice("'%s' is not a valid element name of '%s'", element, problem_id);
fa19501
@@ -694,12 +686,6 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
 
fa19501
         g_variant_get(parameters, "(&s&s)", &problem_id, &element);
fa19501
 
fa19501
-        if (!allowed_problem_dir(problem_id))
fa19501
-        {
fa19501
-            return_InvalidProblemDir_error(invocation, problem_id);
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
         struct dump_dir *dd = open_directory_for_modification_of_element(
fa19501
                                     invocation, caller_uid, problem_id, element);
fa19501
         if (!dd)
fa19501
@@ -732,33 +718,10 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
 
fa19501
         g_variant_get(parameters, "(&s&s)", &problem_id, &element);
fa19501
 
fa19501
-        if (!allowed_problem_dir(problem_id))
fa19501
-        {
fa19501
-            return_InvalidProblemDir_error(invocation, problem_id);
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
-        struct dump_dir *dd = dd_opendir(problem_id, DD_OPEN_READONLY);
fa19501
+        struct dump_dir *dd = open_dump_directory(invocation, caller, caller_uid,
fa19501
+                problem_id, DD_OPEN_READONLY, OPEN_AUTH_ASK);
fa19501
         if (!dd)
fa19501
-        {
fa19501
-            log_notice("Can't access the problem '%s'", problem_id);
fa19501
-            g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                    "org.freedesktop.problems.Failure",
fa19501
-                                    _("Can't access the problem"));
fa19501
-            return;
fa19501
-        }
fa19501
-
fa19501
-        int ddstat = dump_dir_stat_for_uid(problem_id, caller_uid);
fa19501
-        if ((ddstat & DD_STAT_ACCESSIBLE_BY_UID) == 0 &&
fa19501
-                polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
fa19501
-        {
fa19501
-            dd_close(dd);
fa19501
-            log_notice("Unauthorized access : '%s'", problem_id);
fa19501
-            g_dbus_method_invocation_return_dbus_error(invocation,
fa19501
-                                              "org.freedesktop.problems.AuthFailure",
fa19501
-                                              _("Not Authorized"));
fa19501
             return;
fa19501
-        }
fa19501
 
fa19501
         int ret = dd_exist(dd, element);
fa19501
         dd_close(dd);
fa19501
@@ -793,20 +756,18 @@ static void handle_method_call(GDBusConnection *connection,
fa19501
         for (GList *l = problem_dirs; l; l = l->next)
fa19501
         {
fa19501
             const char *dir_name = (const char*)l->data;
fa19501
-            if (!dump_dir_accessible_by_uid(dir_name, caller_uid))
fa19501
+
fa19501
+            struct dump_dir *dd = open_dump_directory(invocation, caller, caller_uid,
fa19501
+                        dir_name, /*Read/Write*/0, OPEN_FAIL_NO_REPLY | OPEN_AUTH_ASK);
fa19501
+
fa19501
+            if (dd)
fa19501
             {
fa19501
-                if (errno == ENOTDIR)
fa19501
+                if (dd_delete(dd) != 0)
fa19501
                 {
fa19501
-                    log_notice("Requested directory does not exist '%s'", dir_name);
fa19501
-                    continue;
fa19501
-                }
fa19501
-
fa19501
-                if (polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
fa19501
-                { // if user didn't provide correct credentials, just move to the next dir
fa19501
-                    continue;
fa19501
+                    error_msg("Failed to delete problem directory '%s'", dir_name);
fa19501
+                    dd_close(dd);
fa19501
                 }
fa19501
             }
fa19501
-            delete_dump_dir(dir_name);
fa19501
         }
fa19501
 
fa19501
         g_dbus_method_invocation_return_value(invocation, NULL);
fa19501
diff --git a/src/lib/problem_api.c b/src/lib/problem_api.c
fa19501
index 86222cf..96a49fc 100644
fa19501
--- a/src/lib/problem_api.c
fa19501
+++ b/src/lib/problem_api.c
fa19501
@@ -46,7 +46,17 @@ int for_each_problem_in_dir(const char *path,
fa19501
             continue; /* skip "." and ".." */
fa19501
 
fa19501
         char *full_name = concat_path_file(path, dent->d_name);
fa19501
-        if (caller_uid == -1 || dump_dir_accessible_by_uid(full_name, caller_uid))
fa19501
+
fa19501
+        struct dump_dir *dd = dd_opendir(full_name,   DD_OPEN_FD_ONLY
fa19501
+                                                    | DD_FAIL_QUIETLY_ENOENT
fa19501
+                                                    | DD_FAIL_QUIETLY_EACCES);
fa19501
+        if (dd == NULL)
fa19501
+        {
fa19501
+            VERB2 perror_msg("can't open problem directory '%s'", full_name);
fa19501
+            continue;
fa19501
+        }
fa19501
+
fa19501
+        if (caller_uid == -1 || dd_accessible_by_uid(dd, caller_uid))
fa19501
         {
fa19501
             /* Silently ignore *any* errors, not only EACCES.
fa19501
              * We saw "lock file is locked by process PID" error
fa19501
@@ -55,14 +65,15 @@ int for_each_problem_in_dir(const char *path,
fa19501
             int sv_logmode = logmode;
fa19501
             /* Silently ignore errors only in the silent log level. */
fa19501
             logmode = g_verbose == 0 ? 0: sv_logmode;
fa19501
-            struct dump_dir *dd = dd_opendir(full_name, DD_OPEN_READONLY | DD_FAIL_QUIETLY_EACCES | DD_DONT_WAIT_FOR_LOCK);
fa19501
+            dd = dd_fdopendir(dd, DD_OPEN_READONLY | DD_DONT_WAIT_FOR_LOCK);
fa19501
             logmode = sv_logmode;
fa19501
             if (dd)
fa19501
-            {
fa19501
                 brk = callback ? callback(dd, arg) : 0;
fa19501
-                dd_close(dd);
fa19501
-            }
fa19501
         }
fa19501
+
fa19501
+        if (dd)
fa19501
+            dd_close(dd);
fa19501
+
fa19501
         free(full_name);
fa19501
         if (brk)
fa19501
             break;
fa19501
-- 
fa19501
2.1.0
fa19501