From b9ad75fa602dd72b042b2ea08ce86da50746ab30 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 28 Mar 2008 17:49:31 +0000 Subject: [PATCH] copy_file: handle "cp /dev/foo file" (almost) compatibly to coreutils. (almost because we do not copy mode, which is probably wasn't intended). +61 bytes. --- coreutils/mv.c | 6 ++- include/libbb.h | 34 +++++++++-------- libbb/copy_file.c | 37 +++++++++++++++---- testsuite/cp/cp-a-files-to-dir | 3 +- testsuite/cp/cp-dev-file | 2 + testsuite/cp/cp-does-not-copy-unreadable-file | 7 +++- 6 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 testsuite/cp/cp-dev-file diff --git a/coreutils/mv.c b/coreutils/mv.c index 5d02196e3..613d4ac46 100644 --- a/coreutils/mv.c +++ b/coreutils/mv.c @@ -71,7 +71,8 @@ int mv_main(int argc, char **argv) } DO_MOVE: - if (dest_exists && !(flags & OPT_FILEUTILS_FORCE) + if (dest_exists + && !(flags & OPT_FILEUTILS_FORCE) && ((access(dest, W_OK) < 0 && isatty(0)) || (flags & OPT_FILEUTILS_INTERACTIVE)) ) { @@ -108,6 +109,9 @@ int mv_main(int argc, char **argv) goto RET_1; } } + /* FILEUTILS_RECUR also prevents nasties like + * "read from device and write contents to dst" + * instead of "create same device node" */ copy_flag = FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS; #if ENABLE_SELINUX copy_flag |= FILEUTILS_PRESERVE_SECURITY_CONTEXT; diff --git a/include/libbb.h b/include/libbb.h index 3638b7e15..afc053efb 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -223,8 +223,27 @@ extern char *skip_non_whitespace(const char *); //TODO: supply a pointer to char[11] buffer (avoid statics)? extern const char *bb_mode_string(mode_t mode); extern int is_directory(const char *name, int followLinks, struct stat *statBuf); +enum { /* DO NOT CHANGE THESE VALUES! cp.c, mv.c, install.c depend on them. */ + FILEUTILS_PRESERVE_STATUS = 1, + FILEUTILS_DEREFERENCE = 2, + FILEUTILS_RECUR = 4, + FILEUTILS_FORCE = 8, + FILEUTILS_INTERACTIVE = 0x10, + FILEUTILS_MAKE_HARDLINK = 0x20, + FILEUTILS_MAKE_SOFTLINK = 0x40, +#if ENABLE_SELINUX + FILEUTILS_PRESERVE_SECURITY_CONTEXT = 0x80, + FILEUTILS_SET_SECURITY_CONTEXT = 0x100 +#endif +}; +#define FILEUTILS_CP_OPTSTR "pdRfils" USE_SELINUX("c") extern int remove_file(const char *path, int flags); +/* NB: without FILEUTILS_RECUR in flags, it will basically "cat" + * the source, not copy (unless "source" is a directory). + * This makes "cp /dev/null file" and "install /dev/null file" (!!!) + * work coreutils-compatibly. */ extern int copy_file(const char *source, const char *dest, int flags); + enum { ACTION_RECURSE = (1 << 0), ACTION_FOLLOWLINKS = (1 << 1), @@ -1170,21 +1189,6 @@ void *md5_end(void *resbuf, md5_ctx_t *ctx); uint32_t *crc32_filltable(uint32_t *tbl256, int endian); -enum { /* DO NOT CHANGE THESE VALUES! cp.c, mv.c, install.c depend on them. */ - FILEUTILS_PRESERVE_STATUS = 1, - FILEUTILS_DEREFERENCE = 2, - FILEUTILS_RECUR = 4, - FILEUTILS_FORCE = 8, - FILEUTILS_INTERACTIVE = 0x10, - FILEUTILS_MAKE_HARDLINK = 0x20, - FILEUTILS_MAKE_SOFTLINK = 0x40, -#if ENABLE_SELINUX - FILEUTILS_PRESERVE_SECURITY_CONTEXT = 0x80, - FILEUTILS_SET_SECURITY_CONTEXT = 0x100 -#endif -}; - -#define FILEUTILS_CP_OPTSTR "pdRfils" USE_SELINUX("c") extern const char *applet_name; /* "BusyBox vN.N.N (timestamp or extra_version)" */ extern const char bb_banner[]; diff --git a/libbb/copy_file.c b/libbb/copy_file.c index d37d51562..3b83e1216 100644 --- a/libbb/copy_file.c +++ b/libbb/copy_file.c @@ -81,6 +81,7 @@ int copy_file(const char *source, const char *dest, int flags) signed char dest_exists = 0; signed char ovr; +/* Inverse of cp -d ("cp without -d") */ #define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE) if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { @@ -229,12 +230,22 @@ int copy_file(const char *source, const char *dest, int flags) return 0; } - if (S_ISREG(source_stat.st_mode) - /* DEREF uses stat, which never returns S_ISLNK() == true. */ + if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */ + !(flags & FILEUTILS_RECUR) + /* "cp [-opts] regular_file thing2" */ + || S_ISREG(source_stat.st_mode) + /* DEREF uses stat, which never returns S_ISLNK() == true. + * So the below is never true: */ /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */ ) { int src_fd; int dst_fd; + mode_t new_mode; + + if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) { + /* "cp -d symlink dst": create a link */ + goto dont_cat; + } if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) { const char *link_target; @@ -258,18 +269,24 @@ int copy_file(const char *source, const char *dest, int flags) if (src_fd < 0) return -1; + /* Do not try to open with weird mode fields */ + new_mode = source_stat.st_mode; + if (!S_ISREG(source_stat.st_mode)) + new_mode = 0666; + /* POSIX way is a security problem versus symlink attacks, * we do it only for non-symlinks, and only for non-recursive, * non-interactive cp. NB: it is still racy * for "cp file /home/bad_user/file" case * (user can rm file and create a link to /etc/passwd) */ if (DO_POSIX_CP - || (dest_exists && !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE)) + || (dest_exists + && !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE)) && !S_ISLNK(dest_stat.st_mode)) ) { - dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); + dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); } else /* safe way: */ - dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode); + dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); if (dst_fd == -1) { ovr = ask_and_unlink(dest, flags); if (ovr <= 0) { @@ -277,7 +294,7 @@ int copy_file(const char *source, const char *dest, int flags) return ovr; } /* It shouldn't exist. If it exists, do not open (symlink attack?) */ - dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode); + dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); if (dst_fd < 0) { close(src_fd); return -1; @@ -285,8 +302,7 @@ int copy_file(const char *source, const char *dest, int flags) } #if ENABLE_SELINUX - if (((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) - || (flags & FILEUTILS_SET_SECURITY_CONTEXT)) + if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT)) && is_selinux_enabled() > 0 ) { security_context_t con; @@ -313,8 +329,13 @@ int copy_file(const char *source, const char *dest, int flags) } /* ...but read size is already checked by bb_copyfd_eof */ close(src_fd); + /* "cp /dev/something new_file" should not + * copy mode of /dev/something */ + if (!S_ISREG(source_stat.st_mode)) + return retval; goto preserve_mode_ugid_time; } + dont_cat: /* Source is a symlink or a special file */ /* We are lazy here, a bit lax with races... */ diff --git a/testsuite/cp/cp-a-files-to-dir b/testsuite/cp/cp-a-files-to-dir index 39f8f8103..abdbdf77d 100644 --- a/testsuite/cp/cp-a-files-to-dir +++ b/testsuite/cp/cp-a-files-to-dir @@ -2,7 +2,8 @@ echo file number one > file1 echo file number two > file2 ln -s file2 link1 mkdir dir1 -touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +# why??? +#touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 mkdir there busybox cp -a file1 file2 link1 dir1 there test -f there/file1 diff --git a/testsuite/cp/cp-dev-file b/testsuite/cp/cp-dev-file new file mode 100644 index 000000000..055f0d9ee --- /dev/null +++ b/testsuite/cp/cp-dev-file @@ -0,0 +1,2 @@ +busybox cp /dev/null foo +test -f foo diff --git a/testsuite/cp/cp-does-not-copy-unreadable-file b/testsuite/cp/cp-does-not-copy-unreadable-file index ce11bfab0..e17e8e66d 100644 --- a/testsuite/cp/cp-does-not-copy-unreadable-file +++ b/testsuite/cp/cp-does-not-copy-unreadable-file @@ -1,6 +1,11 @@ touch foo chmod a-r foo set +e -busybox cp foo bar +if test `id -u` = 0; then + # run as user with nonzero uid + setuidgid 1 busybox cp foo bar +else + busybox cp foo bar +fi set -e test ! -f bar