3a204fb
From ad27acbb2104d4172a33dd82ef9d94c8f5dba617 Mon Sep 17 00:00:00 2001
3a204fb
From: Jakub Filak <jfilak@redhat.com>
3a204fb
Date: Thu, 28 May 2015 11:47:39 +0200
3a204fb
Subject: [PATCH] dd: add owner to meta-data
3a204fb
3a204fb
Use 'nobody' user to mark dump directories which have no owner and are
3a204fb
publicly readable and chownable. The 'nobody' should not be used
3a204fb
directly but through the function set_no_owner() which is a substitution
3a204fb
for adding S_IROTH (man 2 open) to dump dir mode.
3a204fb
3a204fb
Use meta-data to get the owner only if the dump directory is owned by
3a204fb
super-user, because it is the only state when we can trust that value.
3a204fb
3a204fb
Related: #354
3a204fb
3a204fb
Signed-off-by: Jakub Filak <jfilak@redhat.com>
3a204fb
---
3a204fb
 src/include/dump_dir.h |  21 ++++++
3a204fb
 src/lib/dump_dir.c     | 195 ++++++++++++++++++++++++++++++++++++++++++++++++-
3a204fb
 tests/dump_dir.at      | 110 ++++++++++++++++++++++++++++
3a204fb
 3 files changed, 322 insertions(+), 4 deletions(-)
3a204fb
3a204fb
diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
3a204fb
index f33eb99..b37b262 100644
3a204fb
--- a/src/include/dump_dir.h
3a204fb
+++ b/src/include/dump_dir.h
3a204fb
@@ -141,6 +141,26 @@ int dd_rename(struct dump_dir *dd, const char *new_path);
3a204fb
  */
3a204fb
 int dd_chown(struct dump_dir *dd, uid_t new_uid);
3a204fb
 
3a204fb
+/* Sets a new owner (does NOT chown the directory)
3a204fb
+ *
3a204fb
+ * Does not validate the passed uid.
3a204fb
+ * The given dump_dir must be opened for writing.
3a204fb
+ */
3a204fb
+int dd_set_owner(struct dump_dir *dd, uid_t owner);
3a204fb
+
3a204fb
+/* Makes the dump directory owned by nobody.
3a204fb
+ *
3a204fb
+ * The directory will be accessible for all users.
3a204fb
+ * The given dump_dir must be opened for writing.
3a204fb
+ */
3a204fb
+int dd_set_no_owner(struct dump_dir *dd);
3a204fb
+
3a204fb
+/* Gets the owner
3a204fb
+ *
3a204fb
+ * If meta-data misses owner, returns fs owner.
3a204fb
+ * Can be used with DD_OPEN_FD_ONLY.
3a204fb
+ */
3a204fb
+uid_t dd_get_owner(struct dump_dir *dd);
3a204fb
 
3a204fb
 /* reported_to handling */
3a204fb
 #define add_reported_to_data libreport_add_reported_to_data
3a204fb
@@ -187,6 +207,7 @@ int dd_accessible_by_uid(struct dump_dir *dd, uid_t uid);
3a204fb
 enum {
3a204fb
     DD_STAT_ACCESSIBLE_BY_UID = 1,
3a204fb
     DD_STAT_OWNED_BY_UID = DD_STAT_ACCESSIBLE_BY_UID << 1,
3a204fb
+    DD_STAT_NO_OWNER = DD_STAT_OWNED_BY_UID << 1,
3a204fb
 };
3a204fb
 
3a204fb
 /* Gets information about a dump directory for particular uid.
3a204fb
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
3a204fb
index 9ba2352..2cd14bb 100644
3a204fb
--- a/src/lib/dump_dir.c
3a204fb
+++ b/src/lib/dump_dir.c
3a204fb
@@ -88,6 +88,7 @@
3a204fb
 // parent dump directory. It is not a fatal error, if the meta-data directory
3a204fb
 // does not exist (backward compatibility).
3a204fb
 #define META_DATA_DIR_NAME             ".libreport"
3a204fb
+#define META_DATA_FILE_OWNER           "owner"
3a204fb
 
3a204fb
 enum {
3a204fb
     /* Try to create meta-data dir if it does not exist */
3a204fb
@@ -106,6 +107,8 @@ static char *load_text_file(const char *path, unsigned flags);
3a204fb
 static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
3a204fb
 static void copy_file_from_chroot(struct dump_dir* dd, const char *name,
3a204fb
         const char *chroot_dir, const char *file_path);
