Matej Habrnal fa1950
From 427b1c2b9b03c1fe52edb8a129e821a9ea2e0bea Mon Sep 17 00:00:00 2001
Matej Habrnal fa1950
From: Jakub Filak <jfilak@redhat.com>
Matej Habrnal fa1950
Date: Thu, 23 Apr 2015 14:40:18 +0200
Matej Habrnal fa1950
Subject: [PATCH] lib: add functions validating dump dir
Matej Habrnal fa1950
Matej Habrnal fa1950
Move the code from abrt-server to shared library and fix the condition
Matej Habrnal fa1950
validating dump dir's path.
Matej Habrnal fa1950
Matej Habrnal fa1950
As of now, abrt is allowed to process only direct sub-directories of the
Matej Habrnal fa1950
dump locations with appropriate mode and owner and group.
Matej Habrnal fa1950
Matej Habrnal fa1950
Signed-off-by: Jakub Filak <jfilak@redhat.com>
Matej Habrnal fa1950
---
Matej Habrnal fa1950
 src/daemon/abrt-server.c | 36 ++++++++++++--------
Matej Habrnal fa1950
 src/include/libabrt.h    |  9 +++++
Matej Habrnal fa1950
 src/lib/hooklib.c        | 77 +++++++++++++++++++++++++++++++++++++++++++
Matej Habrnal fa1950
 tests/Makefile.am        |  3 +-
Matej Habrnal fa1950
 tests/hooklib.at         | 85 ++++++++++++++++++++++++++++++++++++++++++++++++
Matej Habrnal fa1950
 tests/testsuite.at       |  1 +
Matej Habrnal fa1950
 6 files changed, 196 insertions(+), 15 deletions(-)
Matej Habrnal fa1950
 create mode 100644 tests/hooklib.at
Matej Habrnal fa1950
Matej Habrnal fa1950
diff --git a/src/daemon/abrt-server.c b/src/daemon/abrt-server.c
Matej Habrnal fa1950
index 287c510..8c48509 100644
Matej Habrnal fa1950
--- a/src/daemon/abrt-server.c
Matej Habrnal fa1950
+++ b/src/daemon/abrt-server.c
Matej Habrnal fa1950
@@ -15,6 +15,7 @@
Matej Habrnal fa1950
   with this program; if not, write to the Free Software Foundation, Inc.,
Matej Habrnal fa1950
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Matej Habrnal fa1950
 */
Matej Habrnal fa1950
+#include "problem_api.h"
Matej Habrnal fa1950
 #include "libabrt.h"
Matej Habrnal fa1950
 
Matej Habrnal fa1950
 /* Maximal length of backtrace. */
Matej Habrnal fa1950
@@ -75,20 +76,6 @@ static unsigned total_bytes_read = 0;
Matej Habrnal fa1950
 static uid_t client_uid = (uid_t)-1L;
Matej Habrnal fa1950
 
Matej Habrnal fa1950
 
Matej Habrnal fa1950
-static bool dir_is_in_dump_location(const char *dump_dir_name)
Matej Habrnal fa1950
-{
Matej Habrnal fa1950
-    unsigned len = strlen(g_settings_dump_location);
Matej Habrnal fa1950
-
Matej Habrnal fa1950
-    if (strncmp(dump_dir_name, g_settings_dump_location, len) == 0
Matej Habrnal fa1950
-     && dump_dir_name[len] == '/'
Matej Habrnal fa1950
-    /* must not contain "/." anywhere (IOW: disallow ".." component) */
Matej Habrnal fa1950
-     && !strstr(dump_dir_name + len, "/.")
Matej Habrnal fa1950
-    ) {
Matej Habrnal fa1950
-        return 1;
Matej Habrnal fa1950
-    }
Matej Habrnal fa1950
-    return 0;
Matej Habrnal fa1950
-}
Matej Habrnal fa1950
-
Matej Habrnal fa1950
 /* Remove dump dir */
Matej Habrnal fa1950
 static int delete_path(const char *dump_dir_name)
