Blame SOURCES/0132-lib-fix-races-in-dump-directory-handling-code.patch

4b6aa8
From 1951e7282043dfe1268d492aea056b554baedb75 Mon Sep 17 00:00:00 2001
4b6aa8
From: Jakub Filak <jfilak@redhat.com>
4b6aa8
Date: Fri, 24 Apr 2015 10:57:23 +0200
4b6aa8
Subject: [LIBREPORT PATCH] lib: fix races in dump directory handling code
4b6aa8
4b6aa8
Florian Weimer <fweimer@redhat.com>:
4b6aa8
4b6aa8
    dd_opendir() should keep a file handle (opened with O_DIRECTORY) and
4b6aa8
    use openat() and similar functions to access files in it.
4b6aa8
4b6aa8
    ...
4b6aa8
4b6aa8
    The file system manipulation functions should guard against hard
4b6aa8
    links (check that link count is <= 1, just as in the user coredump
4b6aa8
    code in abrt-hook-ccpp), possibly after opening the file
4b6aa8
    with O_PATH first to avoid side effects on open/close.
4b6aa8
4b6aa8
Related: #1214745
4b6aa8
4b6aa8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
4b6aa8
---
4b6aa8
 src/include/dump_dir.h           |   7 +
4b6aa8
 src/include/internal_libreport.h |   4 +
4b6aa8
 src/lib/dump_dir.c               | 417 +++++++++++++++++++++++----------------
4b6aa8
 src/lib/problem_data.c           |   6 +-
4b6aa8
 src/lib/xfuncs.c                 |  22 ++-
4b6aa8
 5 files changed, 275 insertions(+), 181 deletions(-)
