Fix to harden GRUB manual editor allowlist

This commit is contained in:
Ilia Ross
2026-05-28 14:50:00 +02:00
parent 010f15c2a5
commit 1335d05f7c
2 changed files with 45 additions and 2 deletions

View File

@@ -1406,7 +1406,7 @@ if ($grub_dir ne '' && -d $grub_dir && opendir(my $dh, $grub_dir)) {
# Hide dotfiles and only expose regular generator/text files.
next if ($base =~ /^\./);
my $file = "$grub_dir/$base";
next if (!-f $file);
next if (!&grub2_manual_file_safe($file, $grub_dir, 1));
my $type = defined($custom_file) && $file eq $custom_file ? 'custom' :
(-x $file || $base =~ /^\d+_/) ? 'grub_script' :
'text';
@@ -1422,7 +1422,7 @@ if ($bls_dir ne '' && -d $bls_dir && opendir(my $dh, $bls_dir)) {
# Disabled rescue files deliberately do not appear in the editor.
next if ($base =~ /^\./ || $base !~ /\.conf\z/);
my $file = "$bls_dir/$base";
next if (!-f $file);
next if (!&grub2_manual_file_safe($file, $bls_dir, 1));
&add_grub2_manual_file(\@files, \%seen, 'bls_dir', $file,
'bls');
}
@@ -1438,9 +1438,23 @@ sub add_grub2_manual_file
{
my ($files, $seen, $key, $file, $type) = @_;
return if (!defined($file) || $file eq '' || $seen->{$file}++);
return if (!&grub2_manual_file_safe($file, undef, 0));
push(@$files, { 'key' => $key, 'file' => $file, 'type' => $type });
}
# grub2_manual_file_safe(file, [parent-dir], must-exist?)
# Returns true if a path is safe for the manual editor allowlist.
sub grub2_manual_file_safe
{
my ($file, $parent, $must_exist) = @_;
return 0 if (!defined($file) || $file eq '');
my @st = lstat($file);
return $must_exist ? 0 : 1 if (!@st);
# Symlinks would be followed by the Webmin write path, so reject them here.
return 0 if (-l _ || !-f _);
return $parent ? &grub2_path_is_under($file, $parent) : 1;
}
# grub2_manual_file(file)
# Returns the manual-edit descriptor for an allowed file path.
sub grub2_manual_file

View File

@@ -677,6 +677,35 @@ ok(grub2_manual_file($os_prober_file), 'grub.d script is manual-edit allowlisted
ok(grub2_manual_file($readme_file), 'grub.d regular file is manual-edit allowlisted');
ok(grub2_manual_file($bls_entry_file), 'BLS entry is manual-edit allowlisted');
ok(!grub2_manual_file("$work/not-allowed"), 'unexpected file is rejected');
SKIP: {
my $outside_manual = "$work/outside-manual-target";
my $manual_link = "$work/grub.d/09_symlink";
write_test_file($outside_manual, "outside\n");
skip 'symlink unavailable', 5 if (!symlink($outside_manual, $manual_link));
ok(!grub2_manual_file($manual_link),
'grub.d symlink is not manual-edit allowlisted');
is(save_manual_grub_file($manual_link, "#!/bin/sh\nexit 0\n"),
$text{'manual_efile'}, 'manual save rejects grub.d symlink');
is(slurp_test_file($outside_manual), "outside\n",
'manual save does not write through grub.d symlink');
my $outside_bls = "$work/outside-bls-target";
my $bls_link = "$bls_dir/symlink.conf";
write_test_file($outside_bls, "title Outside\nlinux /vmlinuz\n");
if (!symlink($outside_bls, $bls_link)) {
unlink($manual_link);
skip 'second symlink unavailable', 2;
}
ok(!grub2_manual_file($bls_link),
'BLS symlink is not manual-edit allowlisted');
{
local $config{'custom_file'} = $manual_link;
ok(!grub2_manual_file($manual_link),
'configured custom symlink is not manual-edit allowlisted');
}
unlink($manual_link);
unlink($bls_link);
}
is(save_manual_grub_file($default_file, $saved), undef,
'manual save validates default file');
is(save_manual_grub_file($custom_file, "menuentry 'X' { true }\n"), undef,