3a204fb
+static bool save_binary_file_at(int dir_fd, const char *name, const char* data,
3a204fb
+        unsigned size, uid_t uid, gid_t gid, mode_t mode);
3a204fb
 
3a204fb
 static bool isdigit_str(const char *str)
3a204fb
 {
3a204fb
@@ -136,6 +139,22 @@ static bool exist_file_dir_at(int dir_fd, const char *name)
3a204fb
 #define dd_validate_element_name(name) \
3a204fb
     (str_is_correct_filename(name) && (strcmp(META_DATA_DIR_NAME, name) != 0))
3a204fb
 
3a204fb
+/* nobody user should not own any file */
3a204fb
+static int get_no_owner_uid(uid_t *uid)
3a204fb
+{
3a204fb
+    struct passwd *pw = getpwnam("nobody");
3a204fb
+    if (pw == NULL)
3a204fb
+    {
3a204fb
+        perror_msg("can't get nobody's uid");
3a204fb
+        if (errno == 0)
3a204fb
+            return -ENOENT;
3a204fb
+        return -errno;
3a204fb
+    }
3a204fb
+
3a204fb
+    *uid = pw->pw_uid;
3a204fb
+    return 0;
3a204fb
+}
3a204fb
+
3a204fb
 /* Opens the file in the three following steps:
3a204fb
  * 1. open the file with O_PATH (get a file descriptor for operations with
3a204fb
  *    inode) and O_NOFOLLOW (do not dereference symbolick links)
3a204fb
@@ -611,6 +630,113 @@ static int dd_get_meta_data_dir_fd(struct dump_dir *dd, int flags)
3a204fb
     return dd->dd_md_fd;
3a204fb
 }
3a204fb
 
3a204fb
+/* Tries to safely overwrite the existing file.
3a204fb
+ *
3a204fb
+ * The functions writes the new value to a temporary file and if the temporary
3a204fb
+ * file is successfully created, then moves the tmp file to the old file name.
3a204fb
+ *
3a204fb
+ * If the meta-data directory does not exist, the function will try to create
3a204fb
+ * it.
3a204fb
+ */
3a204fb
+static int dd_meta_data_save_text(struct dump_dir *dd, const char *name, const char *data)
3a204fb
+{
3a204fb
+    if (!dd->locked)
3a204fb
+        error_msg_and_die("dump_dir is not opened"); /* bug */
3a204fb
+
3a204fb
+    if (!str_is_correct_filename(name))
3a204fb
+        error_msg_and_die("Cannot save meta-data. '%s' is not a valid file name", name);
3a204fb
+
3a204fb
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, DD_MD_GET_CREATE);
3a204fb
+    if (dd_md_fd < 0)
3a204fb
+    {
3a204fb
+        error_msg("Can't save meta-data: '%s'", name);
3a204fb
+        return dd_md_fd;
3a204fb
+    }
3a204fb
+
3a204fb
+    char *tmp_name = xasprintf("~%s.tmp", name);
3a204fb
+
3a204fb
+    int ret = -1;
3a204fb
+    if (!save_binary_file_at(dd_md_fd, tmp_name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode))
3a204fb
+        goto finito;
3a204fb
+
3a204fb
+    /* man 2 rename
3a204fb
+     *
3a204fb
+     * If newpath  already exists it will be atomically replaced (subject to a
3a204fb
+     * few conditions; see ERRORS below), so that there is no point at which
3a204fb
+     * another process attempting to access newpath will find it missing.
3a204fb
+     */
3a204fb
+    if (renameat(dd_md_fd, tmp_name, dd_md_fd, name))
3a204fb
+    {
3a204fb
+        ret = -errno;
3a204fb
+        perror_msg("Failed to move temporary file '%s' to '%s'", tmp_name, name);
3a204fb
+        goto finito;
3a204fb
+    }
3a204fb
+
3a204fb
+    ret = 0;
3a204fb
+
3a204fb
+finito:
3a204fb
+    free(tmp_name);
3a204fb
+    return ret;
3a204fb
+}
3a204fb
+
3a204fb
+int dd_set_owner(struct dump_dir *dd, uid_t owner)
3a204fb
+{
3a204fb
+    /* I was tempted to use the keyword static, but we should have reentracy
3a204fb
+     * always on mind. Who knows! */
3a204fb
+    char long_str[sizeof(long) * 3 + 2];
3a204fb
+
3a204fb
+    if (owner == (uid_t)-1)
3a204fb
+    {
3a204fb
+        owner = getuid();
3a204fb
+        if (owner < 0)
3a204fb
+            perror_msg_and_die("%s: getuid", __func__);
3a204fb
+    }
3a204fb
+
3a204fb
+    snprintf(long_str, sizeof(long_str), "%li", (long)owner);
3a204fb
+    const int ret = dd_meta_data_save_text(dd, META_DATA_FILE_OWNER, long_str);
3a204fb
+    if (ret < 0)
3a204fb
+        error_msg("The dump dir owner wasn't set to '%s'", long_str);
3a204fb
+    return ret;
3a204fb
+}
3a204fb
+
3a204fb
+int dd_set_no_owner(struct dump_dir *dd)
3a204fb
+{
3a204fb
+    uid_t no_owner_uid = (uid_t)-1;
3a204fb
+    int ret = get_no_owner_uid(&no_owner_uid);
3a204fb
+    if (ret < 0)
3a204fb
+        return ret;
3a204fb
+
3a204fb
+    return dd_set_owner(dd, no_owner_uid);
3a204fb
+}
3a204fb
+
3a204fb
+uid_t dd_get_owner(struct dump_dir *dd)
3a204fb
+{
3a204fb
+    static const long long MAX_UID_T = (1ULL << (sizeof(uid_t)*8 - 1)) - 1;
3a204fb
+
3a204fb
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
3a204fb
+    if (dd_md_fd < 0)
3a204fb
+    {
3a204fb
+        log_info("No meta-data, using fs owner.");
3a204fb
+        return dd->dd_uid;
3a204fb
+    }
3a204fb
+
3a204fb
+    long long owner = -1;
3a204fb
+
3a204fb
+    int ret = read_number_from_file_at(dd_md_fd, META_DATA_FILE_OWNER, "UID",
3a204fb
+                                       sizeof(uid_t), MAX_UID_T, -1, &owner);
3a204fb
+
3a204fb
+    if (ret < 0)
3a204fb
+    {
3a204fb
+        if (ret != -ENOENT)
3a204fb
+            return ret;
3a204fb
+
3a204fb
+        log_info("No meta-data 'owner', using fs owner.");
3a204fb
+        return dd->dd_uid;
3a204fb
+    }
3a204fb
+
3a204fb
+    return (uid_t)owner;
3a204fb
+}
3a204fb
+
3a204fb
 /* A helper function useful for traversing directories.
3a204fb
  *
3a204fb
  * DIR* d opendir(dir_fd); ... closedir(d); closes also dir_fd but we want to
3a204fb
@@ -1026,6 +1152,12 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
3a204fb
         goto fail;
3a204fb
     }
3a204fb
 
3a204fb
+    if (dd_set_owner(dd, dd->dd_uid) < 0)
3a204fb
+    {
3a204fb
+        log_debug("Failed to initialized 'owner'");
3a204fb
+        goto fail;
3a204fb
+    }
3a204fb
+
3a204fb
     if (uid != (uid_t)-1L)
3a204fb
     {
3a204fb
         dd->dd_uid = 0;
3a204fb
@@ -1087,6 +1219,11 @@ int dd_reset_ownership(struct dump_dir *dd)
3a204fb
     if (dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid) != 0)
3a204fb
         error_msg("Failed to reset ownership of meta-data");
3a204fb
 
3a204fb
+    /* We ignore failures above, so we will ignore failures here too.
3a204fb
+     * The meta-data owner already exist (created by dd_create_skeleton).
3a204fb
+     */
3a204fb
+    dd_set_owner(dd, dd->dd_uid);
3a204fb
+
3a204fb
     return r;
