Blob Blame History Raw
From c7f7a2f3f6c4847bcf4ac2285b0efbfb281334b5 Mon Sep 17 00:00:00 2001
From: Jakub Filak <jfilak@redhat.com>
Date: Fri, 24 Apr 2015 10:57:23 +0200
Subject: [PATCH] lib: fix races in dump directory handling code

Florian Weimer <fweimer@redhat.com>:

    dd_opendir() should keep a file handle (opened with O_DIRECTORY) and
    use openat() and similar functions to access files in it.

    ...

    The file system manipulation functions should guard against hard
    links (check that link count is <= 1, just as in the user coredump
    code in abrt-hook-ccpp), possibly after opening the file
    with O_PATH first to avoid side effects on open/close.

Related: #1214745

Signed-off-by: Jakub Filak <jfilak@redhat.com>
---
 src/include/dump_dir.h           |   7 +
 src/include/internal_libreport.h |  15 +-
 src/lib/compress.c               |  23 +-
 src/lib/copyfd.c                 |  19 +-
 src/lib/dump_dir.c               | 456 +++++++++++++++++++++++----------------
 src/lib/problem_data.c           |   6 +-
 src/lib/xfuncs.c                 |  22 +-
 tests/dump_dir.at                | 156 ++++++++++++++
 8 files changed, 499 insertions(+), 205 deletions(-)

diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
index 46349ed..669f7fd 100644
--- a/src/include/dump_dir.h
+++ b/src/include/dump_dir.h
@@ -34,6 +34,12 @@ extern "C" {
 
 /* Utility function */
 int create_symlink_lockfile(const char *filename, const char *pid_str);
+int create_symlink_lockfile_at(int dir_fd, const char *filename, const char *pid_str);
+
+/* Opens filename for reading relatively to a directory represented by dir_fd.
+ * The function fails if the file is symbolic link, directory or hard link.
+ */
+int secure_openat_read(int dir_fd, const char *filename);
 
 enum {
     DD_FAIL_QUIETLY_ENOENT = (1 << 0),
@@ -63,6 +69,7 @@ struct dump_dir {
      * lock but are not able to unlock the dump directory.
      */
     int owns_lock;
+    int dd_fd;
 };
 
 void dd_close(struct dump_dir *dd);
diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h
index a4303de..66f91e9 100644
--- a/src/include/internal_libreport.h
+++ b/src/include/internal_libreport.h
@@ -158,10 +158,14 @@ off_t copyfd_eof(int src_fd, int dst_fd, int flags);
 off_t copyfd_size(int src_fd, int dst_fd, off_t size, int flags);
 #define copyfd_exact_size libreport_copyfd_exact_size
 void copyfd_exact_size(int src_fd, int dst_fd, off_t size);
-#define copy_file_ext libreport_copy_file_ext
-off_t copy_file_ext(const char *src_name, const char *dst_name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags);
+#define copy_file_ext_at libreport_copy_file_ext_at
+off_t copy_file_ext_at(const char *src_name, int dir_fd, const char *name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags);
+#define copy_file_ext(src_name, dst_name, mode, uid, gid, src_flags, dst_flags) \
+    copy_file_ext_at(src_name, AT_FDCWD, dst_name, mode, uid, gid, src_flags, dst_flags)
 #define copy_file libreport_copy_file
 off_t copy_file(const char *src_name, const char *dst_name, int mode);
+#define copy_file_at libreport_copy_file_at
+off_t copy_file_at(const char *src_name, int dir_fd, const char *name, int mode);
 #define copy_file_recursive libreport_copy_file_recursive
 int copy_file_recursive(const char *source, const char *dest);
 
@@ -169,6 +173,9 @@ int copy_file_recursive(const char *source, const char *dest);
 int decompress_fd(int fdi, int fdo);
 #define decompress_file libreport_decompress_file
 int decompress_file(const char *path_in, const char *path_out, mode_t mode_out);
+#define decompress_file_ext_at libreport_decompress_file_ext_at
+int decompress_file_ext_at(const char *path_in, int dir_fd, const char *path_out,
+        mode_t mode_out, uid_t uid, gid_t gid, int src_flags, int dst_flags);
 
 // NB: will return short read on error, not -1,
 // if some data was read before error occurred
@@ -414,6 +421,8 @@ int xopen3(const char *pathname, int flags, int mode);
 int xopen(const char *pathname, int flags);
 #define xunlink libreport_xunlink
 void xunlink(const char *pathname);
+#define xunlinkat libreport_xunlinkat
+void xunlinkat(int dir_fd, const char *pathname, int flags);
 
 /* Just testing dent->d_type == DT_REG is wrong: some filesystems
  * do not report the type, they report DT_UNKNOWN for every dirent
@@ -423,6 +432,8 @@ void xunlink(const char *pathname);
  */
 #define is_regular_file libreport_is_regular_file
 int is_regular_file(struct dirent *dent, const char *dirname);
+#define is_regular_file_at libreport_is_regular_file_at
+int is_regular_file_at(struct dirent *dent, int dir_fd);
 
 #define dot_or_dotdot libreport_dot_or_dotdot
 bool dot_or_dotdot(const char *filename);
diff --git a/src/lib/compress.c b/src/lib/compress.c
index eec155a..86343b3 100644
--- a/src/lib/compress.c
+++ b/src/lib/compress.c
@@ -124,16 +124,17 @@ decompress_fd(int fdi, int fdo)
 }
 
 int
-decompress_file(const char *path_in, const char *path_out, mode_t mode_out)
+decompress_file_ext_at(const char *path_in, int dir_fd, const char *path_out, mode_t mode_out,
+                       uid_t uid, gid_t gid, int src_flags, int dst_flags)
 {
-    int fdi = open(path_in, O_RDONLY | O_CLOEXEC);
+    int fdi = open(path_in, src_flags);
     if (fdi < 0)
     {
         perror_msg("Could not open file: %s", path_in);
         return -1;
     }
 
-    int fdo = open(path_out, O_WRONLY | O_CLOEXEC | O_EXCL | O_CREAT, mode_out);
+    int fdo = openat(dir_fd, path_out, dst_flags, mode_out);
     if (fdo < 0)
     {
         close(fdi);
@@ -143,10 +144,24 @@ decompress_file(const char *path_in, const char *path_out, mode_t mode_out)
 
     int ret = decompress_fd(fdi, fdo);
     close(fdi);
+    if (uid != (uid_t)-1L)
+    {
+        if (fchown(fdo, uid, gid) == -1)
+        {
+            perror_msg("Can't change ownership of '%s' to %lu:%lu", path_out, (long)uid, (long)gid);
+            ret = -1;
+        }
+    }
     close(fdo);
 
     if (ret != 0)
-        unlink(path_out);
+        unlinkat(dir_fd, path_out, /*only files*/0);
 
     return ret;
 }
+
+int decompress_file(const char *path_in, const char *path_out, mode_t mode_out)
+{
+    return decompress_file_ext_at(path_in, AT_FDCWD, path_out, mode_out, -1, -1,
+            O_RDONLY, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC);
+}
diff --git a/src/lib/copyfd.c b/src/lib/copyfd.c
index 64fece7..45c0d2f 100644
--- a/src/lib/copyfd.c
+++ b/src/lib/copyfd.c
@@ -149,7 +149,7 @@ off_t copyfd_eof(int fd1, int fd2, int flags)
 	return full_fd_action(fd1, fd2, 0, flags);
 }
 
-off_t copy_file_ext(const char *src_name, const char *dst_name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags)
+off_t copy_file_ext_at(const char *src_name, int dir_fd, const char *name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags)
 {
     off_t r;
     int src = open(src_name, src_flags);
@@ -158,11 +158,11 @@ off_t copy_file_ext(const char *src_name, const char *dst_name, int mode, uid_t
         perror_msg("Can't open '%s'", src_name);
         return -1;
     }
-    int dst = open(dst_name, dst_flags, mode);
+    int dst = openat(dir_fd, name, dst_flags, mode);
     if (dst < 0)
     {
         close(src);
-        perror_msg("Can't open '%s'", dst_name);
+        perror_msg("Can't open '%s'", name);
         return -1;
     }
     r = copyfd_eof(src, dst, /*flags:*/ 0);
@@ -171,17 +171,24 @@ off_t copy_file_ext(const char *src_name, const char *dst_name, int mode, uid_t
     {
         if (fchown(dst, uid, gid) == -1)
         {
-            perror_msg("Can't change '%s' ownership to %lu:%lu", dst_name, (long)uid, (long)gid);
+            perror_msg("Can't change ownership of '%s' to %lu:%lu", name, (long)uid, (long)gid);
             close(dst);
-            unlink(dst_name);
             return -1;
         }
     }
     close(dst);
+
     return r;
 }
 
+off_t copy_file_at(const char *src_name, int dir_fd, const char *name, int mode)
+{
+    return copy_file_ext_at(src_name, dir_fd, name, mode, -1, -1,
+            O_RDONLY, O_WRONLY | O_TRUNC | O_CREAT);
+}
+
 off_t copy_file(const char *src_name, const char *dst_name, int mode)
 {
-    return copy_file_ext(src_name, dst_name, mode, -1, -1, O_RDONLY, O_WRONLY | O_TRUNC | O_CREAT);
+    return copy_file_ext(src_name, dst_name, mode, -1, -1,
+            O_RDONLY, O_WRONLY | O_TRUNC | O_CREAT);
 }
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
index 017a9c1..7cc918a 100644
--- a/src/lib/dump_dir.c
+++ b/src/lib/dump_dir.c
@@ -85,6 +85,7 @@
 
 
 static char *load_text_file(const char *path, unsigned flags);
+static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
 static void copy_file_from_chroot(struct dump_dir* dd, const char *name,
         const char *chroot_dir, const char *file_path);
 
@@ -98,10 +99,10 @@ static bool isdigit_str(const char *str)
     return true;
 }
 
-static bool exist_file_dir(const char *path)
+static bool exist_file_dir_at(int dir_fd, const char *name)
 {
     struct stat buf;
-    if (stat(path, &buf) == 0)
+    if (fstatat(dir_fd, name, &buf, AT_SYMLINK_NOFOLLOW) == 0)
     {
         if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode))
         {
@@ -111,15 +112,68 @@ static bool exist_file_dir(const char *path)
     return false;
 }
 
+/* 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)
+ * 2. stat the resulting file descriptor and fail if the opened file is not a
+ *    regular file or if the number of links is greater than 1 (that means that
+ *    the inode has more names (hard links))
+ * 3. "re-open" the file descriptor retrieved in the first step with O_RDONLY
+ *    by opening /proc/self/fd/$fd (then close the former file descriptor and
+ *    return the new one).
+ */
+int secure_openat_read(int dir_fd, const char *filename)
+{
+    if (strchr(filename, '/'))
+    {
+        error_msg("Path must be file name without directory: '%s'", filename);
+        errno = EFAULT;
+        return -1;
+    }
+
+    static char reopen_buf[sizeof("/proc/self/fd/") + 3*sizeof(int) + 1];
+
+    int path_fd = openat(dir_fd, filename, O_PATH | O_NOFOLLOW);
+    if (path_fd < 0)
+        return -1;
+
+    struct stat path_sb;
+    int r = fstat(path_fd, &path_sb);
+    if (r < 0)
+    {
+        perror_msg("stat");
+        close(path_fd);
+        return -1;
+    }
+
+    if (!S_ISREG(path_sb.st_mode) || path_sb.st_nlink > 1)
+    {
+        log_notice("Path isn't a regular file or has more links (%lu)", (unsigned long)path_sb.st_nlink);
+        errno = EINVAL;
+        close(path_fd);
+        return -1;
+    }
+
+    if (snprintf(reopen_buf, sizeof(reopen_buf), "/proc/self/fd/%d", path_fd) >= sizeof(reopen_buf)) {
+        error_msg("BUG: too long path to a file descriptor");
+        abort();
+    }
+
+    const int fd = open(reopen_buf, O_RDONLY);
+    close(path_fd);
+
+    return fd;
+}
+
 /* Returns value less than 0 if the file is not readable or
  * if the file doesn't contain valid unixt time stamp.
  *
  * Any possible failure will be logged.
  */
-static time_t parse_time_file(const char *filename)
+static time_t parse_time_file_at(int dir_fd, const char *filename)
 {
     /* Open input file, and parse it. */
-    int fd = open(filename, O_RDONLY | O_NOFOLLOW);
+    int fd = secure_openat_read(dir_fd, filename);
     if (fd < 0)
     {
         VERB2 pwarn_msg("Can't open '%s'", filename);
@@ -183,9 +237,9 @@ static time_t parse_time_file(const char *filename)
  *  0: failed to lock (someone else has it locked)
  *  1: success
  */
-int create_symlink_lockfile(const char* lock_file, const char* pid)
+int create_symlink_lockfile_at(int dir_fd, const char* lock_file, const char* pid)
 {
-    while (symlink(pid, lock_file) != 0)
+    while (symlinkat(pid, dir_fd, lock_file) != 0)
     {
         if (errno != EEXIST)
         {
@@ -198,7 +252,7 @@ int create_symlink_lockfile(const char* lock_file, const char* pid)
         }
 
         char pid_buf[sizeof(pid_t)*3 + 4];
-        ssize_t r = readlink(lock_file, pid_buf, sizeof(pid_buf) - 1);
+        ssize_t r = readlinkat(dir_fd, lock_file, pid_buf, sizeof(pid_buf) - 1);
         if (r < 0)
         {
             if (errno == ENOENT)
@@ -231,7 +285,7 @@ int create_symlink_lockfile(const char* lock_file, const char* pid)
             log("Lock file '%s' was locked by process %s, but it crashed?", lock_file, pid_buf);
         }
         /* The file may be deleted by now by other process. Ignore ENOENT */
-        if (unlink(lock_file) != 0 && errno != ENOENT)
+        if (unlinkat(dir_fd, lock_file, /*only files*/0) != 0 && errno != ENOENT)
         {
             perror_msg("Can't remove stale lock file '%s'", lock_file);
             errno = 0;
@@ -243,21 +297,21 @@ int create_symlink_lockfile(const char* lock_file, const char* pid)
     return 1;
 }
 
+int create_symlink_lockfile(const char *filename, const char *pid_str)
+{
+    return create_symlink_lockfile_at(AT_FDCWD, filename, pid_str);
+}
+
 static const char *dd_check(struct dump_dir *dd)
 {
-    unsigned dirname_len = strlen(dd->dd_dirname);
-    char filename_buf[FILENAME_MAX+1];
-    strcpy(filename_buf, dd->dd_dirname);
-    strcpy(filename_buf + dirname_len, "/"FILENAME_TIME);
-    dd->dd_time = parse_time_file(filename_buf);
+    dd->dd_time = parse_time_file_at(dd->dd_fd, FILENAME_TIME);
     if (dd->dd_time < 0)
     {
         log_debug("Missing file: "FILENAME_TIME);
         return FILENAME_TIME;
     }
 
-    strcpy(filename_buf + dirname_len, "/"FILENAME_TYPE);
-    dd->dd_type = load_text_file(filename_buf, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
+    dd->dd_type = load_text_file_at(dd->dd_fd, FILENAME_TYPE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
     if (!dd->dd_type || (strlen(dd->dd_type) == 0))
     {
         log_debug("Missing or empty file: "FILENAME_TYPE);
@@ -275,17 +329,12 @@ static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags)
     char pid_buf[sizeof(long)*3 + 2];
     snprintf(pid_buf, sizeof(pid_buf), "%lu", (long)getpid());
 
-    unsigned dirname_len = strlen(dd->dd_dirname);
-    char lock_buf[dirname_len + sizeof("/.lock")];
-    strcpy(lock_buf, dd->dd_dirname);
-    strcpy(lock_buf + dirname_len, "/.lock");
-
     unsigned count = NO_TIME_FILE_COUNT;
 
  retry:
     while (1)
     {
-        int r = create_symlink_lockfile(lock_buf, pid_buf);
+        int r = create_symlink_lockfile_at(dd->dd_fd, ".lock", pid_buf);
         if (r < 0)
             return r; /* error */
         if (r > 0 || errno == EALREADY)
@@ -312,9 +361,9 @@ static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags)
         if (missing_file)
         {
             if (dd->owns_lock)
-                xunlink(lock_buf);
+                xunlinkat(dd->dd_fd, ".lock", /*only files*/0);
 
-            log_warning("Unlocked '%s' (no or corrupted '%s' file)", lock_buf, missing_file);
+            log_warning("Unlocked '%s' (no or corrupted '%s' file)", dd->dd_dirname, missing_file);
             if (--count == 0 || flags & DD_DONT_WAIT_FOR_LOCK)
             {
                 errno = EISDIR; /* "this is an ordinary dir, not dump dir" */
@@ -333,18 +382,13 @@ static void dd_unlock(struct dump_dir *dd)
 {
     if (dd->locked)
     {
-        unsigned dirname_len = strlen(dd->dd_dirname);
-        char lock_buf[dirname_len + sizeof("/.lock")];
-        strcpy(lock_buf, dd->dd_dirname);
-        strcpy(lock_buf + dirname_len, "/.lock");
-
         if (dd->owns_lock)
-            xunlink(lock_buf);
+            xunlinkat(dd->dd_fd, ".lock", /*only files*/0);
 
         dd->owns_lock = 0;
         dd->locked = 0;
 
-        log_info("Unlocked '%s'", lock_buf);
+        log_info("Unlocked '%s/.lock'", dd->dd_dirname);
     }
 }
 
@@ -352,17 +396,16 @@ 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;
     return dd;
 }
 
-int dd_exist(const struct dump_dir *dd, const char *path)
+int dd_exist(const struct dump_dir *dd, const char *name)
 {
-    if (!str_is_correct_filename(path))
-        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", path);
+    if (!str_is_correct_filename(name))
+        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
 
-    char *full_path = concat_path_file(dd->dd_dirname, path);
-    int ret = exist_file_dir(full_path);
-    free(full_path);
+    const int ret = exist_file_dir_at(dd->dd_fd, name);
     return ret;
 }
 
@@ -372,6 +415,10 @@ void dd_close(struct dump_dir *dd)
         return;
 
     dd_unlock(dd);
+
+    if (dd->dd_fd >= 0)
+        close(dd->dd_fd);
+
     if (dd->next_dir)
     {
         closedir(dd->next_dir);
@@ -396,10 +443,13 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
     struct dump_dir *dd = dd_init();
 
     dir = dd->dd_dirname = rm_trailing_slashes(dir);
-
+    dd->dd_fd = open(dir, O_DIRECTORY | O_NOFOLLOW);
     struct stat stat_buf;
-    if (stat(dir, &stat_buf) != 0)
+    if (dd->dd_fd < 0)
         goto cant_access;
+    if (fstat(dd->dd_fd, &stat_buf) != 0)
+        goto cant_access;
+
     /* & 0666 should remove the executable bit */
     dd->mode = (stat_buf.st_mode & 0666);
 
@@ -409,11 +459,12 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
         if ((flags & DD_OPEN_READONLY) && errno == EACCES)
         {
             /* Directory is not writable. If it seems to be readable,
-             * return "read only" dd, not NULL */
-            if (stat(dir, &stat_buf) == 0
-             && S_ISDIR(stat_buf.st_mode)
-             && access(dir, R_OK) == 0
-            ) {
+             * 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);
@@ -456,10 +507,9 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
     if (geteuid() == 0)
     {
         /* In case caller would want to create more files, he'll need uid:gid */
-        struct stat stat_buf;
-        if (stat(dir, &stat_buf) != 0 || !S_ISDIR(stat_buf.st_mode))
+        if (fstat(dd->dd_fd, &stat_buf) != 0)
         {
-            error_msg("Can't stat '%s', or it is not a directory", dir);
+            error_msg("Can't stat '%s'", dir);
             dd_close(dd);
             return NULL;
         }
@@ -554,8 +604,7 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
          * dd_create("dir/..") and similar are madness, refuse them.
          */
         error_msg("Bad dir name '%s'", dir);
-        dd_close(dd);
-        return NULL;
+        goto fail;
     }
 
     /* Was creating it with mode 0700 and user as the owner, but this allows
@@ -571,22 +620,31 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
     if (r != 0)
     {
         perror_msg("Can't create directory '%s'", dir);
-        dd_close(dd);
-        return NULL;
+        goto fail;
     }
 
-    if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0)
+    dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);
+    if (dd->dd_fd < 0)
     {
-        dd_close(dd);
-        return NULL;
+        perror_msg("Can't open newly created directory '%s'", dir);
+        goto fail;
+    }
+
+    struct stat stat_sb;
+    if (fstat(dd->dd_fd, &stat_sb) < 0)
+    {
+        perror_msg("stat(%s)", dd->dd_dirname);
+        goto fail;
     }
 
+    if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0)
+        goto fail;
+
     /* mkdir's mode (above) can be affected by umask, fix it */
-    if (chmod(dir, dir_mode) == -1)
+    if (fchmod(dd->dd_fd, dir_mode) == -1)
     {
         perror_msg("Can't change mode of '%s'", dir);
-        dd_close(dd);
-        return NULL;
+        goto fail;
     }
 
     dd->dd_uid = (uid_t)-1L;
@@ -628,6 +686,10 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
     }
 
     return dd;
+
+fail:
+    dd_close(dd);
+    return NULL;
 }
 
 /* Resets ownership of the given directory to UID and GID according to values
@@ -638,7 +700,7 @@ int dd_reset_ownership(struct dump_dir *dd)
     if (!dd->locked)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    const int r =lchown(dd->dd_dirname, dd->dd_uid, dd->dd_gid);
+    const 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,
@@ -755,61 +817,43 @@ void dd_sanitize_mode_and_owner(struct dump_dir *dd)
     if (!dd->locked)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
-    DIR *d = opendir(dd->dd_dirname);
-    if (!d)
-        return;
-
-    struct dirent *dent;
-    while ((dent = readdir(d)) != NULL)
+    dd_init_next_file(dd);
+    char *short_name;
+    while (dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
     {
-        if (dent->d_name[0] == '.') /* ".lock", ".", ".."? skip */
-            continue;
-        char *full_path = concat_path_file(dd->dd_dirname, dent->d_name);
-        struct stat statbuf;
-        if (lstat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
-        {
-            if ((statbuf.st_mode & 0777) != dd->mode)
-            {
-                /* We open the file only for fchmod()
-                 *
-                 * We use fchmod() because chmod() changes the permissions of
-                 * the file specified whose pathname is given in path, which
-                 * is dereferenced if it is a symbolic link.
-                 */
-                int fd = open(full_path, O_RDONLY | O_NOFOLLOW, dd->mode);
-                if (fd >= 0)
-                {
-                    if (fchmod(fd, dd->mode) != 0)
-                    {
-                        perror_msg("Can't change '%s' mode to 0%o", full_path,
-                                   (unsigned)dd->mode);
-                    }
-                    close(fd);
-                }
-                else
-                {
-                    perror_msg("Can't open regular file '%s'", full_path);
-                }
-            }
-            if (statbuf.st_uid != dd->dd_uid || statbuf.st_gid != dd->dd_gid)
-            {
-                if (lchown(full_path, dd->dd_uid, dd->dd_gid) != 0)
-                {
-                    perror_msg("Can't change '%s' ownership to %lu:%lu", full_path,
-                               (long)dd->dd_uid, (long)dd->dd_gid);
-                }
-            }
-        }
-        free(full_path);
+        /* The current process has to have read access at least */
+        int fd = secure_openat_read(dd->dd_fd, short_name);
+        if (fd < 0)
+            goto next;
+
+        if (fchmod(fd, dd->mode) != 0)
+            perror_msg("Can't change '%s/%s' mode to 0%o", dd->dd_dirname, short_name,
+                       (unsigned)dd->mode);
+
+        if (fchown(fd, dd->dd_uid, dd->dd_gid) != 0)
+            perror_msg("Can't change '%s/%s' ownership to %lu:%lu", dd->dd_dirname, short_name,
+                       (long)dd->dd_uid, (long)dd->dd_gid);
+
+        close(fd);
+next:
+        free(short_name);
     }
-    closedir(d);
 }
 
-static int delete_file_dir(const char *dir, bool skip_lock_file)
+static int delete_file_dir(int dir_fd, bool skip_lock_file)
 {
-    DIR *d = opendir(dir);
+    int opendir_fd = dup(dir_fd);
+    if (opendir_fd < 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.
@@ -833,26 +877,35 @@ static int delete_file_dir(const char *dir, bool skip_lock_file)
             unlink_lock_file = true;
             continue;
         }
-        char *full_path = concat_path_file(dir, dent->d_name);
-        if (unlink(full_path) == -1 && errno != ENOENT)
+        if (unlinkat(dir_fd, dent->d_name, /*only files*/0) == -1 && errno != ENOENT)
         {
             int err = 0;
             if (errno == EISDIR)
             {
                 errno = 0;
-                err = delete_file_dir(full_path, /*skip_lock_file:*/ false);
+                int subdir_fd = openat(dir_fd, dent->d_name, O_DIRECTORY);
+                if (subdir_fd < 0)
+                {
+                    perror_msg("Can't open sub-dir'%s'", dent->d_name);
+                    closedir(d);
+                    return -1;
+                }
+                else
+                {
+                    err = delete_file_dir(subdir_fd, /*skip_lock_file:*/ false);
+                    close(subdir_fd);
+                    if (err == 0)
+                        unlinkat(dir_fd, dent->d_name, AT_REMOVEDIR);
+                }
             }
             if (errno || err)
             {
-                perror_msg("Can't remove '%s'", full_path);
-                free(full_path);
+                perror_msg("Can't remove '%s'", dent->d_name);
                 closedir(d);
                 return -1;
             }
         }
-        free(full_path);
     }
-    closedir(d);
 
     /* Here we know for sure that all files/subdirs we found via readdir
      * were deleted successfully. If rmdir below fails, we assume someone
@@ -860,29 +913,11 @@ static int delete_file_dir(const char *dir, bool skip_lock_file)
      */
 
     if (unlink_lock_file)
-    {
-        char *full_path = concat_path_file(dir, ".lock");
-        xunlink(full_path);
-        free(full_path);
+        xunlinkat(dir_fd, ".lock", /*only files*/0);
 
-        unsigned cnt = RMDIR_FAIL_COUNT;
-        do {
-            if (rmdir(dir) == 0)
-                return 0;
-            /* Someone locked the dir after unlink, but before rmdir.
-             * This "someone" must be dd_lock().
-             * It detects this (by seeing that there is no time file)
-             * and backs off at once. So we need to just retry rmdir,
-             * with minimal sleep.
-             */
-            usleep(RMDIR_FAIL_USLEEP);
-        } while (--cnt != 0);
-    }
+    closedir(d);
 
-    int r = rmdir(dir);
-    if (r)
-        perror_msg("Can't remove directory '%s'", dir);
-    return r;
+    return 0;
 }
 
 int dd_delete(struct dump_dir *dd)
@@ -893,10 +928,34 @@ int dd_delete(struct dump_dir *dd)
         return -1;
     }
 
-    int r = delete_file_dir(dd->dd_dirname, /*skip_lock_file:*/ true);
+    if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0)
+    {
+        perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname);
+        return -2;
+    }
+
+    unsigned cnt = RMDIR_FAIL_COUNT;
+    do {
+        if (rmdir(dd->dd_dirname) == 0)
+            break;
+        /* Someone locked the dir after unlink, but before rmdir.
+         * This "someone" must be dd_lock().
+         * It detects this (by seeing that there is no time file)
+         * and backs off at once. So we need to just retry rmdir,
+         * with minimal sleep.
+         */
+        usleep(RMDIR_FAIL_USLEEP);
+    } while (--cnt != 0);
+
+    if (cnt == 0)
+    {
+        perror_msg("Can't remove directory '%s'", dd->dd_dirname);
+        return -3;
+    }
+
     dd->locked = 0; /* delete_file_dir already removed .lock */
     dd_close(dd);
-    return r;
+    return 0;
 }
 
 int dd_chown(struct dump_dir *dd, uid_t new_uid)
@@ -905,7 +964,7 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid)
         error_msg_and_die("dump_dir is not opened"); /* bug */
 
     struct stat statbuf;
-    if (!(stat(dd->dd_dirname, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)))
+    if (fstat(dd->dd_fd, &statbuf) != 0)
     {
         perror_msg("stat('%s')", dd->dd_dirname);
         return 1;
@@ -926,29 +985,37 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid)
     gid_t groups_gid = pw->pw_gid;
 #endif
 
-    int chown_res = lchown(dd->dd_dirname, owners_uid, groups_gid);
+    int chown_res = fchown(dd->dd_fd, owners_uid, groups_gid);
     if (chown_res)
-        perror_msg("lchown('%s')", dd->dd_dirname);
+        perror_msg("fchown('%s')", dd->dd_dirname);
     else
     {
         dd_init_next_file(dd);
-        char *full_name;
-        while (chown_res == 0 && dd_get_next_file(dd, /*short_name*/ NULL, &full_name))
+        char *short_name;
+        while (chown_res == 0 && dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
         {
-            log_debug("chowning %s", full_name);
-            chown_res = lchown(full_name, owners_uid, groups_gid);
+            /* The current process has to have read access at least */
+            int fd = secure_openat_read(dd->dd_fd, short_name);
+            if (fd < 0)
+                goto next;
+
+            log_debug("chowning %s", short_name);
+
+            chown_res = fchown(fd, owners_uid, groups_gid);
             if (chown_res)
-                perror_msg("lchown('%s')", full_name);
-            free(full_name);
+                perror_msg("fchownat('%s')", short_name);
+
+            close(fd);
+next:
+            free(short_name);
         }
     }
 
     return chown_res;
 }
 
-static char *load_text_file(const char *path, unsigned flags)
+static char *load_text_from_file_descriptor(int fd, const char *path, int flags)
 {
-    int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
     if (fd == -1)
     {
         if (!(flags & DD_FAIL_QUIETLY_ENOENT))
@@ -1003,6 +1070,20 @@ static char *load_text_file(const char *path, unsigned flags)
     return strbuf_free_nobuf(buf_content);
 }
 
+static char *load_text_file_at(int dir_fd, const char *name, unsigned flags)
+{
+    assert(name[0] != '/');
+
+    const int fd = openat(dir_fd, name, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
+    return load_text_from_file_descriptor(fd, name, flags);
+}
+
+static char *load_text_file(const char *path, unsigned flags)
+{
+    const int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
+    return load_text_from_file_descriptor(fd, path, flags);
+}
+
 static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const char *chroot_dir, const char *file_path)
 {
     char *chrooted_name = concat_path_file(chroot_dir, file_path);
@@ -1016,14 +1097,16 @@ static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const c
     }
 }
 
-static bool save_binary_file(const char *path, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode)
+static bool save_binary_file_at(int dir_fd, const char *name, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode)
 {
+    assert(name[0] != '/');
+
     /* the mode is set by the caller, see dd_create() for security analysis */
-    unlink(path);
-    int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT | O_NOFOLLOW, mode);
+    unlinkat(dir_fd, name, /*remove only files*/0);
+    int fd = openat(dir_fd, name, O_WRONLY | O_EXCL | O_CREAT | O_NOFOLLOW, mode);
     if (fd < 0)
     {
-        perror_msg("Can't open file '%s'", path);
+        perror_msg("Can't open file '%s'", name);
         return false;
     }
 
@@ -1031,7 +1114,9 @@ static bool save_binary_file(const char *path, const char* data, unsigned size,
     {
         if (fchown(fd, uid, gid) == -1)
         {
-            perror_msg("Can't change '%s' ownership to %lu:%lu", path, (long)uid, (long)gid);
+            perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid);
+            close(fd);
+            return false;
         }
     }
 
@@ -1043,14 +1128,16 @@ static bool save_binary_file(const char *path, const char* data, unsigned size,
      */
     if (fchmod(fd, mode) == -1)
     {
-        perror_msg("Can't change mode of '%s'", path);
+        perror_msg("Can't change mode of '%s'", name);
+        close(fd);
+        return false;
     }
 
     unsigned r = full_write(fd, data, size);
     close(fd);
     if (r != size)
     {
-        error_msg("Can't save file '%s'", path);
+        error_msg("Can't save file '%s'", name);
         return false;
     }
 
@@ -1075,11 +1162,7 @@ char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned fla
     if (strcmp(name, "release") == 0)
         name = FILENAME_OS_RELEASE;
 
-    char *full_path = concat_path_file(dd->dd_dirname, name);
-    char *ret = load_text_file(full_path, flags);
-    free(full_path);
-
-    return ret;
+    return load_text_file_at(dd->dd_fd, name, flags);
 }
 
 char* dd_load_text(const struct dump_dir *dd, const char *name)
@@ -1095,9 +1178,7 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data)
     if (!str_is_correct_filename(name))
         error_msg_and_die("Cannot save text. '%s' is not a valid file name", name);
 
-    char *full_path = concat_path_file(dd->dd_dirname, name);
-    save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
-    free(full_path);
+    save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
 }
 
 void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, unsigned size)
