From 376d663340a9dc91c91a5849e5713f07571c1628 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Thu, 27 Apr 2023 15:14:23 +0300 Subject: [PATCH] Fix 45b0ee2b407913c533f7ded8d6f8cbeec16ff6ca. The commit in question brought in more problems than solutions. To properly fix the issue, use symlink placeholders, modelled after delayed symlinks in tar. * src/copyin.c (symlink_placeholder) (replace_symlink_placeholders): New functions. (copyin_link): Create symlink placeholder if --no-absolute-filenames was given. (process_copy_in): Replace placeholders after extraction. * tests/CVE-2015-1197.at: Update. Don't use /tmp. --- src/copyin.c | 172 +++++++++++++++++++++++++++++++++++------ tests/CVE-2015-1197.at | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 20 deletions(-) diff --git a/src/copyin.c b/src/copyin.c index 60cef9d..5ed2db6 100644 --- a/src/copyin.c +++ b/src/copyin.c @@ -30,6 +30,7 @@ #ifndef FNM_PATHNAME # include #endif +#include #ifndef HAVE_LCHOWN # define lchown(f,u,g) 0 @@ -620,6 +621,136 @@ copyin_device (struct cpio_file_stat* file_hdr) file_hdr->c_mtime); } +struct delayed_link + { + /* The device and inode number of the placeholder. */ + dev_t dev; + ino_t ino; + + /* The desired link metadata. */ + mode_t mode; + uid_t uid; + gid_t gid; + time_t mtime; + + /* Link source and target names. */ + char *source; + char target[1]; + }; + +static Hash_table *delayed_link_table; + +static size_t +dl_hash (void const *entry, size_t table_size) +{ + struct delayed_link const *dl = entry; + uintmax_t n = dl->dev; + int nshift = (sizeof (n) - sizeof (dl->dev)) * CHAR_BIT; + if (0 < nshift) + n <<= nshift; + n ^= dl->ino; + return n % table_size; +} + +static bool +dl_compare (void const *a, void const *b) +{ + struct delayed_link const *da = a, *db = b; + return (da->dev == db->dev) & (da->ino == db->ino); +} + +static int +symlink_placeholder (char *oldpath, char *newpath, struct cpio_file_stat *file_stat) +{ + int fd = open (newpath, O_WRONLY | O_CREAT | O_EXCL, 0); + struct stat st; + struct delayed_link *p; + size_t newlen = strlen (newpath); + + if (fd < 0) + { + open_error (newpath); + return -1; + } + + if (fstat (fd, &st) != 0) + { + stat_error (newpath); + close (fd); + return -1; + } + + close (fd); + + p = xmalloc (sizeof (*p) + strlen (oldpath) + newlen + 1); + p->dev = st.st_dev; + p->ino = st.st_ino; + + p->mode = file_stat->c_mode; + p->uid = file_stat->c_uid; + p->gid = file_stat->c_gid; + p->mtime = file_stat->c_mtime; + + strcpy (p->target, newpath); + p->source = p->target + newlen + 1; + strcpy (p->source, oldpath); + + if (!((delayed_link_table + || (delayed_link_table = hash_initialize (0, 0, dl_hash, + dl_compare, free))) + && hash_insert (delayed_link_table, p))) + xalloc_die (); + + return 0; +} + +static void +replace_symlink_placeholders (void) +{ + struct delayed_link *dl; + + if (!delayed_link_table) + return; + for (dl = hash_get_first (delayed_link_table); + dl; + dl = hash_get_next (delayed_link_table, dl)) + { + struct stat st; + + /* Make sure the placeholder file is still there. If not, + don't create a link, as the placeholder was probably + removed by a later extraction. */ + if (lstat (dl->target, &st) == 0 + && st.st_dev == dl->dev + && st.st_ino == dl->ino) + { + if (unlink (dl->target)) + unlink_error (dl->target); + else + { + int res = UMASKED_SYMLINK (dl->source, dl->target, dl->mode); + if (res < 0 && create_dir_flag) + { + create_all_directories (dl->target); + res = UMASKED_SYMLINK (dl->source, dl->target, dl->mode); + } + if (res < 0) + symlink_error (dl->source, dl->target); + else if (!no_chown_flag) + { + uid_t uid = set_owner_flag ? set_owner : dl->uid; + gid_t gid = set_group_flag ? set_group : dl->gid; + if (lchown (dl->target, uid, gid) < 0 && errno != EPERM) + chown_error_details (dl->target, uid, gid); + } + } + } + } + + hash_free (delayed_link_table); + delayed_link_table = NULL; +} + static void copyin_link (struct cpio_file_stat *file_hdr, int in_file_des) { @@ -645,28 +776,26 @@ copyin_link (struct cpio_file_stat *file_hdr, int in_file_des) link_name = xstrdup (file_hdr->c_tar_linkname); } - res = UMASKED_SYMLINK (link_name, file_hdr->c_name, - file_hdr->c_mode); - if (res < 0 && create_dir_flag) + if (no_abs_paths_flag) + symlink_placeholder (link_name, file_hdr->c_name, file_hdr); + else { - create_all_directories (file_hdr->c_name); res = UMASKED_SYMLINK (link_name, file_hdr->c_name, - file_hdr->c_mode); - } - if (res < 0) - { - error (0, errno, _("%s: Cannot symlink to %s"), - quotearg_colon (link_name), quote_n (1, file_hdr->c_name)); - free (link_name); - return; - } - if (!no_chown_flag) - { - uid_t uid = set_owner_flag ? set_owner : file_hdr->c_uid; - gid_t gid = set_group_flag ? set_group : file_hdr->c_gid; - if ((lchown (file_hdr->c_name, uid, gid) < 0) - && errno != EPERM) - chown_error_details (file_hdr->c_name, uid, gid); + file_hdr->c_mode); + if (res < 0 && create_dir_flag) + { + create_all_directories (file_hdr->c_name); + res = UMASKED_SYMLINK (link_name, file_hdr->c_name, file_hdr->c_mode); + } + if (res < 0) + symlink_error (link_name, file_hdr->c_name); + else if (!no_chown_flag) + { + uid_t uid = set_owner_flag ? set_owner : file_hdr->c_uid; + gid_t gid = set_group_flag ? set_group : file_hdr->c_gid; + if (lchown (file_hdr->c_name, uid, gid) < 0 && errno != EPERM) + chown_error_details (file_hdr->c_name, uid, gid); + } } free (link_name); } @@ -1425,6 +1553,7 @@ process_copy_in (void) if (dot_flag) fputc ('\n', stderr); + replace_symlink_placeholders (); apply_delayed_set_stat (); cpio_file_stat_free (&file_hdr); diff --git a/tests/CVE-2015-1197.at b/tests/CVE-2015-1197.at new file mode 100644 index 0000000..6079af7 --- /dev/null +++ b/tests/CVE-2015-1197.at @@ -0,0 +1,40 @@ +# Process this file with autom4te to create testsuite. -*- Autotest-*- +# Copyright (C) 2009-2019 Free Software Foundation, 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 3, 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, see . + +AT_SETUP([CVE-2015-1197 (--no-absolute-filenames for symlinks)]) +AT_CHECK([ +tempdir=$(pwd)/tmp +mkdir $tempdir +touch $tempdir/file +ln -s $tempdir dir +AT_DATA([filelist], +[dir +dir/file +]) +cpio -o < filelist > test.cpio +rm -rf dir $tempdir +cpio --no-absolute-filenames -iv < test.cpio +], +[2], +[], +[1 block +dir +cpio: dir/file: Cannot open: Not a directory +dir/file +1 block +]) +AT_CLEANUP + -- 2.27.0