3a204fb
 }
3a204fb
 
3a204fb
@@ -1122,8 +1259,14 @@ void dd_create_basic_files(struct dump_dir *dd, uid_t uid, const char *chroot_di
3a204fb
     free(time_str);
3a204fb
 
3a204fb
     /* it doesn't make sense to create the uid file if uid == -1 */
3a204fb
+    /* and 'owner' is set since dd_create_skeleton */
3a204fb
     if (uid != (uid_t)-1L)
3a204fb
     {
3a204fb
+        /* Failure is not a problem here, because we still have the fs
3a204fb
+         * attributes and there is only a little chance that the old value
3a204fb
+         * gets lost. */
3a204fb
+        dd_set_owner(dd, uid);
3a204fb
+
3a204fb
         snprintf(long_str, sizeof(long_str), "%li", (long)uid);
3a204fb
         dd_save_text(dd, FILENAME_UID, long_str);
3a204fb
     }
3a204fb
@@ -1436,6 +1579,9 @@ next:
3a204fb
         dd->dd_gid = groups_gid;
3a204fb
     }
3a204fb
 
3a204fb
+    if (chown_res == 0)
3a204fb
+        chown_res = dd_set_owner(dd, (long)dd->dd_uid);
3a204fb
+
3a204fb
     return chown_res;
