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

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