From 1a71d9d33cc6939e716e7ece61e9c01c0803e228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 13 Oct 2026 11:38:51 -0300 Subject: [PATCH 1/2] fix(packimage): refuse an image directory that the shell would split packimage passes the image directory to shell commands without quotes, such as the rm -rf of the xcatpost directory. It finds its abandoned stages with glob. For an image directory such as "/install/custom images/node", the shell and glob split the path at the space, so packimage removed /install/custom with the images in it. packimage now refuses an image directory whose path has a space or a shell character, before it runs any command. --- xCAT-server/lib/xcat/plugins/packimage.pm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xCAT-server/lib/xcat/plugins/packimage.pm b/xCAT-server/lib/xcat/plugins/packimage.pm index 506482909..071592d0e 100644 --- a/xCAT-server/lib/xcat/plugins/packimage.pm +++ b/xCAT-server/lib/xcat/plugins/packimage.pm @@ -196,6 +196,13 @@ sub process_request { } $rootimg_dir = "$destdir/rootimg"; + # packimage passes the image directory through the shell, which splits a path at a space and expands other + # characters, so a command such as rm -rf would act on another path. + if ($destdir =~ /[\s'"`\$;&|<>()\\*?\[\]{}]/x) { + $callback->({ error => ["Cannot pack the image in $destdir: its path has a space or a shell character"], errorcode => [1] }); + return 1; + } + my $is_openeuler = $osver =~ /^openeuler/; if ($is_openeuler) { unless (defined(xCAT::Utils::normalize_openeuler_version(substr($osver, 9))) From c675b186fa9889d5780b3723b340841fc862ffbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 13 Oct 2026 11:38:51 -0300 Subject: [PATCH 2/2] test(packimage): cover an image directory with a space packimage must refuse an image directory with a space, and leave the directory or file that the first word of its path names. The previous archive, the other formats and the image must stay. --- xCAT-test/unit/packimage_atomic_archive.t | 27 ++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/xCAT-test/unit/packimage_atomic_archive.t b/xCAT-test/unit/packimage_atomic_archive.t index d31dabec5..319f23e05 100644 --- a/xCAT-test/unit/packimage_atomic_archive.t +++ b/xCAT-test/unit/packimage_atomic_archive.t @@ -60,11 +60,12 @@ our @LITEFILES; my ($unsquashfs, $mksquashfs) = (tool('unsquashfs'), tool('mksquashfs')); my $sequence = 0; -# The wrapper records what nodes would download while the pack runs, then runs or fails the real tool. +# The wrapper records what nodes would download while the pack runs, then runs or fails the real tool. $parent +# is the directory of the image directory. sub image { - my ($method, $compress, $wrapped, $mode) = @_; + my ($method, $compress, $wrapped, $mode, $parent) = @_; my $name = 'atomic-' . ++$sequence; - my $dest = "$fixture/images/$name"; + my $dest = ($parent // "$fixture/images") . "/$name"; my $root = "$dest/rootimg"; make_path("$root/etc", "$root/opt/xcat", "$root/usr/bin", "$root/usr/lib/dracut/modules.d/97xcat", "$dest/bin"); write_text("$root/etc/shadow", "root:*:1:0:99999:7:::\n"); @@ -364,4 +365,24 @@ SCRIPT is_deeply([ glob("$dest/.packimage-$host+*") ], [], "$label: no temporary file is left"); } +for my $kind (qw(directory file)) { + # The shell splits an image directory with a space, and the first word of its path names another path. + my $word = "$fixture/word-$kind"; + if ($kind eq 'directory') { + make_path("$word/keep"); + } else { + write_text($word, "file that the first word of the image directory names\n"); + } + my ($name, $dest, $archive) = image('cpio', 'gzip', 'gzip', 'pass', "$word images"); + make_path("$dest/rootimg/xcatpost"); + write_text("$dest/rootimg/xcatpost/previous", "postscript of the previous pack\n"); + my ($result, $errors) = packimage($dest, '-m', 'cpio', '-c', 'gzip', $name); + my $label = "image directory with a space after the path of a $kind"; + like($errors, qr/Cannot pack the image in \Q$dest\E: its path has a space or a shell character/, "$label: the pack is refused"); + ok($kind eq 'directory' ? -d "$word/keep" : -f $word, "$label: the $kind that the first word of the path names stays"); + is(content($archive), "previous archive\n", "$label: the previous archive stays"); + is(content("$dest/rootimg.previous"), "previous format\n", "$label: other formats stay"); + is(content("$dest/rootimg/xcatpost/previous"), "postscript of the previous pack\n", "$label: the image stays as it was"); +} + done_testing();