3a204fb
 }
3a204fb
 
3a204fb
@@ -1812,7 +1958,45 @@ static bool uid_in_group(uid_t uid, gid_t gid)
3a204fb
 int dd_stat_for_uid(struct dump_dir *dd, uid_t uid)
3a204fb
 {
3a204fb
     int ddstat = 0;
3a204fb
-    if (uid == 0 || (dd->mode & S_IROTH))
3a204fb
+
3a204fb
+    if (uid == 0)
3a204fb
+    {
3a204fb
+        log_debug("directory accessible by super-user");
3a204fb
+        ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
+    }
3a204fb
+
3a204fb
+#define DD_OWNER_FLAGS (DD_STAT_ACCESSIBLE_BY_UID | DD_STAT_OWNED_BY_UID)
3a204fb
+    if (dd->dd_uid == 0)
3a204fb
+    {
3a204fb
+        log_debug("directory owned by super-user: checking meta-data");
3a204fb
+
3a204fb
+        const uid_t owner = dd_get_owner(dd);
3a204fb
+
3a204fb
+        if (owner < 0)
3a204fb
+            goto fsattributes;
3a204fb
+
3a204fb
+        if (owner == uid)
3a204fb
+        {
3a204fb
+            log_debug("meta-data: %ld uid owns directory", (long)uid);
3a204fb
+            ddstat |= DD_OWNER_FLAGS;
3a204fb
+            goto finito;
3a204fb
+        }
3a204fb
+
3a204fb
+        uid_t no_owner_uid = (uid_t)-1;
3a204fb
+        int ret = get_no_owner_uid(&no_owner_uid);
3a204fb
+        if (   ret >= 0
3a204fb
+            && owner == no_owner_uid)
3a204fb
+        {
3a204fb
+            log_debug("meta-data: directory is accessible by %ld uid", (long)uid);
3a204fb
+            ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
+            ddstat |= DD_STAT_NO_OWNER;
3a204fb
+        }
3a204fb
+
3a204fb
+        goto finito;
3a204fb
+    }
3a204fb
+
3a204fb
+fsattributes:
3a204fb
+    if (dd->mode & S_IROTH)
3a204fb
     {
3a204fb
         log_debug("directory is accessible by %ld uid", (long)uid);
3a204fb
         ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
@@ -1824,11 +2008,14 @@ int dd_stat_for_uid(struct dump_dir *dd, uid_t uid)
3a204fb
     if (uid_in_group(uid, dd->dd_gid))
3a204fb
 #endif
3a204fb
     {
3a204fb
-        log_debug("%ld uid owns directory", (long)uid);
3a204fb
-        ddstat |= DD_STAT_ACCESSIBLE_BY_UID;
3a204fb
-        ddstat |= DD_STAT_OWNED_BY_UID;
3a204fb
+        log_debug("fs attributes: %ld uid owns directory", (long)uid);
3a204fb
+        ddstat |= DD_OWNER_FLAGS;
3a204fb
     }
3a204fb
 
3a204fb
+#undef DD_OWNER_FLAGS
3a204fb
+
3a204fb
+finito:
3a204fb
+    log_debug("UID=%d, %s: %o", uid, dd->dd_dirname, ddstat);
3a204fb
     return ddstat;
3a204fb
 }
3a204fb
 
3a204fb
diff --git a/tests/dump_dir.at b/tests/dump_dir.at
3a204fb
index 4bf479e..cc0148c 100644
3a204fb
--- a/tests/dump_dir.at
3a204fb
+++ b/tests/dump_dir.at
3a204fb
@@ -203,6 +203,11 @@ int main(int argc, char **argv)
3a204fb
     assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0);
3a204fb
     assert(md_st.st_ino = path_md_st.st_ino);
3a204fb
 
3a204fb
+    struct stat owner_md_st;
3a204fb
+    assert(fstatat(dd->dd_md_fd, "owner", &owner_md_st, 0) == 0);
3a204fb
+    assert((dd_st.st_mode & 0666) == (owner_md_st.st_mode & 0666));
3a204fb
+    assert(geteuid() == dd_get_owner(dd));
3a204fb
+
3a204fb
     dd_create_basic_files(dd, (uid_t)-1, NULL);
3a204fb
     dd_save_text(dd, FILENAME_TYPE, "attest");
3a204fb
 
3a204fb
@@ -329,6 +334,111 @@ int main(int argc, char **argv)
3a204fb
 }
3a204fb
 ]])