Matej Habrnal fa1950
 {
Matej Habrnal fa1950
@@ -99,6 +86,11 @@ static int delete_path(const char *dump_dir_name)
Matej Habrnal fa1950
         error_msg("Bad problem directory name '%s', should start with: '%s'", dump_dir_name, g_settings_dump_location);
Matej Habrnal fa1950
         return 400; /* Bad Request */
Matej Habrnal fa1950
     }
Matej Habrnal fa1950
+    if (!dir_has_correct_permissions(dump_dir_name, DD_PERM_DAEMONS))
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        error_msg("Problem directory '%s' has wrong owner or group", dump_dir_name);
Matej Habrnal fa1950
+        return 400; /*  */
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
     if (!dump_dir_accessible_by_uid(dump_dir_name, client_uid))
Matej Habrnal fa1950
     {
Matej Habrnal fa1950
         if (errno == ENOTDIR)
Matej Habrnal fa1950
@@ -153,6 +145,22 @@ static int run_post_create(const char *dirname)
Matej Habrnal fa1950
         error_msg("Bad problem directory name '%s', should start with: '%s'", dirname, g_settings_dump_location);
Matej Habrnal fa1950
         return 400; /* Bad Request */
Matej Habrnal fa1950
     }
Matej Habrnal fa1950
+    if (!dir_has_correct_permissions(dirname, DD_PERM_EVENTS))
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        error_msg("Problem directory '%s' has wrong owner or group", dirname);
Matej Habrnal fa1950
+        return 400; /*  */
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+    /* Check completness */
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_READONLY);
Matej Habrnal fa1950
+        const bool complete = dd && problem_dump_dir_is_complete(dd);
Matej Habrnal fa1950
+        dd_close(dd);
Matej Habrnal fa1950
+        if (complete)
Matej Habrnal fa1950
+        {
Matej Habrnal fa1950
+            error_msg("Problem directory '%s' has already been processed", dirname);
Matej Habrnal fa1950
+            return 403;
Matej Habrnal fa1950
+        }
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
     if (!dump_dir_accessible_by_uid(dirname, client_uid))
Matej Habrnal fa1950
     {
Matej Habrnal fa1950
         if (errno == ENOTDIR)
Matej Habrnal fa1950
diff --git a/src/include/libabrt.h b/src/include/libabrt.h
Matej Habrnal fa1950
index abfbc97..9de222d 100644
Matej Habrnal fa1950
--- a/src/include/libabrt.h
Matej Habrnal fa1950
+++ b/src/include/libabrt.h
Matej Habrnal fa1950
@@ -47,6 +47,15 @@ char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec);
Matej Habrnal fa1950
 #define get_backtrace abrt_get_backtrace
Matej Habrnal fa1950
 char *get_backtrace(const char *dump_dir_name, unsigned timeout_sec, const char *debuginfo_dirs);
Matej Habrnal fa1950
 
Matej Habrnal fa1950
+#define dir_is_in_dump_location abrt_dir_is_in_dump_location
Matej Habrnal fa1950
+bool dir_is_in_dump_location(const char *dir_name);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+enum {
Matej Habrnal fa1950
+    DD_PERM_EVENTS  = 1 << 0,
Matej Habrnal fa1950
+    DD_PERM_DAEMONS = 1 << 1,
Matej Habrnal fa1950
+};
Matej Habrnal fa1950
+#define dir_has_correct_permissions abrt_dir_has_correct_permissions
Matej Habrnal fa1950
+bool dir_has_correct_permissions(const char *dir_name, int flags);
Matej Habrnal fa1950
 
Matej Habrnal fa1950
 #define g_settings_nMaxCrashReportsSize abrt_g_settings_nMaxCrashReportsSize
Matej Habrnal fa1950
 extern unsigned int  g_settings_nMaxCrashReportsSize;
