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

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