Blob Blame History Raw
From fdc64c5aafe435e34c4369ae8504d98ff57d7350 Mon Sep 17 00:00:00 2001
From: Jakub Filak <jfilak@redhat.com>
Date: Thu, 28 May 2015 12:32:01 +0200
Subject: [PATCH] dd: add support for meta-data

Store the meta-data as plain text files in the dump directory's
sub-directory called '.libreport'.

I chose this name to allow other tools (which do not exist yet) to create
their own directories and files.

Related: #354

Signed-off-by: Jakub Filak <jfilak@redhat.com>
---
 src/include/dump_dir.h |   4 +
 src/lib/dump_dir.c     | 511 ++++++++++++++++++++++++++++++++++++++++++-------
 tests/dump_dir.at      | 171 +++++++++++++++++
 3 files changed, 618 insertions(+), 68 deletions(-)

diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
index b613e0b..f33eb99 100644
--- a/src/include/dump_dir.h
+++ b/src/include/dump_dir.h
@@ -75,6 +75,10 @@ struct dump_dir {
      */
     int owns_lock;
     int dd_fd;
+    /* Never use this member directly, it is intialized on demand in
+     * dd_get_meta_data_dir_fd()
+     */
+    int dd_md_fd;
 };
 
 void dd_close(struct dump_dir *dd);
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
index 4dcfa66..aed2de0 100644
--- a/src/lib/dump_dir.c
+++ b/src/lib/dump_dir.c
@@ -83,6 +83,24 @@
 #define RMDIR_FAIL_USLEEP              (10*1000)
 #define RMDIR_FAIL_COUNT                     50
 
+// A sub-directory of a dump directory where the meta-data such as owner are
+// stored. The meta-data directory must have same owner, group and mode as its
+// parent dump directory. It is not a fatal error, if the meta-data directory
+// does not exist (backward compatibility).
+#define META_DATA_DIR_NAME             ".libreport"
+
+enum {
+    /* Try to create meta-data dir if it does not exist */
+    DD_MD_GET_CREATE = 1 << 0,
+};
+
+// a little trick to copy read bits from file mode to exec bit of dir mode
+// * mode of dump directory is in the form of 640 (no X) because we create
+//   files more often then we play with directories
+// * so if we want to get real mode of the directory we have to copy the read
+//   bits
+#define DD_MODE_TO_DIR_MODE(mode) ((mode) | (((mode) & 0444) >> 2))
+
 
 static char *load_text_file(const char *path, unsigned flags);
 static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
@@ -112,6 +130,12 @@ static bool exist_file_dir_at(int dir_fd, const char *name)
     return false;
 }
 