3a204fb
 
3a204fb
+## -------- ##
3a204fb
+## dd_owner ##
3a204fb
+## -------- ##
3a204fb
+
3a204fb
+AT_TESTFUN([dd_owner],
3a204fb
+[[
3a204fb
+#include "internal_libreport.h"
3a204fb
+#include <errno.h>
3a204fb
+#include <assert.h>
3a204fb
+
3a204fb
+int main(int argc, char **argv)
3a204fb
+{
3a204fb
+    g_verbose = 3;
3a204fb
+
3a204fb
+    char template[] = "/tmp/XXXXXX/dump_dir";
3a204fb
+
3a204fb
+    char *last_slash = strrchr(template, '/');
3a204fb
+    *last_slash = '\0';
3a204fb
+
3a204fb
+    if (mkdtemp(template) == NULL) {
3a204fb
+        perror("mkdtemp()");
3a204fb
+        return EXIT_FAILURE;
3a204fb
+    }
3a204fb
+
3a204fb
+    *last_slash = '/';
3a204fb
+
3a204fb
+    printf("Dump dir path: %s\n", template);
3a204fb
+
3a204fb
+    {
3a204fb
+        fprintf(stderr, "TEST === default owner\n");
3a204fb
+        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+        assert(dd != NULL);
3a204fb
+
3a204fb
+        assert(geteuid() == dd_get_owner(dd));
3a204fb
+
3a204fb
+        assert(dd_delete(dd) == 0);
3a204fb
+    }
3a204fb
+
3a204fb
+    {
3a204fb
+        fprintf(stderr, "TEST === create basic files w/o UID\n");
3a204fb
+        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+        assert(dd != NULL);
3a204fb
+
3a204fb
+        dd_create_basic_files(dd, (uid_t)-1, NULL);
3a204fb
+        assert(geteuid() == dd_get_owner(dd));
3a204fb
+
3a204fb
+        assert(dd_delete(dd) == 0);
3a204fb
+    }
3a204fb
+
3a204fb
+    {
3a204fb
+        fprintf(stderr, "TEST === create basic files with UID\n");
3a204fb
+        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+        assert(dd != NULL);
3a204fb
+
3a204fb
+        dd_create_basic_files(dd, (geteuid() + 1), NULL);
3a204fb
+        assert((geteuid() + 1) == dd_get_owner(dd));
3a204fb
+
3a204fb
+        assert(dd_delete(dd) == 0);
3a204fb
+    }
3a204fb
+
3a204fb
+    {
3a204fb
+        fprintf(stderr, "TEST === set no owner\n");
3a204fb
+        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+        assert(dd != NULL);
3a204fb
+
3a204fb
+        dd_set_no_owner(dd);
3a204fb
+
3a204fb
+        struct passwd *nobody_pw= getpwnam("nobody");
3a204fb
+        assert(nobody_pw != NULL);
3a204fb
+        assert(nobody_pw->pw_uid == dd_get_owner(dd));
3a204fb
+
3a204fb
+        assert(dd_delete(dd) == 0);
3a204fb
+    }
3a204fb
+
3a204fb
+    {
3a204fb
+        fprintf(stderr, "TEST === set artibrary owner\n");
3a204fb
+        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+        assert(dd != NULL);
3a204fb
+
3a204fb
+        dd_set_owner(dd, (geteuid() + 2));
3a204fb
+        assert((geteuid() + 2) == dd_get_owner(dd));
3a204fb
+
3a204fb
+        assert(dd_delete(dd) == 0);
3a204fb
+    }
3a204fb
+
3a204fb
+    {
3a204fb
+        fprintf(stderr, "TEST === chown no owner\n");
3a204fb
+        struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
3a204fb
+        assert(dd != NULL);
3a204fb
+
3a204fb
+        dd_set_no_owner(dd);
3a204fb
+        assert(geteuid() != dd_get_owner(dd));
3a204fb
+
3a204fb
+        dd_chown(dd, geteuid());
3a204fb
+        assert(geteuid() == dd_get_owner(dd));
3a204fb
+
3a204fb
+        assert(dd_delete(dd) == 0);
3a204fb
+    }
3a204fb
+    *last_slash = '\0';
3a204fb
+    assert(rmdir(template) == 0);
3a204fb
+
3a204fb
+    return EXIT_SUCCESS;
3a204fb
+}
3a204fb
+]])
3a204fb
+
3a204fb
 ## -------------- ##
3a204fb
 ## recursive_lock ##
3a204fb
 ## -------------- ##
3a204fb
-- 
3a204fb
2.1.0
3a204fb