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

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