@@ -1108,9 +1189,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns
     if (!str_is_correct_filename(name))
         error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name);
 
-    char *full_path = concat_path_file(dd->dd_dirname, name);
-    save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
-    free(full_path);
+    save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
 }
 
 long dd_get_item_size(struct dump_dir *dd, const char *name)
@@ -1119,21 +1198,19 @@ long dd_get_item_size(struct dump_dir *dd, const char *name)
         error_msg_and_die("Cannot get item size. '%s' is not a valid file name", name);
 
     long size = -1;
-    char *iname = concat_path_file(dd->dd_dirname, name);
     struct stat statbuf;
+    int r = fstatat(dd->dd_fd, name, &statbuf, AT_SYMLINK_NOFOLLOW);
 
-    if (lstat(iname, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
+    if (r == 0 && S_ISREG(statbuf.st_mode))
         size = statbuf.st_size;
     else
     {
         if (errno == ENOENT)
             size = 0;
         else
-            perror_msg("Can't get size of file '%s'", iname);
+            perror_msg("Can't get size of file '%s'", name);
     }
 
-    free(iname);
-
     return size;
 }
 
@@ -1145,18 +1222,16 @@ int dd_delete_item(struct dump_dir *dd, const char *name)
     if (!str_is_correct_filename(name))
         error_msg_and_die("Cannot delete item. '%s' is not a valid file name", name);
 
-    char *path = concat_path_file(dd->dd_dirname, name);
-    int res = unlink(path);
+    int res = unlinkat(dd->dd_fd, name, /*only files*/0);
 
     if (res < 0)
     {
         if (errno == ENOENT)
             errno = res = 0;
         else
-            perror_msg("Can't delete file '%s'", path);
+            perror_msg("Can't delete file '%s'", name);
     }
 
-    free(path);
     return res;
 }
 