+/* A valid dump dir element name is correct filename and is not a name of any
+ * internal file or directory.
+ */
+#define dd_validate_element_name(name) \
+    (str_is_correct_filename(name) && (strcmp(META_DATA_DIR_NAME, name) != 0))
+
 /* Opens the file in the three following steps:
  * 1. open the file with O_PATH (get a file descriptor for operations with
  *    inode) and O_NOFOLLOW (do not dereference symbolick links)
@@ -397,18 +421,28 @@ static inline struct dump_dir *dd_init(void)
     struct dump_dir* dd = (struct dump_dir*)xzalloc(sizeof(struct dump_dir));
     dd->dd_time = -1;
     dd->dd_fd = -1;
+    dd->dd_md_fd = -1;
     return dd;
 }
 
 int dd_exist(const struct dump_dir *dd, const char *name)
 {
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
 
     const int ret = exist_file_dir_at(dd->dd_fd, name);
     return ret;
 }
 
+static void dd_close_meta_data_dir(struct dump_dir *dd)
+{
+    if (dd->dd_md_fd < 0)
+        return;
+
+    close(dd->dd_md_fd);
+    dd->dd_md_fd = -1;
+}
+
 void dd_close(struct dump_dir *dd)
 {
     if (!dd)
@@ -419,6 +453,8 @@ void dd_close(struct dump_dir *dd)
     if (dd->dd_fd >= 0)
         close(dd->dd_fd);
 
+    dd_close_meta_data_dir(dd);
+
     if (dd->next_dir)
     {
         closedir(dd->next_dir);
@@ -430,6 +466,255 @@ void dd_close(struct dump_dir *dd)
     free(dd);
 }
 
+static int dd_create_subdir(int dd_fd, const char *dirname, uid_t dd_uid, gid_t dd_gid, mode_t dd_mode)
+{
+    if (mkdirat(dd_fd, dirname, dd_mode) < 0)
+    {
+        perror_msg("Can't create directory '%s'", dirname);
+        return -1;
+    }
+
+    int dd_md_fd = openat(dd_fd, dirname, O_DIRECTORY | O_NOFOLLOW);
+    if (dd_md_fd < 0)
+    {
+        perror_msg("Can't open newly created directory '%s'", dirname);
+        goto fail_open;
+    }
+
+    if (dd_uid != (uid_t)-1)
+    {
+        if (fchown(dd_md_fd, dd_uid, dd_gid) != 0)
+        {
+            perror_msg("Can't change owner and group of '%s'", dirname);
+            goto fail_modify;
+        }
+    }
+
+    /* mkdir's mode (above) can be affected by umask, fix it */
+    if (fchmod(dd_md_fd, dd_mode) == -1)
+    {
+        perror_msg("Can't change mode of '%s'", dirname);
+        goto fail_modify;
+    }
+
+    return dd_md_fd;
+fail_modify:
+    close(dd_md_fd);
+fail_open:
+    if (unlinkat(dd_fd, dirname, AT_REMOVEDIR) < 0)
+        perror_msg("Fialed to unlink '%s' while cleaning up after failure", dirname);
+    return -1;
+}
+
+/* Opens the meta-data directory, checks its file system attributes and returns
+ * its file descriptor.
+ *
+ * The meta-data directory must have the same file system attributes as the
+ * parent dump directory in order to avoid unexpected situations and detects
+ * program errors (it is an error to modify bits of the dump directory and
+ * forgot to update the meta-data directory).
+ *
+ * Keep on mind that the old dump directories might miss the meta-data directory
+ * so the return value -ENOENT does not necessarily need to be fatal.
+ */
+static int dd_open_meta_data_dir(struct dump_dir *dd)
+{
+    int md_dir_fd = openat(dd->dd_fd, META_DATA_DIR_NAME, O_DIRECTORY | O_NOFOLLOW);
+    if (md_dir_fd < 0)
+    {
+        md_dir_fd = -errno;
+
+        /* ENOENT is not critical */
+        if (errno != ENOENT)
+            log_warning("Can't open meta-data '"META_DATA_DIR_NAME"'");
+        else
+            log_info("The dump dir doesn't contain '"META_DATA_DIR_NAME"'");
+
+        goto finito;
+    }
+
+    struct stat md_sb;
+    if (fstat(md_dir_fd, &md_sb) < 0)
+    {
+        log_debug("Can't stat '"META_DATA_DIR_NAME"'");
+        goto fail;
+    }
+
+    /* Test only permission bits, ignore SUID, SGID, etc. */
+    const mode_t md_mode = md_sb.st_mode & 0777;
+    const mode_t dd_mode = DD_MODE_TO_DIR_MODE(dd->mode);
+
+    if (   md_sb.st_uid != dd->dd_uid
+        || md_sb.st_gid != dd->dd_gid
+        || md_mode != dd_mode)
+    {
+        log_debug("'"META_DATA_DIR_NAME"' has different attributes than the dump dir, '%d'='%d', '%d'='%d', %o = %o",
+                        md_sb.st_uid, dd->dd_uid, md_sb.st_gid, dd->dd_gid, md_mode, dd_mode);
+        goto fail;
+    }
+
+finito:
+    return md_dir_fd;
+
+fail:
+    close(md_dir_fd);
+
+    return -EINVAL;
+}
+
+/* Returns a file descriptor to the meta-data directory. Can be configured to
+ * create the directory if it does not exist.
+ *
+ * This function enables lazy initialization of the meta-data directory.
+ */
+static int dd_get_meta_data_dir_fd(struct dump_dir *dd, int flags)
+{
+    if (dd->dd_md_fd < 0)
+    {
+        dd->dd_md_fd = dd_open_meta_data_dir(dd);
+
+        if (    dd->dd_md_fd == -ENOENT
+             && (flags & DD_MD_GET_CREATE))
+        {
+            dd->dd_md_fd = dd_create_subdir(dd->dd_fd,
+                                            META_DATA_DIR_NAME,
+                                            dd->dd_uid,
+                                            dd->dd_gid,
+                                            DD_MODE_TO_DIR_MODE(dd->mode));
+        }
+    }
+
+    return dd->dd_md_fd;
+}
+
+/* A helper function useful for traversing directories.
+ *
+ * DIR* d opendir(dir_fd); ... closedir(d); closes also dir_fd but we want to
+ * keep it opened.
+ */
+static int fdreopen(int dir_fd, DIR **d)
+{
+    int opendir_fd = dup(dir_fd);
+    if (opendir_fd < 0)
+    {
+        perror_msg("dup(dir_fd)");
+        return -EBADFD;
+    }
+
+    lseek(opendir_fd, SEEK_SET, 0);
+    *d = fdopendir(opendir_fd);
+    if (!*d)
+    {
+        int ret = -errno;
+        close(opendir_fd);
+        perror_msg("fdopendir(dir_fd)");
+        return ret;
+    }
+
+    /* 'opendir_fd' will be closed with 'd' */
+    return 0;
+}
+
+/* A macro for going through the entries of a directory referenced as a file
+ * descriptor.
+ *
+ * Usage:
+ *
+ * FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd)
+ * {
+ *      printf("Short name '%s'",    dent->d_name);
+ *      printf("File descriptor %d", fd);
+ * }
+ * FOREACH_REGULAR_FILE_AS_FD_AT_END
+ */
+
+#define FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dir_fd) \
+    DIR *d; \
+    struct dirent *dent; \
+    if (fdreopen(dir_fd, &d) < 0) return -1; \
+    while ((dent = readdir(d)) != NULL) \
+    { \
+        if (dot_or_dotdot(dent->d_name)) continue; \
+        int fd = secure_openat_read(dirfd(d), dent->d_name); \
+        if (fd >= 0)
+
+#define FOREACH_REGULAR_FILE_AS_FD_AT_END \
+        close(fd); \
+    } \
+    closedir(d);
+
+
+/* Sets attributes of the meta-data directory and its contents to the same
+ * attributes of the parent dump directory.
+ */
+static int dd_sanitize_mode_meta_data(struct dump_dir *dd)
+{
+    if (!dd->locked)
+        error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */
+
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
+    if (dd_md_fd < 0)
+        return 0;
+
+    int res = fchmod(dd_md_fd, DD_MODE_TO_DIR_MODE(dd->mode));
+    if (res < 0)
+    {
+        perror_msg("Failed to chmod meta-data sub-dir");
+        return res;
+    }
+
+    FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
+    {
+        log_debug("chmoding %s", dent->d_name);
+
+        res = fchmod(fd, dd->mode);
+        if (res)
+        {
+            perror_msg("fchmod('%s')", dent->d_name);
+            break;
+        }
+    }
+    FOREACH_REGULAR_FILE_AS_FD_AT_END
+
+    return 0;
+}
+
+/* Sets owner and group of the meta-data directory and its contents to the same
+ * attributes of the parent dump directory.
+ */
+
+static int dd_chown_meta_data(struct dump_dir *dd, uid_t uid, gid_t gid)
+{
+    if (!dd->locked)
+        error_msg_and_die("%s: dump_dir is not opened", __func__); /* bug */
+
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
+    if (dd_md_fd < 0)
+        return 0;
+
+    int res = fchown(dd_md_fd, uid, gid);
+    if (res < 0)
+    {
+        perror_msg("Failed to chown meta-data sub-dir");
+        return res;
+    }
+
+    FOREACH_REGULAR_FILE_AS_FD_AT_BEGIN(dd_md_fd)
+    {
+        log_debug("%s: chowning %s", __func__, dent->d_name);
+
+        res = fchown(fd, uid, gid);
+        if (res)
+        {
+            perror_msg("fchown('%s')", dent->d_name);
+            break;
+        }
+    }
+    FOREACH_REGULAR_FILE_AS_FD_AT_END
+
+    return res;
+}
+
 static char* rm_trailing_slashes(const char *dir)
 {
     unsigned len = strlen(dir);
@@ -455,45 +740,41 @@ static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int fla
         /* & 0666 should remove the executable bit */
         dd->mode = (stat_buf.st_mode & 0666);
 
-        dd->dd_uid = (uid_t)-1L;
-        dd->dd_gid = (gid_t)-1L;
+        /* We want to have dd_uid and dd_gid always initialized. But we have to
+         * initialize it in the way which does not prevent non-privileged user
+         * from saving data in their dump directories.
+         *
+         * Non-privileged users are not allowed to change the group to
+         * 'abrt' so we have to use their GID.
+         *
+         * If the caller is super-user, we have to use dd's fs owner and fs
+         * group, because he can do everything and the data must be readable by
+         * the real owner.
+         *
+         * We always use fs uid, because non-privileged users must own the
+         * directory and super-user must use fs owner.
+         */
+        dd->dd_uid = stat_buf.st_uid;
+
+        /* We use fs group only if the caller is super-user, because we want to
+         * make sure non-privileged users can modify elements (libreport call
+         * chown(dd_uid, dd_gid) after modifying an element) and the modified
+         * elements do not have super-user's group.
+         */
+        dd->dd_gid = getegid();
         if (geteuid() == 0)
-        {
-            /* In case caller would want to create more files, he'll need uid:gid */
-            if (fstat(dd->dd_fd, &stat_buf) != 0)
-            {
-                error_msg("Can't stat '%s'", dd->dd_dirname);
-                dd_close(dd);
-                return NULL;
-            }
-            dd->dd_uid = stat_buf.st_uid;
             dd->dd_gid = stat_buf.st_gid;
-        }
 
         if ((flags & DD_OPEN_FD_ONLY))
+        {
+            dd->dd_md_fd = dd_open_meta_data_dir(dd);
             return dd;
+        }
     }
 
     errno = 0;
     if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0)
     {
-        if ((flags & DD_OPEN_READONLY) && errno == EACCES)
-        {
-            /* Directory is not writable. If it seems to be readable,
-             * return "read only" dd, not NULL
-             *
-             * Does the directory have 'r' flag?
-             */
-            if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) == 0)
-            {
-                if(dd_check(dd) != NULL)
-                {
-                    dd_close(dd);
-                    dd = NULL;
-                }
-                return dd;
-            }
-        }
         if (errno == EISDIR)
         {
             /* EISDIR: dd_lock can lock the dir, but it sees no time file there,
@@ -504,26 +785,56 @@ static struct dump_dir *dd_do_open(struct dump_dir *dd, const char *dir, int fla
              * defaults to "."!
              */
             error_msg("'%s' is not a problem directory", dd->dd_dirname);
+            goto fail_with_close;
         }
