From 68a4855c7fa595193f1932c3fd0334ba41a68aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Fri, 17 Jul 2026 20:36:51 -0300 Subject: [PATCH] fix(statelite): reject invalid NFS root options early --- xCAT-server/lib/perl/xCAT/SvrUtils.pm | 34 ++++++++++++++++---- xCAT-server/lib/xcat/plugins/DBobjectdefs.pm | 27 ++++++++++++++++ xCAT-test/autotest/testcase/chdef/cases0 | 27 +++++++++++++++- xCAT-test/unit/nfs_root_options.t | 9 ++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/xCAT-server/lib/perl/xCAT/SvrUtils.pm b/xCAT-server/lib/perl/xCAT/SvrUtils.pm index 04b7305c8..e1a8df247 100644 --- a/xCAT-server/lib/perl/xCAT/SvrUtils.pm +++ b/xCAT-server/lib/perl/xCAT/SvrUtils.pm @@ -2415,13 +2415,12 @@ sub getplatform { return $platform; } -# Build the dracut root=nfs parameter while preserving statelite's read-only -# root filesystem contract. -sub build_statelite_nfsroot_parameter { - shift if @_ && defined($_[0]) && $_[0] eq __PACKAGE__; - my ($server, $rootdir, $additional_options) = @_; +# Parse statelite NFS root options once so command-time validation and the +# dracut root=nfs renderer enforce the same rules. +sub _parse_statelite_nfsroot_options { + my ($additional_options) = @_; - my @mount_options = ('ro'); + my @mount_options; if (defined($additional_options) && $additional_options ne '') { foreach my $option (split(/,/, $additional_options, -1)) { if ($option eq '' || $option =~ /\s/) { @@ -2438,6 +2437,29 @@ sub build_statelite_nfsroot_parameter { } } + return (\@mount_options, undef); +} + +# Return an error message when statelite NFS root options violate the +# read-only root filesystem contract. +sub validate_statelite_nfsroot_options { + shift if @_ && defined($_[0]) && $_[0] eq __PACKAGE__; + my ($additional_options) = @_; + + my ($mount_options, $error) = _parse_statelite_nfsroot_options($additional_options); + return $error; +} + +# Build the dracut root=nfs parameter while preserving statelite's read-only +# root filesystem contract. +sub build_statelite_nfsroot_parameter { + shift if @_ && defined($_[0]) && $_[0] eq __PACKAGE__; + my ($server, $rootdir, $additional_options) = @_; + + my ($additional_mount_options, $error) = _parse_statelite_nfsroot_options($additional_options); + return (undef, $error) if $error; + + my @mount_options = ('ro', @{$additional_mount_options}); return ('root=nfs:' . $server . ':' . $rootdir . ':' . join(',', @mount_options), undef); } diff --git a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm index c9c41cb31..6c154768a 100644 --- a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm +++ b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm @@ -369,6 +369,19 @@ sub parse_attr_for_osimage { return 0; } +sub validate_osimage_nfsrootopts { + my $attr_hash = shift; + return 0 unless exists($attr_hash->{nfsrootopts}); + + my $validation_error = xCAT::SvrUtils->validate_statelite_nfsroot_options($attr_hash->{nfsrootopts}); + return 0 unless $validation_error; + + my $rsp; + $rsp->{data}->[0] = $validation_error; + xCAT::MsgUtils->message("E", $rsp, $::callback); + return 1; +} + #---------------------------------------------------------------------------- =head3 processArgs @@ -1886,6 +1899,13 @@ sub defmk next; } + if (($type eq 'osimage') && validate_osimage_nfsrootopts($::FINALATTRS{$obj})) + { + $error = 1; + delete $::FINALATTRS{$obj}; + next; + } + if ($object_exists && $::opt_f) { # remove the old object after the replacement definition is known valid @@ -2531,6 +2551,13 @@ sub defch $isDefined = 1; } + if (($type eq 'osimage') && !$::opt_m && validate_osimage_nfsrootopts($::FINALATTRS{$obj})) + { + $error = 1; + delete($::FINALATTRS{$obj}); + next; + } + if ($type eq 'network') { my $isInvalid = 0; diff --git a/xCAT-test/autotest/testcase/chdef/cases0 b/xCAT-test/autotest/testcase/chdef/cases0 index dc84f1e40..619f70428 100644 --- a/xCAT-test/autotest/testcase/chdef/cases0 +++ b/xCAT-test/autotest/testcase/chdef/cases0 @@ -34,6 +34,32 @@ cmd:rmdef -t node testnode check:rc==0 end +start:chdef_osimage_nfsrootopts_validation +description:Reject invalid statelite NFS root options before provisioning without changing the osimage +label:mn_only,ci_test,db,statelite,nfsrootopts +cmd:rmdef -t node -o xcatnfsoptscinode >/dev/null 2>&1; rmdef -t osimage -o xcatnfsoptsciimage >/dev/null 2>&1; rm -rf /tmp/xcat-nfsrootopts-ci-root /tftpboot/xcat/osimage/xcatnfsoptsciimage; rm -f /tftpboot/xcat/xnba/nodes/xcatnfsoptscinode* +cmd:mkdir -p /tmp/xcat-nfsrootopts-ci-root; touch /tmp/xcat-nfsrootopts-ci-root/kernel /tmp/xcat-nfsrootopts-ci-root/initrd-statelite.gz +check:rc==0 +cmd:mkdef -t osimage -o xcatnfsoptsciimage imagetype=linux provmethod=statelite profile=compute osname=Linux osvers=rhels6.99 osarch=x86_64 rootfstype=nfs rootimgdir=/tmp/xcat-nfsrootopts-ci-root nfsrootopts=noac +check:rc==0 +cmd:mkdef -t node -o xcatnfsoptscinode groups=all arch=x86_64 netboot=xnba os=rhels6.99 profile=compute ip=192.0.2.100 mac=02:00:00:00:00:01 +check:rc==0 +cmd:chdef -t osimage -o xcatnfsoptsciimage nfsrootopts=rw +check:rc!=0 +check:output=~nfsrootopts cannot contain 'rw'; statelite NFS roots are always read-only +cmd:lsdef -t osimage -o xcatnfsoptsciimage -i nfsrootopts +check:rc==0 +check:output=~nfsrootopts=noac +check:output!~nfsrootopts=rw +cmd:nodeset xcatnfsoptscinode osimage=xcatnfsoptsciimage +check:rc==0 +check:output!~nfsrootopts +cmd:grep 'root=nfs:.*:ro,noac' /tftpboot/xcat/xnba/nodes/xcatnfsoptscinode +check:rc==0 +cmd:rmdef -t node -o xcatnfsoptscinode; rmdef -t osimage -o xcatnfsoptsciimage; rm -rf /tmp/xcat-nfsrootopts-ci-root /tftpboot/xcat/osimage/xcatnfsoptsciimage; rm -f /tftpboot/xcat/xnba/nodes/xcatnfsoptscinode* +check:rc==0 +end + start:chdef_t_network description:chdef -t network label:mn_only,ci_test,db @@ -324,4 +350,3 @@ cmd:rmdef -t network -o aaaaa_not_exist check:rc==0 cmd:rm -rf /tmp/bbbbb_not_exist.def end - diff --git a/xCAT-test/unit/nfs_root_options.t b/xCAT-test/unit/nfs_root_options.t index 99391f85b..bc721adc0 100644 --- a/xCAT-test/unit/nfs_root_options.t +++ b/xCAT-test/unit/nfs_root_options.t @@ -38,8 +38,17 @@ foreach my $invalid ('rw', 'RW', 'noac,rw', 'defaults', 'noac, actimeo=0', 'noac ($parameter, $error) = build_nfsroot($invalid); is($parameter, undef, "invalid option list '$invalid' is rejected"); like($error, qr/^nfsrootopts /, "invalid option list '$invalid' reports the attribute name"); + like( + xCAT::SvrUtils->validate_statelite_nfsroot_options($invalid), + qr/^nfsrootopts /, + "command-time validation rejects '$invalid'" + ); } +is(xCAT::SvrUtils->validate_statelite_nfsroot_options(undef), undef, 'command-time validation accepts an unset value'); +is(xCAT::SvrUtils->validate_statelite_nfsroot_options(''), undef, 'command-time validation accepts an empty value'); +is(xCAT::SvrUtils->validate_statelite_nfsroot_options('noac,actimeo=0'), undef, 'command-time validation accepts valid options'); + ok( scalar(grep { $_ eq 'nfsrootopts' } @{ $xCAT::Schema::tabspec{osimage}->{cols} }), 'osimage table includes nfsrootopts'