@@ -1164,14 +1239,22 @@ DIR *dd_init_next_file(struct dump_dir *dd)
 {
 //    if (!dd->locked)
 //        error_msg_and_die("dump_dir is not opened"); /* bug */
+    int opendir_fd = dup(dd->dd_fd);
+    if (opendir_fd < 0)
+    {
+        perror_msg("dd_init_next_file: dup(dd_fd)");
+        return NULL;
+    }
 
     if (dd->next_dir)
         closedir(dd->next_dir);
 
-    dd->next_dir = opendir(dd->dd_dirname);
+    lseek(opendir_fd, SEEK_SET, 0);
+    dd->next_dir = fdopendir(opendir_fd);
     if (!dd->next_dir)
     {
         error_msg("Can't open directory '%s'", dd->dd_dirname);
+        close(opendir_fd);
     }
 
     return dd->next_dir;
@@ -1185,7 +1268,7 @@ int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name)
     struct dirent *dent;
     while ((dent = readdir(dd->next_dir)) != NULL)
     {
-        if (is_regular_file(dent, dd->dd_dirname))
+        if (is_regular_file_at(dent, dd->dd_fd))
         {
             if (short_name)
                 *short_name = xstrdup(dent->d_name);
@@ -1250,6 +1333,7 @@ int dd_rename(struct dump_dir *dd, const char *new_path)
         return -1;
     }
 
+    /* Keeps the opened file descriptor valid */
     int res = rename(dd->dd_dirname, new_path);
     if (res == 0)
     {
@@ -1362,17 +1446,17 @@ int dd_copy_file(struct dump_dir *dd, const char *name, const char *source_path)
     if (!str_is_correct_filename(name))
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
 
-    char *dest = concat_path_file(dd->dd_dirname, name);
+    log_debug("copying '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
 
-    log_debug("copying '%s' to '%s'", source_path, dest);
+    unlinkat(dd->dd_fd, name, /*remove only files*/0);
+    off_t copied = copy_file_ext_at(source_path, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
+            dd->dd_uid, dd->dd_gid, O_RDONLY, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);
 
-    off_t copied = copy_file(source_path, dest, DEFAULT_DUMP_DIR_MODE | S_IROTH);
     if (copied < 0)
-        error_msg("Can't copy %s to %s", source_path, dest);
+        error_msg("Can't copy %s to %s at '%s'", source_path, name, dd->dd_dirname);
     else
         log_debug("copied %li bytes", (unsigned long)copied);
 
-    free(dest);
     return copied < 0;
 }
 
@@ -1381,17 +1465,17 @@ int dd_copy_file_unpack(struct dump_dir *dd, const char *name, const char *sourc
     if (!str_is_correct_filename(name))
         error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
 
-    char *dest = concat_path_file(dd->dd_dirname, name);
+    log_debug("unpacking '%s' to '%s' at '%s'", source_path, name, dd->dd_dirname);
 
-    log_debug("unpacking '%s' to '%s'", source_path, dest);
+    unlinkat(dd->dd_fd, name, /*remove only files*/0);
+    off_t copied = decompress_file_ext_at(source_path, dd->dd_fd, name, DEFAULT_DUMP_DIR_MODE,
+            dd->dd_uid, dd->dd_gid, O_RDONLY, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT);
 
-    off_t copied = decompress_file(source_path, dest, DEFAULT_DUMP_DIR_MODE | S_IROTH);
     if (copied != 0)
-        error_msg("Can't copy %s to %s", source_path, dest);
+        error_msg("Can't copy %s to %s at '%s'", source_path, name, dd->dd_dirname);
     else
-        log_debug("unpackaged file '%s'", dest);
+        log_debug("unpackaged file '%s'", source_path);
 
-    free(dest);
     return copied < 0;
 
 }
diff --git a/src/lib/problem_data.c b/src/lib/problem_data.c
index ef76406..185e2ae 100644
--- a/src/lib/problem_data.c
+++ b/src/lib/problem_data.c
@@ -319,14 +319,14 @@ static const char *const always_text_files[] = {
     FILENAME_OS_RELEASE,
     NULL
 };
-static char* is_text_file(const char *name, ssize_t *sz)
+static char* is_text_file_at(int dir_fd, const char *name, ssize_t *sz)
 {
     /* We were using magic.h API to check for file being text, but it thinks
      * that file containing just "0" is not text (!!)
      * So, we do it ourself.
      */
 
-    int fd = open(name, O_RDONLY);
+    int fd = secure_openat_read(dir_fd, name);
     if (fd < 0)
         return NULL; /* it's not text (because it does not exist! :) */
 
@@ -439,7 +439,7 @@ void problem_data_load_from_dump_dir(problem_data_t *problem_data, struct dump_d
         }
 
         ssize_t sz = 4*1024;
-        char *text = is_text_file(full_name, &sz);
+        char *text = is_text_file_at(dd->dd_fd, short_name, &sz);
         if (!text || text == HUGE_TEXT)
         {
             int flag = !text ? CD_FLAG_BIN : (CD_FLAG_BIN+CD_FLAG_BIGTXT);
diff --git a/src/lib/xfuncs.c b/src/lib/xfuncs.c
index 1ce44aa..979c7b8 100644
--- a/src/lib/xfuncs.c
+++ b/src/lib/xfuncs.c
@@ -331,6 +331,12 @@ int xopen(const char *pathname, int flags)
     return xopen3(pathname, flags, 0666);
 }
 
+void xunlinkat(int dir_fd, const char *pathname, int flags)
+{
+    if (unlinkat(dir_fd, pathname, flags))
+        perror_msg_and_die("Can't remove file '%s'", pathname);
+}
+
 void xunlink(const char *pathname)
 {
     if (unlink(pathname))
@@ -359,21 +365,29 @@ int open_or_warn(const char *pathname, int flags)
  * do not report the type, they report DT_UNKNOWN for every dirent
  * (and this is not a bug in filesystem, this is allowed by standards).
  */
-int is_regular_file(struct dirent *dent, const char *dirname)
+int is_regular_file_at(struct dirent *dent, int dir_fd)
 {
     if (dent->d_type == DT_REG)
         return 1;
     if (dent->d_type != DT_UNKNOWN)
         return 0;
 
-    char *fullname = xasprintf("%s/%s", dirname, dent->d_name);
     struct stat statbuf;
-    int r = lstat(fullname, &statbuf);
-    free(fullname);
+    int r = fstatat(dir_fd, dent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW);
 
     return r == 0 && S_ISREG(statbuf.st_mode);
 }
 
+int is_regular_file(struct dirent *dent, const char *dirname)
+{
+    int dir_fd = open(dirname, O_DIRECTORY);
+    if (dir_fd < 0)
+        return 0;
+    int r = is_regular_file_at(dent, dir_fd);
+    close(dir_fd);
+    return r;
+}
+
 /* Is it "." or ".."? */
 /* abrtlib candidate */
 bool dot_or_dotdot(const char *filename)
diff --git a/tests/dump_dir.at b/tests/dump_dir.at
index 19584d1..31c320e 100644
--- a/tests/dump_dir.at
+++ b/tests/dump_dir.at
@@ -2,6 +2,162 @@
 
 AT_BANNER([dump_dir])
 
+## --------- ##
+## dd_sanity ##
+## --------- ##
+
+AT_TESTFUN([dd_sanity],
+[[
+#include "internal_libreport.h"
+#include <errno.h>
+#include <assert.h>
+
+void validate_dump_dir_contents(struct dump_dir *dd)
+{
+    int items = 0;
+    assert(dd_exist(dd, FILENAME_TIME));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_KERNEL));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_HOSTNAME));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_ARCHITECTURE));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_OS_INFO));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_OS_RELEASE));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_OS_RELEASE));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_TYPE));
+    ++items;
+
+    assert(dd_exist(dd, FILENAME_LAST_OCCURRENCE));
+    ++items;
+
+    assert(dd_exist(dd, "at_test_text"));
+    assert(dd_get_item_size(dd, "at_test_text") == 3);
+    ++items;
+
+    assert(dd_exist(dd, "at_test_binary"));
+    assert(dd_get_item_size(dd, "at_test_binary") == 4);
+    ++items;
+
+    assert(dd_exist(dd, "at_test_services"));
+    ++items;
+
+    dd_save_text(dd, "at_test_to_delete", "deleted");
+    assert(dd_exist(dd, "at_test_to_delete"));
+    dd_delete_item(dd, "at_test_to_delete");
+    assert(!dd_exist(dd, "at_test_to_delete"));
+
+    DIR *d1 = dd_init_next_file(dd);
+    assert(d1 != NULL);
+
+    int counter = 0;
+    char *short_name, *full_name;
+    while (dd_get_next_file(dd, &short_name, &full_name))
+    {
+        ++counter;
+
+
+        printf("Iter = %s\n", short_name);
+
+        assert(short_name != NULL);
+        assert(full_name != NULL);
+        assert(strcmp(short_name, strrchr(full_name, '/') + 1) == 0);
+        assert(strncmp(dd->dd_dirname, full_name, strlen(dd->dd_dirname)) == 0);
+        assert(full_name[strlen(dd->dd_dirname)] == '/');
+    }
+
+    printf("Items = %d, Counter = %d\n", items, counter);
+    assert(items == counter);
+
+    DIR *d2 = dd_init_next_file(dd);
+    assert(d2 != NULL);
+
+    while (dd_get_next_file(dd, &short_name, &full_name))
+        --counter;
+
+    assert(counter == 0);
+}
+
+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 = '/';
+
+    printf("Dump dir path: %s\n", template);
+
+    fprintf(stderr, "Create new dump directory\n");
+    struct dump_dir *dd = dd_create(template, (uid_t)-1, 0640);
+    assert(dd != NULL || !"Cannot create new dump directory");
+
+    dd_create_basic_files(dd, geteuid(), NULL);
+    dd_save_text(dd, FILENAME_TYPE, "attest");
+
+    dd_save_text(dd, "at_test_text", "foo");
+    assert(dd_exist(dd, "at_test_text"));
+
+    dd_save_binary(dd, "at_test_binary", "blah", 4);
+    assert(dd_exist(dd, "at_test_binary"));
+
+    dd_copy_file(dd, "at_test_services", "/etc/services");
+
+    fprintf(stderr, "Test newly created dump directory\n");
+    validate_dump_dir_contents(dd);
+    dd_close(dd);
+
+
+    fprintf(stderr, "Test opened dump directory\n");
+    dd = dd_opendir(template, /*for writing*/0);
+    assert(dd != NULL || !"Cannot open the dump directory");
+    validate_dump_dir_contents(dd);
+    dd_close(dd);
+
+
+    fprintf(stderr, "Test renamed dump directory\n");
+    dd = dd_opendir(template, /*for writing*/0);
+    assert(dd != NULL || !"Cannot open the dump directory second time");
+
+    *(last_slash+1) = 'X';
+    assert(dd_rename(dd, template) == 0 || !"Cannot rename the dump directory");
+
+    validate_dump_dir_contents(dd);
+    dd_close(dd);
+
+
+    fprintf(stderr, "Test opened renamed dump directory\n");
+    assert(dd != NULL || !"Cannot open the renamed dump directory");
+    dd = dd_opendir(template, /*for writing*/0);
+    validate_dump_dir_contents(dd);
+
+    assert(dd_delete(dd) == 0);
+
+    *last_slash = '\0';
+    assert(rmdir(template) == 0);
+    return EXIT_SUCCESS;
+}
+]])
+
 ## -------------- ##
 ## recursive_lock ##
 ## -------------- ##
-- 
2.1.0