-        else
+
+        if (!(flags & DD_OPEN_READONLY))
         {
- cant_access:
-            if (errno == ENOENT || errno == ENOTDIR)
-            {
-                if (!(flags & DD_FAIL_QUIETLY_ENOENT))
-                    error_msg("'%s' does not exist", dd->dd_dirname);
-            }
-            else
-            {
-                if (!(flags & DD_FAIL_QUIETLY_EACCES))
-                    perror_msg("Can't access '%s'", dd->dd_dirname);
-            }
+            log_debug("'%s' can't be opened for writing", dd->dd_dirname);
+            goto fail_with_close;
         }
-        dd_close(dd);
-        return NULL;
+
+        if (errno != EACCES)
+        {
+            VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
+            goto fail_with_close;
+        }
+
+        /* Directory is not writable. If it seems to be readable,
+         * return "read only" dd, not NULL
+         *
+         * Does the directory have 'r' flag?
+         */
+        if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) != 0)
+        {
+            VERB3 perror_msg("failed to lock dump directory '%s'", dd->dd_dirname);
+            goto fail_with_close;
+        }
+
+        /* dd_check prints out good log messages */
+        if(dd_check(dd) != NULL)
+            goto fail_with_close;
+
+        /* The dd is opened in READONLY moded, continue.*/
     }
 
     return dd;