4b6aa8
4b6aa8
diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h
4b6aa8
index 8f672d3..d675362 100644
4b6aa8
--- a/src/include/dump_dir.h
4b6aa8
+++ b/src/include/dump_dir.h
4b6aa8
@@ -34,6 +34,12 @@ extern "C" {
4b6aa8
 
4b6aa8
 /* Utility function */
4b6aa8
 int create_symlink_lockfile(const char *filename, const char *pid_str);
4b6aa8
+int create_symlink_lockfile_at(int dir_fd, const char *filename, const char *pid_str);
4b6aa8
+
4b6aa8
+/* Opens filename for reading relatively to a directory represented by dir_fd.
4b6aa8
+ * The function fails if the file is symbolic link, directory or hard link.
4b6aa8
+ */
4b6aa8
+int secure_openat_read(int dir_fd, const char *filename);
4b6aa8
 
4b6aa8
 enum {
4b6aa8
     DD_FAIL_QUIETLY_ENOENT = (1 << 0),
4b6aa8
@@ -57,6 +63,7 @@ struct dump_dir {
4b6aa8
     mode_t mode;
4b6aa8
     time_t dd_time;
4b6aa8
     char *dd_type;
4b6aa8
+    int dd_fd;
4b6aa8
 };
4b6aa8
 
4b6aa8
 void dd_close(struct dump_dir *dd);
4b6aa8
diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h
4b6aa8
index 8d84fd4..d35d715 100644
4b6aa8
--- a/src/include/internal_libreport.h
4b6aa8
+++ b/src/include/internal_libreport.h
4b6aa8
@@ -406,6 +406,8 @@ int xopen3(const char *pathname, int flags, int mode);
4b6aa8
 int xopen(const char *pathname, int flags);
4b6aa8
 #define xunlink libreport_xunlink
4b6aa8
 void xunlink(const char *pathname);
4b6aa8
+#define xunlinkat libreport_xunlinkat
4b6aa8
+void xunlinkat(int dir_fd, const char *pathname, int flags);
4b6aa8
 
4b6aa8
 /* Just testing dent->d_type == DT_REG is wrong: some filesystems
4b6aa8
  * do not report the type, they report DT_UNKNOWN for every dirent
4b6aa8
@@ -415,6 +417,8 @@ void xunlink(const char *pathname);
4b6aa8
  */
4b6aa8
 #define is_regular_file libreport_is_regular_file
4b6aa8
 int is_regular_file(struct dirent *dent, const char *dirname);
4b6aa8
+#define is_regular_file_at libreport_is_regular_file_at
4b6aa8
+int is_regular_file_at(struct dirent *dent, int dir_fd);
4b6aa8
 
4b6aa8
 #define dot_or_dotdot libreport_dot_or_dotdot
4b6aa8
 bool dot_or_dotdot(const char *filename);
4b6aa8
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
4b6aa8
index 0048faf..16cd987 100644
4b6aa8
--- a/src/lib/dump_dir.c
4b6aa8
+++ b/src/lib/dump_dir.c
4b6aa8
@@ -85,6 +85,7 @@
4b6aa8
 
4b6aa8
 
4b6aa8
 static char *load_text_file(const char *path, unsigned flags);
4b6aa8
+static char *load_text_file_at(int dir_fd, const char *name, unsigned flags);
4b6aa8
 static void copy_file_from_chroot(struct dump_dir* dd, const char *name,
4b6aa8
         const char *chroot_dir, const char *file_path);
4b6aa8
 
4b6aa8
@@ -98,10 +99,10 @@ static bool isdigit_str(const char *str)
4b6aa8
     return true;
4b6aa8
 }
4b6aa8
 
4b6aa8
-static bool exist_file_dir(const char *path)
4b6aa8
+static bool exist_file_dir_at(int dir_fd, const char *name)
4b6aa8
 {
4b6aa8
     struct stat buf;
4b6aa8
-    if (stat(path, &buf) == 0)
4b6aa8
+    if (fstatat(dir_fd, name, &buf, AT_SYMLINK_NOFOLLOW) == 0)
4b6aa8
     {
4b6aa8
         if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode))
4b6aa8
         {
4b6aa8
@@ -111,15 +112,61 @@ static bool exist_file_dir(const char *path)
4b6aa8
     return false;
4b6aa8
 }
4b6aa8
 
4b6aa8
+/* Opens the file in the three following steps:
4b6aa8
+ * 1. open the file with O_PATH (get a file descriptor for operations with
4b6aa8
+ *    inode) and O_NOFOLLOW (do not dereference symbolick links)
4b6aa8
+ * 2. stat the resulting file descriptor and fail if the opened file is not a
4b6aa8
+ *    regular file or if the number of links is greater than 1 (that means that
4b6aa8
+ *    the inode has more names (hard links))
4b6aa8
+ * 3. "re-open" the file descriptor retrieved in the first step with O_RDONLY
4b6aa8
+ *    by opening /proc/self/fd/$fd (then close the former file descriptor and
4b6aa8
+ *    return the new one).
4b6aa8
+ */
4b6aa8
+int secure_openat_read(int dir_fd, const char *pathname)
4b6aa8
+{
4b6aa8
+    static char reopen_buf[sizeof("/proc/self/fd/") + 3*sizeof(int) + 1];
4b6aa8
+
4b6aa8
+    int path_fd = openat(dir_fd, pathname, O_PATH | O_NOFOLLOW);
4b6aa8
+    if (path_fd < 0)
4b6aa8
+        return -1;
4b6aa8
+
4b6aa8
+    struct stat path_sb;
4b6aa8
+    int r = fstat(path_fd, &path_sb);
4b6aa8
+    if (r < 0)
4b6aa8
+    {
4b6aa8
+        perror_msg("stat");
4b6aa8
+        close(path_fd);
4b6aa8
+        return -1;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (!S_ISREG(path_sb.st_mode) || path_sb.st_nlink > 1)
4b6aa8
+    {
4b6aa8
+        log_notice("Path isn't a regular file or has more links (%lu)", path_sb.st_nlink);
4b6aa8
+        errno = EINVAL;
4b6aa8
+        close(path_fd);
4b6aa8
+        return -1;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (snprintf(reopen_buf, sizeof(reopen_buf), "/proc/self/fd/%d", path_fd) >= sizeof(reopen_buf)) {
4b6aa8
+        error_msg("BUG: too long path to a file descriptor");
4b6aa8
+        abort();
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    const int fd = open(reopen_buf, O_RDONLY);
4b6aa8
+    close(path_fd);
4b6aa8
+
4b6aa8
+    return fd;
4b6aa8
+}
4b6aa8
+
4b6aa8
 /* Returns value less than 0 if the file is not readable or
4b6aa8
  * if the file doesn't contain valid unixt time stamp.
4b6aa8
  *
4b6aa8
  * Any possible failure will be logged.
4b6aa8
  */
4b6aa8
-static time_t parse_time_file(const char *filename)
4b6aa8
+static time_t parse_time_file_at(int dir_fd, const char *filename)
4b6aa8
 {
4b6aa8
     /* Open input file, and parse it. */
4b6aa8
-    int fd = open(filename, O_RDONLY | O_NOFOLLOW);
4b6aa8
+    int fd = secure_openat_read(dir_fd, filename);
4b6aa8
     if (fd < 0)
4b6aa8
     {
4b6aa8
         VERB2 pwarn_msg("Can't open '%s'", filename);
4b6aa8
@@ -183,9 +230,9 @@ static time_t parse_time_file(const char *filename)
4b6aa8
  *  0: failed to lock (someone else has it locked)
4b6aa8
  *  1: success
4b6aa8
  */
4b6aa8
-int create_symlink_lockfile(const char* lock_file, const char* pid)
4b6aa8
+int create_symlink_lockfile_at(int dir_fd, const char* lock_file, const char* pid)
4b6aa8
 {
4b6aa8
-    while (symlink(pid, lock_file) != 0)
4b6aa8
+    while (symlinkat(pid, dir_fd, lock_file) != 0)
4b6aa8
     {
4b6aa8
         if (errno != EEXIST)
4b6aa8
         {
4b6aa8
@@ -198,7 +245,7 @@ int create_symlink_lockfile(const char* lock_file, const char* pid)
4b6aa8
         }
4b6aa8
 
4b6aa8
         char pid_buf[sizeof(pid_t)*3 + 4];
4b6aa8
-        ssize_t r = readlink(lock_file, pid_buf, sizeof(pid_buf) - 1);
4b6aa8
+        ssize_t r = readlinkat(dir_fd, lock_file, pid_buf, sizeof(pid_buf) - 1);
4b6aa8
         if (r < 0)
4b6aa8
         {
4b6aa8
             if (errno == ENOENT)
4b6aa8
@@ -230,7 +277,7 @@ int create_symlink_lockfile(const char* lock_file, const char* pid)
4b6aa8
             log("Lock file '%s' was locked by process %s, but it crashed?", lock_file, pid_buf);
4b6aa8
         }
4b6aa8
         /* The file may be deleted by now by other process. Ignore ENOENT */
4b6aa8
-        if (unlink(lock_file) != 0 && errno != ENOENT)
4b6aa8
+        if (unlinkat(dir_fd, lock_file, /*only files*/0) != 0 && errno != ENOENT)
4b6aa8
         {
4b6aa8
             perror_msg("Can't remove stale lock file '%s'", lock_file);
4b6aa8
             errno = 0;
4b6aa8
@@ -242,21 +289,21 @@ int create_symlink_lockfile(const char* lock_file, const char* pid)
4b6aa8
     return 1;
4b6aa8
 }
4b6aa8
 
4b6aa8
+int create_symlink_lockfile(const char *filename, const char *pid_str)
4b6aa8
+{
4b6aa8
+    return create_symlink_lockfile_at(AT_FDCWD, filename, pid_str);
4b6aa8
+}
4b6aa8
+
4b6aa8
 static const char *dd_check(struct dump_dir *dd)
4b6aa8
 {
4b6aa8
-    unsigned dirname_len = strlen(dd->dd_dirname);
4b6aa8
-    char filename_buf[FILENAME_MAX+1];
4b6aa8
-    strcpy(filename_buf, dd->dd_dirname);
4b6aa8
-    strcpy(filename_buf + dirname_len, "/"FILENAME_TIME);
4b6aa8
-    dd->dd_time = parse_time_file(filename_buf);
4b6aa8
+    dd->dd_time = parse_time_file_at(dd->dd_fd, FILENAME_TIME);
4b6aa8
     if (dd->dd_time < 0)
4b6aa8
     {
4b6aa8
         log_warning("Missing file: "FILENAME_TIME);
4b6aa8
         return FILENAME_TIME;
4b6aa8
     }
4b6aa8
 
4b6aa8
-    strcpy(filename_buf + dirname_len, "/"FILENAME_TYPE);
4b6aa8
-    dd->dd_type = load_text_file(filename_buf, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
4b6aa8
+    dd->dd_type = load_text_file_at(dd->dd_fd, FILENAME_TYPE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
4b6aa8
     if (!dd->dd_type || (strlen(dd->dd_type) == 0))
4b6aa8
     {
4b6aa8
         log_warning("Missing or empty file: "FILENAME_TYPE);
4b6aa8
@@ -274,17 +321,12 @@ static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags)
4b6aa8
     char pid_buf[sizeof(long)*3 + 2];
4b6aa8
     snprintf(pid_buf, sizeof(pid_buf), "%lu", (long)getpid());
4b6aa8
 
4b6aa8
-    unsigned dirname_len = strlen(dd->dd_dirname);
4b6aa8
-    char lock_buf[dirname_len + sizeof("/.lock")];
4b6aa8
-    strcpy(lock_buf, dd->dd_dirname);
4b6aa8
-    strcpy(lock_buf + dirname_len, "/.lock");
4b6aa8
-
4b6aa8
     unsigned count = NO_TIME_FILE_COUNT;
4b6aa8
 
4b6aa8
  retry:
4b6aa8
     while (1)
4b6aa8
     {
4b6aa8
-        int r = create_symlink_lockfile(lock_buf, pid_buf);
4b6aa8
+        int r = create_symlink_lockfile_at(dd->dd_fd, ".lock", pid_buf);
4b6aa8
         if (r < 0)
4b6aa8
             return r; /* error */
4b6aa8
         if (r > 0)
4b6aa8
@@ -304,8 +346,8 @@ static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags)
4b6aa8
          */
4b6aa8
         if (missing_file)
4b6aa8
         {
4b6aa8
-            xunlink(lock_buf);
4b6aa8
-            log_warning("Unlocked '%s' (no or corrupted '%s' file)", lock_buf, missing_file);
4b6aa8
+            xunlinkat(dd->dd_fd, ".lock", /*only files*/0);
4b6aa8
+            log_warning("Unlocked '%s' (no or corrupted '%s' file)", dd->dd_dirname, missing_file);
4b6aa8
             if (--count == 0 || flags & DD_DONT_WAIT_FOR_LOCK)
4b6aa8
             {
4b6aa8
                 errno = EISDIR; /* "this is an ordinary dir, not dump dir" */
4b6aa8
@@ -326,13 +368,9 @@ static void dd_unlock(struct dump_dir *dd)
4b6aa8
     {
4b6aa8
         dd->locked = 0;
4b6aa8
 
4b6aa8
-        unsigned dirname_len = strlen(dd->dd_dirname);
4b6aa8
-        char lock_buf[dirname_len + sizeof("/.lock")];
4b6aa8
-        strcpy(lock_buf, dd->dd_dirname);
4b6aa8
-        strcpy(lock_buf + dirname_len, "/.lock");
4b6aa8
-        xunlink(lock_buf);
4b6aa8
+        xunlinkat(dd->dd_fd, ".lock", /*only files*/0);
4b6aa8
 
4b6aa8
-        log_info("Unlocked '%s'", lock_buf);
4b6aa8
+        log_info("Unlocked '%s/.lock'", dd->dd_dirname);
4b6aa8
     }
4b6aa8
 }
4b6aa8
 
4b6aa8
@@ -340,17 +378,16 @@ static inline struct dump_dir *dd_init(void)
4b6aa8
 {
4b6aa8
     struct dump_dir* dd = (struct dump_dir*)xzalloc(sizeof(struct dump_dir));
4b6aa8
     dd->dd_time = -1;
4b6aa8
+    dd->dd_fd = -1;
4b6aa8
     return dd;
4b6aa8
 }
4b6aa8
 
4b6aa8
-int dd_exist(const struct dump_dir *dd, const char *path)
4b6aa8
+int dd_exist(const struct dump_dir *dd, const char *name)
4b6aa8
 {
4b6aa8
-    if (!str_is_correct_filename(path))
4b6aa8
-        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", path);
4b6aa8
+    if (!str_is_correct_filename(name))
4b6aa8
+        error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name);
4b6aa8
 
4b6aa8
-    char *full_path = concat_path_file(dd->dd_dirname, path);
4b6aa8
-    int ret = exist_file_dir(full_path);
4b6aa8
-    free(full_path);
4b6aa8
+    const int ret = exist_file_dir_at(dd->dd_fd, name);
4b6aa8
     return ret;
4b6aa8
 }
4b6aa8
 
4b6aa8
@@ -360,6 +397,7 @@ void dd_close(struct dump_dir *dd)
4b6aa8
         return;
4b6aa8
 
4b6aa8
     dd_unlock(dd);
4b6aa8
+    close(dd->dd_fd);
4b6aa8
     if (dd->next_dir)
4b6aa8
     {
4b6aa8
         closedir(dd->next_dir);
4b6aa8
@@ -384,10 +422,13 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
     struct dump_dir *dd = dd_init();
4b6aa8
 
4b6aa8
     dir = dd->dd_dirname = rm_trailing_slashes(dir);
4b6aa8
-
4b6aa8
+    dd->dd_fd = open(dir, O_DIRECTORY | O_NOFOLLOW);
4b6aa8
     struct stat stat_buf;
4b6aa8
-    if (stat(dir, &stat_buf) != 0)
4b6aa8
+    if (dd->dd_fd < 0)
4b6aa8
         goto cant_access;
4b6aa8
+    if (fstat(dd->dd_fd, &stat_buf) != 0)
4b6aa8
+        goto cant_access;
4b6aa8
+
4b6aa8
     /* & 0666 should remove the executable bit */
4b6aa8
     dd->mode = (stat_buf.st_mode & 0666);
4b6aa8
 
4b6aa8
@@ -397,11 +438,12 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
         if ((flags & DD_OPEN_READONLY) && errno == EACCES)
4b6aa8
         {
4b6aa8
             /* Directory is not writable. If it seems to be readable,
4b6aa8
-             * return "read only" dd, not NULL */
4b6aa8
-            if (stat(dir, &stat_buf) == 0
4b6aa8
-             && S_ISDIR(stat_buf.st_mode)
4b6aa8
-             && access(dir, R_OK) == 0
4b6aa8
-            ) {
4b6aa8
+             * return "read only" dd, not NULL
4b6aa8
+             *
4b6aa8
+             * Does the directory have 'x' flag?
4b6aa8
+             */
4b6aa8
+            if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) == 0)
4b6aa8
+            {
4b6aa8
                 if(dd_check(dd) != NULL)
4b6aa8
                 {
4b6aa8
                     dd_close(dd);
4b6aa8
@@ -444,10 +486,9 @@ struct dump_dir *dd_opendir(const char *dir, int flags)
4b6aa8
     if (geteuid() == 0)
4b6aa8
     {
4b6aa8
         /* In case caller would want to create more files, he'll need uid:gid */
4b6aa8
-        struct stat stat_buf;
4b6aa8
-        if (stat(dir, &stat_buf) != 0 || !S_ISDIR(stat_buf.st_mode))
4b6aa8
+        if (fstat(dd->dd_fd, &stat_buf) != 0)
4b6aa8
         {
4b6aa8
-            error_msg("Can't stat '%s', or it is not a directory", dir);
4b6aa8
+            error_msg("Can't stat '%s'", dir);
4b6aa8
             dd_close(dd);
4b6aa8
             return NULL;
4b6aa8
         }
4b6aa8
@@ -542,8 +583,7 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
4b6aa8
          * dd_create("dir/..") and similar are madness, refuse them.
4b6aa8
          */
4b6aa8
         error_msg("Bad dir name '%s'", dir);
4b6aa8
-        dd_close(dd);
4b6aa8
-        return NULL;
4b6aa8
+        goto fail;
4b6aa8
     }
4b6aa8
 
4b6aa8
     /* Was creating it with mode 0700 and user as the owner, but this allows
4b6aa8
@@ -559,22 +599,31 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
4b6aa8
     if (r != 0)
4b6aa8
     {
4b6aa8
         perror_msg("Can't create directory '%s'", dir);
4b6aa8
-        dd_close(dd);
4b6aa8
-        return NULL;
4b6aa8
+        goto fail;
4b6aa8
     }
4b6aa8
 
4b6aa8
-    if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0)
4b6aa8
+    dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW);
4b6aa8
+    if (dd->dd_fd < 0)
4b6aa8
     {
4b6aa8
-        dd_close(dd);
4b6aa8
-        return NULL;
4b6aa8
+        perror_msg("Can't open newly created directory '%s'", dir);
4b6aa8
+        goto fail;
4b6aa8
     }
4b6aa8
 
4b6aa8
+    struct stat stat_sb;
4b6aa8
+    if (fstat(dd->dd_fd, &stat_sb) < 0)
4b6aa8
+    {
4b6aa8
+        perror_msg("stat(%s)", dd->dd_dirname);
4b6aa8
+        goto fail;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0)
4b6aa8
+        goto fail;
4b6aa8
+
4b6aa8
     /* mkdir's mode (above) can be affected by umask, fix it */
4b6aa8
-    if (chmod(dir, dir_mode) == -1)
4b6aa8
+    if (fchmod(dd->dd_fd, dir_mode) == -1)
4b6aa8
     {
4b6aa8
         perror_msg("Can't change mode of '%s'", dir);
4b6aa8
-        dd_close(dd);
4b6aa8
-        return NULL;
4b6aa8
+        goto fail;
4b6aa8
     }
4b6aa8
 
4b6aa8
     dd->dd_uid = (uid_t)-1L;
4b6aa8
@@ -616,6 +665,10 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
4b6aa8
     }
4b6aa8
 
4b6aa8
     return dd;
4b6aa8
+
4b6aa8
+fail:
4b6aa8
+    dd_close(dd);
4b6aa8
+    return NULL;
4b6aa8
 }
4b6aa8
 
4b6aa8
 /* Resets ownership of the given directory to UID and GID according to values
4b6aa8
@@ -623,7 +676,7 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int
4b6aa8
  */
4b6aa8
 int dd_reset_ownership(struct dump_dir *dd)
4b6aa8
 {
4b6aa8
-    const int r =lchown(dd->dd_dirname, dd->dd_uid, dd->dd_gid);
4b6aa8
+    const int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid);
4b6aa8
     if (r < 0)
4b6aa8
     {
4b6aa8
         perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname,
4b6aa8
@@ -740,59 +793,39 @@ void dd_sanitize_mode_and_owner(struct dump_dir *dd)
4b6aa8
     if (!dd->locked)
4b6aa8
         error_msg_and_die("dump_dir is not opened"); /* bug */
4b6aa8
 
4b6aa8
-    DIR *d = opendir(dd->dd_dirname);
4b6aa8
-    if (!d)
4b6aa8
-        return;
4b6aa8
-
4b6aa8
-    struct dirent *dent;
4b6aa8
-    while ((dent = readdir(d)) != NULL)
4b6aa8
+    dd_init_next_file(dd);
4b6aa8
+    char *short_name;
4b6aa8
+    while (dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
4b6aa8
     {
4b6aa8
-        if (dent->d_name[0] == '.') /* ".lock", ".", ".."? skip */
4b6aa8
-            continue;
4b6aa8
-        char *full_path = concat_path_file(dd->dd_dirname, dent->d_name);
4b6aa8
-        struct stat statbuf;
4b6aa8
-        if (lstat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
4b6aa8
-        {
4b6aa8
-            if ((statbuf.st_mode & 0777) != dd->mode)
4b6aa8
-            {
4b6aa8
-                /* We open the file only for fchmod()
4b6aa8
-                 *
4b6aa8
-                 * We use fchmod() because chmod() changes the permissions of
4b6aa8
-                 * the file specified whose pathname is given in path, which
4b6aa8
-                 * is dereferenced if it is a symbolic link.
4b6aa8
-                 */
4b6aa8
-                int fd = open(full_path, O_RDONLY | O_NOFOLLOW, dd->mode);
4b6aa8
-                if (fd >= 0)
4b6aa8
-                {
4b6aa8
-                    if (fchmod(fd, dd->mode) != 0)
4b6aa8
-                    {
4b6aa8
-                        perror_msg("Can't change '%s' mode to 0%o", full_path,
4b6aa8
-                                   (unsigned)dd->mode);
4b6aa8
-                    }
4b6aa8
-                    close(fd);
4b6aa8
-                }
4b6aa8
-                else
4b6aa8
-                {
4b6aa8
-                    perror_msg("Can't open regular file '%s'", full_path);
4b6aa8
-                }
4b6aa8
-            }
4b6aa8
-            if (statbuf.st_uid != dd->dd_uid || statbuf.st_gid != dd->dd_gid)
4b6aa8
-            {
4b6aa8
-                if (lchown(full_path, dd->dd_uid, dd->dd_gid) != 0)
4b6aa8
-                {
4b6aa8
-                    perror_msg("Can't change '%s' ownership to %lu:%lu", full_path,
4b6aa8
-                               (long)dd->dd_uid, (long)dd->dd_gid);
4b6aa8
-                }
4b6aa8
-            }
4b6aa8
-        }
4b6aa8
-        free(full_path);
4b6aa8
+        /* The current process has to have read access at least */
4b6aa8
+        int fd = secure_openat_read(dd->dd_fd, short_name);
4b6aa8
+        if (fd < 0)
4b6aa8
+            goto next;
4b6aa8
+
4b6aa8
+        if (fchmod(fd, dd->mode) != 0)
4b6aa8
+            perror_msg("Can't change '%s/%s' mode to 0%o", dd->dd_dirname, short_name,
4b6aa8
+                       (unsigned)dd->mode);
4b6aa8
+
4b6aa8
+        if (fchown(fd, dd->dd_uid, dd->dd_gid) != 0)
4b6aa8
+            perror_msg("Can't change '%s/%s' ownership to %lu:%lu", dd->dd_dirname, short_name,
4b6aa8
+                       (long)dd->dd_uid, (long)dd->dd_gid);
4b6aa8
+
4b6aa8
+        close(fd);
4b6aa8
+next:
4b6aa8
+        free(short_name);
4b6aa8
     }
4b6aa8
-    closedir(d);
4b6aa8
 }
4b6aa8
 
4b6aa8
-static int delete_file_dir(const char *dir, bool skip_lock_file)
4b6aa8
+static int delete_file_dir(int dir_fd, bool skip_lock_file)
4b6aa8
 {
4b6aa8
-    DIR *d = opendir(dir);
4b6aa8
+    int opendir_fd = dup(dir_fd);
4b6aa8
+    if (opendir_fd < 0)
4b6aa8
+    {
4b6aa8
+        perror_msg("delete_file_dir: dup(dir_fd)");
4b6aa8
+        return -1;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    DIR *d = fdopendir(opendir_fd);
4b6aa8
     if (!d)
4b6aa8
     {
4b6aa8
         /* The caller expects us to error out only if the directory
4b6aa8
@@ -818,26 +851,35 @@ static int delete_file_dir(const char *dir, bool skip_lock_file)
4b6aa8
             unlink_lock_file = true;
4b6aa8
             continue;
4b6aa8
         }
4b6aa8
-        char *full_path = concat_path_file(dir, dent->d_name);
4b6aa8
-        if (unlink(full_path) == -1 && errno != ENOENT)
4b6aa8
+        if (unlinkat(dir_fd, dent->d_name, /*only files*/0) == -1 && errno != ENOENT)
4b6aa8
         {
4b6aa8
             int err = 0;
4b6aa8
             if (errno == EISDIR)
4b6aa8
             {
4b6aa8
                 errno = 0;
4b6aa8
-                err = delete_file_dir(full_path, /*skip_lock_file:*/ false);
4b6aa8
+                int subdir_fd = openat(dir_fd, dent->d_name, O_DIRECTORY);
4b6aa8
+                if (subdir_fd < 0)
4b6aa8
+                {
4b6aa8
+                    perror_msg("Can't open sub-dir'%s'", dent->d_name);
4b6aa8
+                    closedir(d);
4b6aa8
+                    return -1;
4b6aa8
+                }
4b6aa8
+                else
4b6aa8
+                {
4b6aa8
+                    err = delete_file_dir(subdir_fd, /*skip_lock_file:*/ false);
4b6aa8
+                    close(subdir_fd);
4b6aa8
+                    if (err == 0)
4b6aa8
+                        unlinkat(dir_fd, dent->d_name, AT_REMOVEDIR);
4b6aa8
+                }
4b6aa8
             }
4b6aa8
             if (errno || err)
4b6aa8
             {
4b6aa8
-                perror_msg("Can't remove '%s'", full_path);
4b6aa8
-                free(full_path);
4b6aa8
+                perror_msg("Can't remove '%s'", dent->d_name);
4b6aa8
                 closedir(d);
4b6aa8
                 return -1;
4b6aa8
             }
4b6aa8
         }
4b6aa8
-        free(full_path);
4b6aa8
     }
4b6aa8
-    closedir(d);
4b6aa8
 
4b6aa8
     /* Here we know for sure that all files/subdirs we found via readdir
4b6aa8
      * were deleted successfully. If rmdir below fails, we assume someone
4b6aa8
@@ -845,29 +887,9 @@ static int delete_file_dir(const char *dir, bool skip_lock_file)
4b6aa8
      */
4b6aa8
 
4b6aa8
     if (unlink_lock_file)
4b6aa8
-    {
4b6aa8
-        char *full_path = concat_path_file(dir, ".lock");
4b6aa8
-        xunlink(full_path);
4b6aa8
-        free(full_path);
4b6aa8
-
4b6aa8
-        unsigned cnt = RMDIR_FAIL_COUNT;
4b6aa8
-        do {
4b6aa8
-            if (rmdir(dir) == 0)
4b6aa8
-                return 0;
4b6aa8
-            /* Someone locked the dir after unlink, but before rmdir.
4b6aa8
-             * This "someone" must be dd_lock().
4b6aa8
-             * It detects this (by seeing that there is no time file)
4b6aa8
-             * and backs off at once. So we need to just retry rmdir,
4b6aa8
-             * with minimal sleep.
4b6aa8
-             */
4b6aa8
-            usleep(RMDIR_FAIL_USLEEP);
4b6aa8
-        } while (--cnt != 0);
4b6aa8
-    }
4b6aa8
+        xunlinkat(dir_fd, ".lock", /*only files*/0);
4b6aa8
 
4b6aa8
-    int r = rmdir(dir);
4b6aa8
-    if (r)
4b6aa8
-        perror_msg("Can't remove directory '%s'", dir);
4b6aa8
-    return r;
4b6aa8
+    return 0;
4b6aa8
 }
4b6aa8
 
4b6aa8
 int dd_delete(struct dump_dir *dd)
4b6aa8
@@ -878,10 +900,34 @@ int dd_delete(struct dump_dir *dd)
4b6aa8
         return -1;
4b6aa8
     }
4b6aa8
 
4b6aa8
-    int r = delete_file_dir(dd->dd_dirname, /*skip_lock_file:*/ true);
4b6aa8
+    if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0)
4b6aa8
+    {
4b6aa8
+        perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname);
4b6aa8
+        return -2;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    unsigned cnt = RMDIR_FAIL_COUNT;
4b6aa8
+    do {
4b6aa8
+        if (rmdir(dd->dd_dirname) == 0)
4b6aa8
+            break;
4b6aa8
+        /* Someone locked the dir after unlink, but before rmdir.
4b6aa8
+         * This "someone" must be dd_lock().
4b6aa8
+         * It detects this (by seeing that there is no time file)
4b6aa8
+         * and backs off at once. So we need to just retry rmdir,
4b6aa8
+         * with minimal sleep.
4b6aa8
+         */
4b6aa8
+        usleep(RMDIR_FAIL_USLEEP);
4b6aa8
+    } while (--cnt != 0);
4b6aa8
+
4b6aa8
+    if (cnt == 0)
4b6aa8
+    {
4b6aa8
+        perror_msg("Can't remove directory '%s'", dd->dd_dirname);
4b6aa8
+        return -3;
4b6aa8
+    }
4b6aa8
+
4b6aa8
     dd->locked = 0; /* delete_file_dir already removed .lock */
4b6aa8
     dd_close(dd);
4b6aa8
-    return r;
4b6aa8
+    return 0;
4b6aa8
 }
4b6aa8
 
4b6aa8
 int dd_chown(struct dump_dir *dd, uid_t new_uid)
4b6aa8
@@ -911,29 +957,37 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid)
4b6aa8
     gid_t groups_gid = pw->pw_gid;
4b6aa8
 #endif
4b6aa8
 
4b6aa8
-    int chown_res = lchown(dd->dd_dirname, owners_uid, groups_gid);
4b6aa8
+    int chown_res = fchown(dd->dd_fd, owners_uid, groups_gid);
4b6aa8
     if (chown_res)
4b6aa8
-        perror_msg("lchown('%s')", dd->dd_dirname);
4b6aa8
+        perror_msg("fchown('%s')", dd->dd_dirname);
4b6aa8
     else
4b6aa8
     {
4b6aa8
         dd_init_next_file(dd);
4b6aa8
-        char *full_name;
4b6aa8
-        while (chown_res == 0 && dd_get_next_file(dd, /*short_name*/ NULL, &full_name))
4b6aa8
+        char *short_name;
4b6aa8
+        while (chown_res == 0 && dd_get_next_file(dd, &short_name, /*full_name*/ NULL))
4b6aa8
         {
4b6aa8
-            log_debug("chowning %s", full_name);
4b6aa8
-            chown_res = lchown(full_name, owners_uid, groups_gid);
4b6aa8
+            /* The current process has to have read access at least */
4b6aa8
+            int fd = secure_openat_read(dd->dd_fd, short_name);
4b6aa8
+            if (fd < 0)
4b6aa8
+                goto next;
4b6aa8
+
4b6aa8
+            log_debug("chowning %s", short_name);
4b6aa8
+
4b6aa8
+            chown_res = fchown(fd, owners_uid, groups_gid);
4b6aa8
             if (chown_res)
4b6aa8
-                perror_msg("lchown('%s')", full_name);
4b6aa8
-            free(full_name);
4b6aa8
+                perror_msg("fchownat('%s')", short_name);
4b6aa8
+
4b6aa8
+            close(fd);
4b6aa8
+next:
4b6aa8
+            free(short_name);
4b6aa8
         }
4b6aa8
     }
4b6aa8
 
4b6aa8
     return chown_res;
4b6aa8
 }
4b6aa8
 
4b6aa8
-static char *load_text_file(const char *path, unsigned flags)
4b6aa8
+static char *load_text_from_file_descriptor(int fd, const char *path, int flags)
4b6aa8
 {
4b6aa8
-    int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
4b6aa8
     if (fd == -1)
4b6aa8
     {
4b6aa8
         if (!(flags & DD_FAIL_QUIETLY_ENOENT))
4b6aa8
@@ -988,6 +1042,20 @@ static char *load_text_file(const char *path, unsigned flags)
4b6aa8
     return strbuf_free_nobuf(buf_content);
4b6aa8
 }
4b6aa8
 
4b6aa8
+static char *load_text_file_at(int dir_fd, const char *name, unsigned flags)
4b6aa8
+{
4b6aa8
+    assert(name[0] != '/');
4b6aa8
+
4b6aa8
+    const int fd = openat(dir_fd, name, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
4b6aa8
+    return load_text_from_file_descriptor(fd, name, flags);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static char *load_text_file(const char *path, unsigned flags)
4b6aa8
+{
4b6aa8
+    const int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW));
4b6aa8
+    return load_text_from_file_descriptor(fd, path, flags);
4b6aa8
+}
4b6aa8
+
4b6aa8
 static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const char *chroot_dir, const char *file_path)
4b6aa8
 {
4b6aa8
     char *chrooted_name = concat_path_file(chroot_dir, file_path);
4b6aa8
@@ -1001,14 +1069,16 @@ static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const c
4b6aa8
     }
4b6aa8
 }
4b6aa8
 
4b6aa8
-static bool save_binary_file(const char *path, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode)
4b6aa8
+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)
4b6aa8
 {
4b6aa8
+    assert(name[0] != '/');
4b6aa8
+
4b6aa8
     /* the mode is set by the caller, see dd_create() for security analysis */
4b6aa8
-    unlink(path);
4b6aa8
-    int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT | O_NOFOLLOW, mode);
4b6aa8
+    unlinkat(dir_fd, name, /*remove only files*/0);
4b6aa8
+    int fd = openat(dir_fd, name, O_WRONLY | O_EXCL | O_CREAT | O_NOFOLLOW, mode);
4b6aa8
     if (fd < 0)
4b6aa8
     {
4b6aa8
-        perror_msg("Can't open file '%s'", path);
4b6aa8
+        perror_msg("Can't open file '%s'", name);
4b6aa8
         return false;
4b6aa8
     }
4b6aa8
 
4b6aa8
@@ -1016,7 +1086,9 @@ static bool save_binary_file(const char *path, const char* data, unsigned size,
4b6aa8
     {
4b6aa8
         if (fchown(fd, uid, gid) == -1)
4b6aa8
         {
4b6aa8
-            perror_msg("Can't change '%s' ownership to %lu:%lu", path, (long)uid, (long)gid);
4b6aa8
+            perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid);
4b6aa8
+            close(fd);
4b6aa8
+            return false;
4b6aa8
         }
4b6aa8
     }
4b6aa8
 
4b6aa8
@@ -1028,14 +1100,16 @@ static bool save_binary_file(const char *path, const char* data, unsigned size,
4b6aa8
      */
4b6aa8
     if (fchmod(fd, mode) == -1)
4b6aa8
     {
4b6aa8
-        perror_msg("Can't change mode of '%s'", path);
4b6aa8
+        perror_msg("Can't change mode of '%s'", name);
4b6aa8
+        close(fd);
4b6aa8
+        return false;
4b6aa8
     }
4b6aa8
 
4b6aa8
     unsigned r = full_write(fd, data, size);
4b6aa8
     close(fd);
4b6aa8
     if (r != size)
4b6aa8
     {
4b6aa8
-        error_msg("Can't save file '%s'", path);
4b6aa8
+        error_msg("Can't save file '%s'", name);
4b6aa8
         return false;
4b6aa8
     }
4b6aa8
 
4b6aa8
@@ -1058,11 +1132,7 @@ char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned fla
4b6aa8
     if (strcmp(name, "release") == 0)
4b6aa8
         name = FILENAME_OS_RELEASE;
4b6aa8
 
4b6aa8
-    char *full_path = concat_path_file(dd->dd_dirname, name);
4b6aa8
-    char *ret = load_text_file(full_path, flags);
4b6aa8
-    free(full_path);
4b6aa8
-
4b6aa8
-    return ret;
4b6aa8
+    return load_text_file_at(dd->dd_fd, name, flags);
4b6aa8
 }
4b6aa8
 
4b6aa8
 char* dd_load_text(const struct dump_dir *dd, const char *name)
4b6aa8
@@ -1078,9 +1148,7 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data)
4b6aa8
     if (!str_is_correct_filename(name))
4b6aa8
         error_msg_and_die("Cannot save text. '%s' is not a valid file name", name);
4b6aa8
 
4b6aa8
-    char *full_path = concat_path_file(dd->dd_dirname, name);
4b6aa8
-    save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
4b6aa8
-    free(full_path);
4b6aa8
+    save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
4b6aa8
 }
4b6aa8
 
4b6aa8
 void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, unsigned size)
4b6aa8
@@ -1091,9 +1159,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns
4b6aa8
     if (!str_is_correct_filename(name))
4b6aa8
         error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name);
4b6aa8
 
4b6aa8
-    char *full_path = concat_path_file(dd->dd_dirname, name);
4b6aa8
-    save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
4b6aa8
-    free(full_path);
4b6aa8
+    save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
4b6aa8
 }
4b6aa8
 
4b6aa8
 long dd_get_item_size(struct dump_dir *dd, const char *name)
4b6aa8
@@ -1102,21 +1168,19 @@ long dd_get_item_size(struct dump_dir *dd, const char *name)
4b6aa8
         error_msg_and_die("Cannot get item size. '%s' is not a valid file name", name);
4b6aa8
 
4b6aa8
     long size = -1;
4b6aa8
-    char *iname = concat_path_file(dd->dd_dirname, name);
4b6aa8
     struct stat statbuf;
4b6aa8
+    int r = fstatat(dd->dd_fd, name, &statbuf, AT_SYMLINK_NOFOLLOW);
4b6aa8
 
4b6aa8
-    if (lstat(iname, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
4b6aa8
+    if (r == 0 && S_ISREG(statbuf.st_mode))
4b6aa8
         size = statbuf.st_size;
4b6aa8
     else
4b6aa8
     {
4b6aa8
         if (errno == ENOENT)
4b6aa8
             size = 0;
4b6aa8
         else
4b6aa8
-            perror_msg("Can't get size of file '%s'", iname);
4b6aa8
+            perror_msg("Can't get size of file '%s'", name);
4b6aa8
     }
4b6aa8
 
4b6aa8
-    free(iname);
4b6aa8
-
4b6aa8
     return size;
4b6aa8
 }
4b6aa8
 
4b6aa8
@@ -1128,18 +1192,16 @@ int dd_delete_item(struct dump_dir *dd, const char *name)
4b6aa8
     if (!str_is_correct_filename(name))
4b6aa8
         error_msg_and_die("Cannot delete item. '%s' is not a valid file name", name);
4b6aa8
 
4b6aa8
-    char *path = concat_path_file(dd->dd_dirname, name);
4b6aa8
-    int res = unlink(path);
4b6aa8
+    int res = unlinkat(dd->dd_fd, name, /*only files*/0);
4b6aa8
 
4b6aa8
     if (res < 0)
4b6aa8
     {
4b6aa8
         if (errno == ENOENT)
4b6aa8
             errno = res = 0;
4b6aa8
         else
4b6aa8
-            perror_msg("Can't delete file '%s'", path);
4b6aa8
+            perror_msg("Can't delete file '%s'", name);
4b6aa8
     }
4b6aa8
 
4b6aa8
-    free(path);
4b6aa8
     return res;
4b6aa8
 }
4b6aa8
 
4b6aa8
@@ -1147,11 +1209,17 @@ DIR *dd_init_next_file(struct dump_dir *dd)
4b6aa8
 {
4b6aa8
 //    if (!dd->locked)
4b6aa8
 //        error_msg_and_die("dump_dir is not opened"); /* bug */
4b6aa8
+    int opendir_fd = dup(dd->dd_fd);
4b6aa8
+    if (opendir_fd < 0)
4b6aa8
+    {
4b6aa8
+        perror_msg("dd_init_next_file: dup(dd_fd)");
4b6aa8
+        return NULL;
4b6aa8
+    }
4b6aa8
 
4b6aa8
     if (dd->next_dir)
4b6aa8
         closedir(dd->next_dir);
4b6aa8
 
4b6aa8
-    dd->next_dir = opendir(dd->dd_dirname);
4b6aa8
+    dd->next_dir = fdopendir(opendir_fd);
4b6aa8
     if (!dd->next_dir)
4b6aa8
     {
4b6aa8
         error_msg("Can't open directory '%s'", dd->dd_dirname);
4b6aa8
@@ -1168,7 +1236,7 @@ int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name)
4b6aa8
     struct dirent *dent;
4b6aa8
     while ((dent = readdir(dd->next_dir)) != NULL)
4b6aa8
     {
4b6aa8
-        if (is_regular_file(dent, dd->dd_dirname))
4b6aa8
+        if (is_regular_file_at(dent, dd->dd_fd))
4b6aa8
         {
4b6aa8
             if (short_name)
4b6aa8
                 *short_name = xstrdup(dent->d_name);
4b6aa8
@@ -1233,6 +1301,7 @@ int dd_rename(struct dump_dir *dd, const char *new_path)
4b6aa8
         return -1;
4b6aa8
     }
4b6aa8
 
4b6aa8
+    /* Keeps the opened file descriptor valid */
4b6aa8
     int res = rename(dd->dd_dirname, new_path);
4b6aa8
     if (res == 0)
4b6aa8
     {
4b6aa8
diff --git a/src/lib/problem_data.c b/src/lib/problem_data.c
4b6aa8
index fc07288..ebddd3c 100644
4b6aa8
--- a/src/lib/problem_data.c
4b6aa8
+++ b/src/lib/problem_data.c
4b6aa8
@@ -279,14 +279,14 @@ static const char *const always_text_files[] = {
4b6aa8
     FILENAME_OS_RELEASE,
4b6aa8
     NULL
4b6aa8
 };
4b6aa8
-static char* is_text_file(const char *name, ssize_t *sz)
4b6aa8
+static char* is_text_file_at(int dir_fd, const char *name, ssize_t *sz)
4b6aa8
 {
4b6aa8
     /* We were using magic.h API to check for file being text, but it thinks
4b6aa8
      * that file containing just "0" is not text (!!)
4b6aa8
      * So, we do it ourself.
4b6aa8
      */
4b6aa8
 
4b6aa8
-    int fd = open(name, O_RDONLY);
4b6aa8
+    int fd = secure_openat_read(dir_fd, name);
4b6aa8
     if (fd < 0)
4b6aa8
         return NULL; /* it's not text (because it does not exist! :) */
4b6aa8
 
4b6aa8
@@ -399,7 +399,7 @@ void problem_data_load_from_dump_dir(problem_data_t *problem_data, struct dump_d
4b6aa8
         }
4b6aa8
 
4b6aa8
         ssize_t sz = 4*1024;
4b6aa8
-        char *text = is_text_file(full_name, &sz);
4b6aa8
+        char *text = is_text_file_at(dd->dd_fd, short_name, &sz);
4b6aa8
         if (!text || text == HUGE_TEXT)
4b6aa8
         {
4b6aa8
             int flag = !text ? CD_FLAG_BIN : (CD_FLAG_BIN+CD_FLAG_BIGTXT);
4b6aa8
diff --git a/src/lib/xfuncs.c b/src/lib/xfuncs.c
4b6aa8
index 1ce44aa..979c7b8 100644
4b6aa8
--- a/src/lib/xfuncs.c
4b6aa8
+++ b/src/lib/xfuncs.c
4b6aa8
@@ -331,6 +331,12 @@ int xopen(const char *pathname, int flags)
4b6aa8
     return xopen3(pathname, flags, 0666);
4b6aa8
 }
4b6aa8
 
4b6aa8
+void xunlinkat(int dir_fd, const char *pathname, int flags)
4b6aa8
+{
4b6aa8
+    if (unlinkat(dir_fd, pathname, flags))
4b6aa8
+        perror_msg_and_die("Can't remove file '%s'", pathname);
4b6aa8
+}
4b6aa8
+
4b6aa8
 void xunlink(const char *pathname)
4b6aa8
 {
4b6aa8
     if (unlink(pathname))
4b6aa8
@@ -359,21 +365,29 @@ int open_or_warn(const char *pathname, int flags)
4b6aa8
  * do not report the type, they report DT_UNKNOWN for every dirent
4b6aa8
  * (and this is not a bug in filesystem, this is allowed by standards).
4b6aa8
  */
4b6aa8
-int is_regular_file(struct dirent *dent, const char *dirname)
4b6aa8
+int is_regular_file_at(struct dirent *dent, int dir_fd)
4b6aa8
 {
4b6aa8
     if (dent->d_type == DT_REG)
4b6aa8
         return 1;
4b6aa8
     if (dent->d_type != DT_UNKNOWN)
4b6aa8
         return 0;
4b6aa8
 
4b6aa8
-    char *fullname = xasprintf("%s/%s", dirname, dent->d_name);
4b6aa8
     struct stat statbuf;
4b6aa8
-    int r = lstat(fullname, &statbuf);
4b6aa8
-    free(fullname);
4b6aa8
+    int r = fstatat(dir_fd, dent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW);
4b6aa8
 
4b6aa8
     return r == 0 && S_ISREG(statbuf.st_mode);
4b6aa8
 }
4b6aa8
 
4b6aa8
+int is_regular_file(struct dirent *dent, const char *dirname)
4b6aa8
+{
4b6aa8
+    int dir_fd = open(dirname, O_DIRECTORY);
4b6aa8
+    if (dir_fd < 0)
4b6aa8
+        return 0;
4b6aa8
+    int r = is_regular_file_at(dent, dir_fd);
4b6aa8
+    close(dir_fd);
4b6aa8
+    return r;
4b6aa8
+}
4b6aa8
+
4b6aa8
 /* Is it "." or ".."? */
4b6aa8
 /* abrtlib candidate */
4b6aa8
 bool dot_or_dotdot(const char *filename)
4b6aa8
-- 
4b6aa8
1.8.3.1
4b6aa8