Matej Habrnal fa1950
diff --git a/src/lib/hooklib.c b/src/lib/hooklib.c
Matej Habrnal fa1950
index 2be4e80..c94cadf 100644
Matej Habrnal fa1950
--- a/src/lib/hooklib.c
Matej Habrnal fa1950
+++ b/src/lib/hooklib.c
Matej Habrnal fa1950
@@ -475,3 +475,80 @@ int signal_is_fatal(int signal_no, const char **name)
Matej Habrnal fa1950
 
Matej Habrnal fa1950
     return signame != NULL;
Matej Habrnal fa1950
 }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+bool dir_is_in_dump_location(const char *dir_name)
Matej Habrnal fa1950
+{
Matej Habrnal fa1950
+    unsigned len = strlen(g_settings_dump_location);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* The path must start with "g_settings_dump_location" */
Matej Habrnal fa1950
+    if (strncmp(dir_name, g_settings_dump_location, len) != 0)
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        log_debug("Bad parent directory: '%s' not in '%s'", g_settings_dump_location, dir_name);
Matej Habrnal fa1950
+        return false;
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* and must be a sub-directory of the g_settings_dump_location dir */
Matej Habrnal fa1950
+    const char *base_name = dir_name + len;
Matej Habrnal fa1950
+    while (*base_name && *base_name == '/')
Matej Habrnal fa1950
+        ++base_name;
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    if (*(base_name - 1) != '/' || !str_is_correct_filename(base_name))
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        log_debug("Invalid dump directory name: '%s'", base_name);
Matej Habrnal fa1950
+        return false;
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* and we are sure it is a directory */
Matej Habrnal fa1950
+    struct stat sb;
Matej Habrnal fa1950
+    if (lstat(dir_name, &sb) < 0)
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        VERB2 perror_msg("stat('%s')", dir_name);
Matej Habrnal fa1950
+        return errno== ENOENT;
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    return S_ISDIR(sb.st_mode);
Matej Habrnal fa1950
+}
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+bool dir_has_correct_permissions(const char *dir_name, int flags)
Matej Habrnal fa1950
+{
Matej Habrnal fa1950
+    struct stat statbuf;
Matej Habrnal fa1950
+    if (lstat(dir_name, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        error_msg("Path '%s' isn't directory", dir_name);
Matej Habrnal fa1950
+        return false;
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* Get ABRT's group id */
Matej Habrnal fa1950
+    struct group *gr = getgrnam("abrt");
Matej Habrnal fa1950
+    if (!gr)
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        error_msg("The group 'abrt' does not exist");
Matej Habrnal fa1950
+        return false;
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* The group must be root or abrt. */
Matej Habrnal fa1950
+    const bool correct_group =    statbuf.st_gid == 0
Matej Habrnal fa1950
+                               || statbuf.st_gid == gr->gr_gid;
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* Require owner 'root' and group 'abrt' for the event shell scripts.
Matej Habrnal fa1950
+     * Because the shell scripts are vulnerable to hard link and symbolic link
Matej Habrnal fa1950
+     * attacks.
Matej Habrnal fa1950
+     */
Matej Habrnal fa1950
+    const bool events =    statbuf.st_uid == 0
Matej Habrnal fa1950
+                        && correct_group
Matej Habrnal fa1950
+                        && (statbuf.st_mode & S_IWGRP) == 0
Matej Habrnal fa1950
+                        && (statbuf.st_mode & S_IWOTH) == 0;
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    if ((flags & DD_PERM_EVENTS))
Matej Habrnal fa1950
+        return events;
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    /* Be less restrictive and allow the daemos (abrtd, abrt-dbus) to work with
Matej Habrnal fa1950
+     * dump directories having group 'root' or 'abrt'.
Matej Habrnal fa1950
+     *
Matej Habrnal fa1950
+     * 1. Chowning of dump directories switches the ownership to 'user':'abrt'
Matej Habrnal fa1950
+     * 2. We want to allow users to delete their problems
Matej Habrnal fa1950
+     * 3. We want to allow users to modify their data
Matej Habrnal fa1950
+     * 4. The daemons are hardened agains hard link and symbolic link issues.
Matej Habrnal fa1950
+     */
Matej Habrnal fa1950
+    return correct_group;
Matej Habrnal fa1950
+}
Matej Habrnal fa1950
diff --git a/tests/Makefile.am b/tests/Makefile.am
Matej Habrnal fa1950
index 5ef08a0..416f579 100644
Matej Habrnal fa1950
--- a/tests/Makefile.am
Matej Habrnal fa1950
+++ b/tests/Makefile.am
Matej Habrnal fa1950
@@ -29,7 +29,8 @@ TESTSUITE_AT = \
Matej Habrnal fa1950
   testsuite.at \
Matej Habrnal fa1950
   pyhook.at \
Matej Habrnal fa1950
   koops-parser.at \
Matej Habrnal fa1950
-  ignored_problems.at
Matej Habrnal fa1950
+  ignored_problems.at \
Matej Habrnal fa1950
+  hooklib.at
Matej Habrnal fa1950
 
Matej Habrnal fa1950
 EXTRA_DIST += $(TESTSUITE_AT)
Matej Habrnal fa1950
 TESTSUITE = $(srcdir)/testsuite
Matej Habrnal fa1950
diff --git a/tests/hooklib.at b/tests/hooklib.at
Matej Habrnal fa1950
new file mode 100644
Matej Habrnal fa1950
index 0000000..70631c6
Matej Habrnal fa1950
--- /dev/null
Matej Habrnal fa1950
+++ b/tests/hooklib.at
Matej Habrnal fa1950
@@ -0,0 +1,85 @@
Matej Habrnal fa1950
+# -*- Autotest -*-
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+AT_BANNER([hooklib])
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+AT_TESTFUN([dir_is_in_dump_location],
Matej Habrnal fa1950
+[[
Matej Habrnal fa1950
+#include "libabrt.h"
Matej Habrnal fa1950
+#include <assert.h>
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+void test(char *name, bool expected)
Matej Habrnal fa1950
+{
Matej Habrnal fa1950
+    if (dir_is_in_dump_location(name) != expected)
Matej Habrnal fa1950
+    {
Matej Habrnal fa1950
+        fprintf(stderr, "Bad: %s", name);
Matej Habrnal fa1950
+        abort();
Matej Habrnal fa1950
+    }
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    free(name);
Matej Habrnal fa1950
+}
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+int main(void)
Matej Habrnal fa1950
+{
Matej Habrnal fa1950
+    g_verbose = 3;
Matej Habrnal fa1950
+    load_abrt_conf();
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    g_verbose = 3;
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    char *name;
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    assert(dir_is_in_dump_location("/") == false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s..evil", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s///", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/.", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s///.", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/./", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/.///", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/..", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s///..", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/../", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/..///", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/good/../../../evil", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, false);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/good..still", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, true);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/good.new", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, true);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/.meta", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, true);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    asprintf(&name, "%s/..data", g_settings_dump_location);
Matej Habrnal fa1950
+    test(name, true);
Matej Habrnal fa1950
+
Matej Habrnal fa1950
+    return 0;
Matej Habrnal fa1950
+}
Matej Habrnal fa1950
+]])
Matej Habrnal fa1950
diff --git a/tests/testsuite.at b/tests/testsuite.at
Matej Habrnal fa1950
index b8f363d..765de2a 100644
Matej Habrnal fa1950
--- a/tests/testsuite.at
Matej Habrnal fa1950
+++ b/tests/testsuite.at
Matej Habrnal fa1950
@@ -4,3 +4,4 @@
Matej Habrnal fa1950
 m4_include([koops-parser.at])
Matej Habrnal fa1950
 m4_include([pyhook.at])
Matej Habrnal fa1950
 m4_include([ignored_problems.at])
Matej Habrnal fa1950
+m4_include([hooklib.at])
Matej Habrnal fa1950
-- 
Matej Habrnal fa1950
2.1.0
Matej Habrnal fa1950