+
+cant_access:
+    if (errno == ENOENT || errno == ENOTDIR)
+    {
+        if (!(flags & DD_FAIL_QUIETLY_ENOENT))
+            error_msg("'%s' does not exist", dd->dd_dirname);
+    }
+    else
+    {
+        if (!(flags & DD_FAIL_QUIETLY_EACCES))
+            perror_msg("Can't access '%s'", dd->dd_dirname);
+    }
+
+fail_with_close:
+    dd_close(dd);
+    return NULL;
 }
 
 struct dump_dir *dd_fdopendir(struct dump_dir *dd, int flags)
@@ -607,8 +918,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
  */
 struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags)
 {
-    /* a little trick to copy read bits from file mode to exec bit of dir mode*/
-    mode_t dir_mode = mode | ((mode & 0444) >> 2);
+    mode_t dir_mode = DD_MODE_TO_DIR_MODE(mode);
     struct dump_dir *dd = dd_init();
 
     dd->mode = mode;
@@ -673,8 +983,25 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
         goto fail;
     }
 
-    dd->dd_uid = (uid_t)-1L;
-    dd->dd_gid = (gid_t)-1L;
+    /* Initiliaze dd_uid and dd_gid to sane values which reflect the reality.
+     */
+    dd->dd_uid = stat_sb.st_uid;
+    dd->dd_gid = stat_sb.st_gid;
+
+    /* Create META-DATA directory with real fs attributes which must be changed
+     * in dd_reset_ownership(), when populating of a new dump directory is
+     * done.
+     *
+     * It allows daemons to create a dump directory, populate the directory as
+     * root and then switch the ownership to the real user.
+     */
+    dd->dd_md_fd = dd_create_subdir(dd->dd_fd, META_DATA_DIR_NAME, dd->dd_uid, dd->dd_gid, dir_mode);
+    if (dd->dd_md_fd < 0)
+    {
+        error_msg("Can't create meta-data directory");
+        goto fail;
+    }
+
     if (uid != (uid_t)-1L)
     {
         dd->dd_uid = 0;
@@ -726,12 +1053,16 @@ int dd_reset_ownership(struct dump_dir *dd)
     if (!dd->locked)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    const int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
+    int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
     if (r < 0)
     {
         perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname,
                    (long)dd->dd_uid, (long)dd->dd_gid);
     }
