From 52e468884eae73784e422ceb90b92ed09da5d806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 5 Sep 2026 01:49:31 -0300 Subject: [PATCH] fix(grub2): keep the whole kernel command line past a grub2 separator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grub2 reads its configuration as a script, so an unquoted command separator ends the linux command and everything after it is lost. The Ubuntu installer seed is written as ds=nocloud-net;s=, so the node booted without the seed URL and without the arguments that followed it, including BOOTIF. The installer then found no autoinstall configuration and waited for someone to answer its questions. A separator that is neither escaped nor inside a quoted span is now escaped where it stands, which grub2 removes before it hands the line to the kernel. A value the caller escaped or quoted keeps exactly the form the caller gave it. Signed-off-by: Vinícius Ferrão <2031761+viniciusferrao@users.noreply.github.com> --- xCAT-server/lib/xcat/plugins/grub2.pm | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/xCAT-server/lib/xcat/plugins/grub2.pm b/xCAT-server/lib/xcat/plugins/grub2.pm index 88a693069..17e48e3b4 100644 --- a/xCAT-server/lib/xcat/plugins/grub2.pm +++ b/xCAT-server/lib/xcat/plugins/grub2.pm @@ -85,6 +85,29 @@ sub getstate { } } +# grub2 reads its configuration as a script, so an unquoted word carrying one of the +# characters below ends the linux command and the rest of the kernel command line is lost. +# The Ubuntu installer seed (ds=nocloud-net;s=) is the usual casualty. +my $GRUB2_TERMINATOR = qr/[;{}|&<>()]/; + +sub quote_kcmdline { + my $kcmdline = shift; + + return $kcmdline unless (defined $kcmdline and $kcmdline =~ $GRUB2_TERMINATOR); + + # Escaped in place rather than quoted as a whole: a value the caller quoted keeps the + # quoting it was given, which grub2 removes before the kernel sees the value. + my $escaped = ''; + while (length $kcmdline) { + if ($kcmdline =~ s/^('[^']*'|"[^"]*")//) { $escaped .= $1; next; } + if ($kcmdline =~ s/^(\\.)//) { $escaped .= $1; next; } + $kcmdline =~ s/^(.)//s; + my $char = $1; + $escaped .= ($char =~ $GRUB2_TERMINATOR) ? "\\$char" : $char; + } + return $escaped; +} + sub setstate { =pod @@ -260,7 +283,8 @@ sub setstate { } if ($kern and $kern->{kcmdline}) { - print $pcfg " linux$efi $protocolrootdir/$kern->{kernel} $kern->{kcmdline} BOOTIF=\$net_default_mac\n"; + my $kcmdline = quote_kcmdline($kern->{kcmdline}); + print $pcfg " linux$efi $protocolrootdir/$kern->{kernel} $kcmdline BOOTIF=\$net_default_mac\n"; } else { print $pcfg " linux$efi $protocolrootdir/$kern->{kernel} BOOTIF=\$net_default_mac\n"; }