From 55ae0eaea8684aa78089bbd0c2116e0c8cb25585 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 1 Feb 2011 16:04:30 +0100 Subject: abrt-cli -r DIR: copy non-writable DIR into $HOME/abrt/spool Signed-off-by: Denys Vlasenko --- src/lib/Makefile.am | 1 + src/lib/copy_file_recursive.c | 139 ++++++++++++++++++++++++++++++++++++++++++ src/lib/create_dump_dir.c | 2 + src/lib/dump_dir.c | 50 ++++++++++++--- 4 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 src/lib/copy_file_recursive.c (limited to 'src/lib') diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 89cad8ab..aece0533 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -20,6 +20,7 @@ libreport_la_SOURCES = \ read_write.c read_write.h \ logging.c logging.h \ copyfd.c \ + copy_file_recursive.c \ concat_path_file.c \ append_to_malloced_string.c \ overlapping_strcpy.c \ diff --git a/src/lib/copy_file_recursive.c b/src/lib/copy_file_recursive.c new file mode 100644 index 00000000..c3f021c7 --- /dev/null +++ b/src/lib/copy_file_recursive.c @@ -0,0 +1,139 @@ +/* + Copyright (C) 2011 ABRT team + Copyright (C) 2011 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "abrtlib.h" + +int copy_file_recursive(const char *source, const char *dest) +{ + /* This is a recursive function, try to minimize stack usage */ + /* NB: each struct stat is ~100 bytes */ + struct stat source_stat; + struct stat dest_stat; + int retval = 0; + int dest_exists = 0; + + if (strcmp(source, ".lock") == 0) + goto skip; + + if (stat(source, &source_stat) < 0) { + perror_msg("Can't stat '%s'", source); + return -1; + } + + if (lstat(dest, &dest_stat) < 0) { + if (errno != ENOENT) { + perror_msg("Can't stat '%s'", dest); + return -1; + } + } else { + if (source_stat.st_dev == dest_stat.st_dev + && source_stat.st_ino == dest_stat.st_ino + ) { + error_msg("'%s' and '%s' are the same file", source, dest); + return -1; + } + dest_exists = 1; + } + + if (S_ISDIR(source_stat.st_mode)) { + DIR *dp; + struct dirent *d; + + if (dest_exists) { + if (!S_ISDIR(dest_stat.st_mode)) { + error_msg("Target '%s' is not a directory", dest); + return -1; + } + /* race here: user can substitute a symlink between + * this check and actual creation of files inside dest */ + } else { + /* Create DEST */ + mode_t mode = source_stat.st_mode; + /* Allow owner to access new dir (at least for now) */ + mode |= S_IRWXU; + if (mkdir(dest, mode) < 0) { + perror_msg("Can't create directory '%s'", dest); + return -1; + } + } + /* Recursively copy files in SOURCE */ + dp = opendir(source); + if (dp == NULL) { + retval = -1; + goto ret; + } + + while (retval == 0 && (d = readdir(dp)) != NULL) { + char *new_source, *new_dest; + + if (dot_or_dotdot(d->d_name)) + continue; + new_source = concat_path_file(source, d->d_name); + new_dest = concat_path_file(dest, d->d_name); + if (copy_file_recursive(new_source, new_dest) < 0) + retval = -1; + free(new_source); + free(new_dest); + } + closedir(dp); + + goto ret; + } + + if (S_ISREG(source_stat.st_mode)) { + int src_fd; + int dst_fd; + mode_t new_mode; + + src_fd = open(source, O_RDONLY); + if (src_fd < 0) { + perror_msg("Can't open '%s'", source); + return -1; + } + + /* Do not try to open with weird mode fields */ + new_mode = source_stat.st_mode; + + // security problem versus (sym)link attacks + // dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); + /* safe way: */ + dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); + if (dst_fd < 0) { + close(src_fd); + return -1; + } + + if (copyfd_eof(src_fd, dst_fd, COPYFD_SPARSE) == -1) + retval = -1; + close(src_fd); + /* Careful: do check that buffered writes succeeded... */ + if (close(dst_fd) < 0) { + perror_msg("Error writing to '%s'", dest); + retval = -1; + } + goto ret; + } + + /* Neither dir not regular file: skip */ + + skip: + log("Skipping '%s'", source); + ret: + return retval; +} diff --git a/src/lib/create_dump_dir.c b/src/lib/create_dump_dir.c index 652a16c6..2caf9af9 100644 --- a/src/lib/create_dump_dir.c +++ b/src/lib/create_dump_dir.c @@ -23,6 +23,8 @@ static struct dump_dir *try_dd_create(const char *base_dir_name, const char *dir { char *path = concat_path_file(base_dir_name, dir_name); struct dump_dir *dd = dd_create(path, (uid_t)-1L); + if (dd) + dd_create_basic_files(dd, (uid_t)-1L); free(path); return dd; } diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c index 6b7fc6df..cacedaee 100644 --- a/src/lib/dump_dir.c +++ b/src/lib/dump_dir.c @@ -282,8 +282,21 @@ struct dump_dir *dd_opendir(const char *dir, int flags) dir = dd->dd_dir = rm_trailing_slashes(dir); + errno = 0; if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP) < 0) { + if ((flags & DD_OPEN_READONLY) && errno == EACCES) + { + /* Directory is not writable. If it seems to be readable, + * return "read only" dd, not NULL */ + struct stat stat_buf; + if (stat(dir, &stat_buf) == 0 + && S_ISDIR(stat_buf.st_mode) + && access(dir, R_OK) == 0 + ) { + return dd; + } + } if (errno == EISDIR) { /* EISDIR: dd_lock can lock the dir, but it sees no time file there, @@ -378,12 +391,31 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) return NULL; } + bool created_parents = false; + try_again: /* Was creating it with mode 0700 and user as the owner, but this allows * the user to replace any file in the directory, changing security-sensitive data * (e.g. "uid", "analyzer", "executable") */ if (mkdir(dir, 0750) == -1) { + int err = errno; + if (!created_parents && errno == ENOENT) + { + char *p = dd->dd_dir + 1; + while ((p = strchr(p, '/')) != NULL) + { + *p = '\0'; + int r = (mkdir(dd->dd_dir, 0755) == 0 || errno == EEXIST); + *p++ = '/'; + if (!r) + goto report_err; + } + created_parents = true; + goto try_again; + } + report_err: + errno = err; perror_msg("Can't create directory '%s'", dir); dd_close(dd); return NULL; @@ -430,12 +462,19 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) } } + return dd; +} + +void dd_create_basic_files(struct dump_dir *dd, uid_t uid) +{ char long_str[sizeof(long) * 3 + 2]; time_t t = time(NULL); sprintf(long_str, "%lu", (long)t); dd_save_text(dd, FILENAME_TIME, long_str); + if (uid == (uid_t)-1) + uid = getuid(); sprintf(long_str, "%lu", (long)uid); dd_save_text(dd, FILENAME_UID, long_str); @@ -448,11 +487,8 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); if (!release) release = load_text_file("/etc/redhat-release", /*flags:*/ 0); - strchrnul(release, '\n')[0] = '\0'; dd_save_text(dd, FILENAME_RELEASE, release); free(release); - - return dd; } static int delete_file_dir(const char *dir, bool skip_lock_file) @@ -608,8 +644,8 @@ static bool save_binary_file(const char *path, const char* data, unsigned size, char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags) { - if (!dd->locked) - error_msg_and_die("dump_dir is not opened"); /* bug */ +// if (!dd->locked) +// error_msg_and_die("dump_dir is not opened"); /* bug */ char *full_path = concat_path_file(dd->dd_dir, name); char *ret = load_text_file(full_path, flags); @@ -645,8 +681,8 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns DIR *dd_init_next_file(struct dump_dir *dd) { - if (!dd->locked) - error_msg_and_die("dump_dir is not opened"); /* bug */ +// if (!dd->locked) +// error_msg_and_die("dump_dir is not opened"); /* bug */ if (dd->next_dir) closedir(dd->next_dir); -- cgit