+
+    if (dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid) != 0)
+        error_msg("Failed to reset ownership of meta-data");
+
     return r;
 }
 
@@ -864,27 +1195,27 @@ void dd_sanitize_mode_and_owner(struct dump_dir *dd)
 next:
         free(short_name);
     }
+
+    /* No need to check return value, the functions print good messages.
+     * There are two approaches for handling errors in libreport:
+     * - print out a warning message and keep status quo
+     * - terminate the process
+     */
+    dd_sanitize_mode_meta_data(dd);
+    dd_chown_meta_data(dd, dd->dd_uid, dd->dd_gid);
 }
 
 static int delete_file_dir(int dir_fd, bool skip_lock_file)
 {
-    int opendir_fd = dup(dir_fd);
-    if (opendir_fd < 0)
+    DIR *d;
+    int ret = fdreopen(dir_fd, &d);
+    if (ret < 0)
     {
-        perror_msg("delete_file_dir: dup(dir_fd)");
-        return -1;
-    }
-
-    lseek(opendir_fd, SEEK_SET, 0);
-    DIR *d = fdopendir(opendir_fd);
-    if (!d)
-    {
-        close(opendir_fd);
         /* The caller expects us to error out only if the directory
          * still exists (not deleted). If directory
          * *doesn't exist*, return 0 and clear errno.
          */
-        if (errno == ENOENT || errno == ENOTDIR)
+        if (ret == -ENOENT || ret == -ENOTDIR)
         {
             errno = 0;
             return 0;
@@ -946,6 +1277,35 @@ static int delete_file_dir(int dir_fd, bool skip_lock_file)
     return 0;
 }
 
+static int dd_delete_meta_data(struct dump_dir *dd)
+{
+    if (!dd->locked)
+    {
+        error_msg("Can't remove meta-data of unlocked problem directory %s", dd->dd_dirname);
+        return -1;
+    }
+
+    int dd_md_fd = dd_get_meta_data_dir_fd(dd, /*no create*/0);
+    if (dd_md_fd < 0)
+        return 0;
+
+    if (delete_file_dir(dd_md_fd, /*skip_lock_file:*/ true) != 0)
+    {
+        perror_msg("Can't remove meta-data from '"META_DATA_DIR_NAME"'");
+        return -2;
+    }
+
+    dd_close_meta_data_dir(dd);
+
+    if (unlinkat(dd->dd_fd, META_DATA_DIR_NAME, AT_REMOVEDIR))
+    {
+        perror_msg("Can't remove meta-data directory '"META_DATA_DIR_NAME"'");
+        return -3;
+    }
+
+    return 0;
+}
+
 int dd_delete(struct dump_dir *dd)
 {
     if (!dd->locked)
@@ -954,6 +1314,9 @@ int dd_delete(struct dump_dir *dd)
         return -1;
     }
 
+    if (dd_delete_meta_data(dd) != 0)
+        return -2;
+
     if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0)
     {
         perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname);
@@ -1029,7 +1392,10 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid)
 
             chown_res = fchown(fd, owners_uid, groups_gid);
             if (chown_res)
+            {
                 perror_msg("fchownat('%s')", short_name);
+                break;
+            }
 
             close(fd);
 next:
@@ -1037,6 +1403,15 @@ next:
         }
     }
 
+    if (chown_res == 0)
+        chown_res = dd_chown_meta_data(dd, owners_uid, groups_gid);
+
+    if (chown_res == 0)
+    {
+        dd->dd_uid = owners_uid;
+        dd->dd_gid = groups_gid;
+    }
+
     return chown_res;
 }
 
@@ -1175,7 +1550,7 @@ char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned fla
 //    if (!dd->locked)
 //        error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
     {
         error_msg("Cannot load text. '%s' is not a valid file name", name);
         if ((flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE))
@@ -1201,7 +1576,7 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data)
     if (!dd->locked)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot save text. '%s' is not a valid file name", name);
 
     save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
@@ -1212,7 +1587,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns
     if (!dd->locked)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name);
 
     save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
@@ -1220,7 +1595,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns
 
 long dd_get_item_size(struct dump_dir *dd, const char *name)
 {
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot get item size. '%s' is not a valid file name", name);
 
     long size = -1;
@@ -1245,7 +1620,7 @@ int dd_delete_item(struct dump_dir *dd, const char *name)
     if (!dd->locked)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot delete item. '%s' is not a valid file name", name);
 
     int res = unlinkat(dd->dd_fd, name, /*only files*/0);
@@ -1483,7 +1858,7 @@ int dd_mark_as_notreportable(struct dump_dir *dd, const char *reason)
 
 int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
 {
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
 
     log_debug("copying '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
@@ -1502,7 +1877,7 @@ int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
 
 int dd_copy_file_unpack(struct dump_dir *dd, const char *name, const char *source_path)
 {
-    if (!str_is_correct_filename(name))
+    if (!dd_validate_element_name(name))
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
 
     log_debug("unpacking '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
diff --git a/tests/dump_dir.at b/tests/dump_dir.at
index 31c320e..4bf479e 100644
--- a/tests/dump_dir.at
+++ b/tests/dump_dir.at
@@ -158,6 +158,177 @@ int main(int argc, char **argv)
 }
 ]])
 
+## --------------------- ##
+## dd_create_open_delete ##
+## --------------------- ##
+
+AT_TESTFUN([dd_create_open_delete],
+[[
+#include "internal_libreport.h"
+#include <errno.h>
+#include <assert.h>
+
+int main(int argc, char **argv)
+{
+    g_verbose = 3;
+
+    char template[] = "/tmp/XXXXXX/dump_dir";
+
+    char *last_slash = strrchr(template, '/');
+    *last_slash = '\0';
+
+    if (mkdtemp(template) == NULL) {
+        perror("mkdtemp()");
+        return EXIT_FAILURE;
+    }
+
+    *last_slash = '/';
+
+    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
+    assert(strcmp(dd->dd_dirname, template) == 0);
+    assert(dd->dd_fd >= 0);
+    assert(dd->dd_md_fd >= 0);
+
+    struct stat dd_st;
+    assert(fstat(dd->dd_fd, &dd_st) == 0);
+
+    struct stat md_st;
+    assert(fstat(dd->dd_md_fd, &md_st) == 0);
+
+    assert(dd_st.st_uid == md_st.st_uid);
+    assert(dd_st.st_gid == md_st.st_gid);
+    assert((dd_st.st_mode & 0666) == (md_st.st_mode & 0666));
+
+    struct stat path_md_st;
+    assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0);
+    assert(md_st.st_ino = path_md_st.st_ino);
+
+    dd_create_basic_files(dd, (uid_t)-1, NULL);
+    dd_save_text(dd, FILENAME_TYPE, "attest");
+
+    dd_close(dd);
+    dd = NULL;
+
+    dd = dd_opendir(template, 0);
+    assert(dd != NULL);
+    assert(strcmp(dd->dd_dirname, template) == 0);
+    assert(dd->dd_fd >= 0);
+    assert(dd->dd_md_fd < 0);
+
+    dd_delete(dd);
+
+    assert(stat(template, &dd_st) != 0);
+
+    *last_slash = '\0';
+    assert(rmdir(template) == 0);
+    return EXIT_SUCCESS;
+}
+]])
+
+## -------------------------- ##
+## dd_sanitize_mode_and_owner ##
+## -------------------------- ##
+
+AT_TESTFUN([dd_sanitize_mode_and_owner],
+[[
+#include "internal_libreport.h"
+#include <errno.h>
+#include <assert.h>
+
+int main(int argc, char **argv)
+{
+    g_verbose = 3;
+
+    char template[] = "/tmp/XXXXXX/dump_dir";
+
+    char *last_slash = strrchr(template, '/');
+    *last_slash = '\0';
+
+    if (mkdtemp(template) == NULL) {
+        perror("mkdtemp()");
+        return EXIT_FAILURE;
+    }
+
+    *last_slash = '/';
+
+    /* Prepare a directory for chmod test, use mode 0600 and chmod it to 0640 */
+    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0600);
+    assert(dd != NULL);
+
+    {
+        struct stat path_md_st;
+        assert((fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0) || !"Failed initialize meta-data");
+        assert((path_md_st.st_mode & 0077) == 0);
+    }
+
+    dd_create_basic_files(dd, (uid_t)-1, NULL);
+    dd_save_text(dd, FILENAME_TYPE, "attest");
+
+    dd_close(dd);
+
+    /* initialize meta-data */
+    dd = dd_opendir(template, DD_OPEN_FD_ONLY);
+    /* reopen for writing */
+    dd = dd_fdopendir(dd, /*for writing*/0);
+    assert(dd != NULL);
+
+    assert(fchmod(dd->dd_fd, 0750) == 0);
+    dd->mode = 0640;
+
+    fprintf(stderr, "Going to sanitize\n");
+    dd_sanitize_mode_and_owner(dd);
+    fprintf(stderr, "Sanitized\n");
+
+    {
+        DIR *d = opendir(template);
+        struct dirent *dent;
+        while ((dent = readdir(d)) != NULL)
+        {
+            if (   strcmp(".", dent->d_name) == 0
+                || strcmp("..", dent->d_name) == 0
+                || strcmp(".lock", dent->d_name) == 0
+                || strcmp(".libreport", dent->d_name) == 0
+                )
+                continue;
+
+            struct stat sb;
+            printf("Testing element: %s\n", dent->d_name);
+            assert(fstatat(dd->dd_fd, dent->d_name, &sb, 0) == 0 || !"Cannot stat a regular element");
+            assert((sb.st_mode & 0777) == 0640 || !"Failed to chmod a regular element");
+        }
+        closedir(d);
+    }
+
+    {
+        struct stat path_md_st;
+        assert(fstatat(dd->dd_fd, ".libreport", &path_md_st, 0) == 0 || !"Cannot stat meta-data directory");
+        assert((path_md_st.st_mode & 0777) == 0750 || !"Failed chmod meta-data");
+    }
+
+    int md_dir_fd = openat(dd->dd_fd, ".libreport", O_DIRECTORY);
+    assert(md_dir_fd >= 0 || !"Cannot open meta-data directory");
+    DIR *d = fdopendir(md_dir_fd);
+    struct dirent *dent;
+    while ((dent = readdir(d)) != NULL)
+    {
+        if (strcmp(".", dent->d_name) == 0 || strcmp("..", dent->d_name) == 0)
+            continue;
+
+        struct stat sb;
+        printf("Testing meta-data: %s\n", dent->d_name);
+        assert(fstatat(md_dir_fd, dent->d_name, &sb, 0) == 0 || !"Cannot stat meta-data file");
+        assert((sb.st_mode & 0777) == 0640 || !"Failed to chmod a meta-data file");
+    }
+
+    closedir(d);
+    dd_delete(dd);
+
+    *last_slash = '\0';
+    assert(rmdir(template) == 0);
+    return EXIT_SUCCESS;
+}
+]])
+
 ## -------------- ##
 ## recursive_lock ##
 ## -------------- ##
-- 
2.1.0