commit: 42eb5eaf744f5561e86458ba242f2514cfa80595 Author: Kerin Millar <kfm <AT> plushkava <DOT> net> AuthorDate: Fri Sep 12 16:31:14 2025 +0000 Commit: Kerin Millar <kfm <AT> plushkava <DOT> net> CommitDate: Fri Sep 12 16:54:08 2025 +0000 URL: https://gitweb.gentoo.org/proj/locale-gen.git/commit/?id=42eb5eaf
Add support for preserving/restoring SELinux contexts Among the changes brought by locale-gen v3.0 was the following: - During the archive generation phase, localedef(1) is made to generate its archive in a temporary directory. Only if it succeeds shall any existing archive be replaced, with locale-gen(1) now assuming that responsibility. The existing archive is guaranteed to be replaced atomically or not at all. Such is problematic for operating environments in which SELinux is enabled because the newly created archive ends up lacking the appropriate label. Consequently, programs are unable to read the archive until such time as the user restores the label. Address this issue in a manner twofold. Firstly, in the event that a prior archive exists, execute the chcon(1) utility so as to copy the label from the old archive to the new archive, just before the former is replaced by a rename(2) syscall. Secondly, in the event that a prior archive does not exist, execute the restorecon(8) utility in an attempt to apply the appropriate label. No labelling shall be attempted unless the underlying filesystem is found to be mounted with the "seclabel" option in effect. Further, restorecon(8) shall only be executed if is found in PATH and the --prefix option was either a) unspecified b) specified as "/". Signed-off-by: Kerin Millar <kfm <AT> plushkava.net> Tested-by: Nicolas PARLANT <nicolas.parlant <AT> parhuet.fr> Tested-by: Kenton Groombridge <concord <AT> gentoo.org> Closes: https://bugs.gentoo.org/962753 locale-gen | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/locale-gen b/locale-gen index 0d180f3..4cb43e8 100755 --- a/locale-gen +++ b/locale-gen @@ -10,9 +10,10 @@ use v5.36; use Cwd qw(getcwd); use Errno qw(ENOENT); use Fcntl qw(SEEK_SET); -use File::Spec::Functions qw(canonpath catfile catdir splitpath); +use File::Spec::Functions qw(canonpath catfile catdir path splitpath); use File::Temp qw(tempdir); use Getopt::Long (); +use List::Util qw(any); # Formally stable as of v5.40; sufficiently functional in both v5.36 and v5.38. use experimental qw(try); @@ -485,7 +486,8 @@ sub generate_archive ($prefix, $gentoo_prefix, $locale_dir, $do_update, @canonic print "The location of the archive shall be '$final_path'.\n"; # If --update was specified, make a copy of the existing archive. - if ($do_update && -e $final_path) { + my $has_archive = -e $final_path; + if ($do_update && $has_archive) { run('cp', '--', $final_path, "$output_dir/"); } @@ -517,6 +519,9 @@ sub generate_archive ($prefix, $gentoo_prefix, $locale_dir, $do_update, @canonic throw_child_error('localedef', 1 << 8); } + # Determine whether the underlying filesystem supports SELinux labels. + my $has_seclabels = has_mount_option(dirname($final_path), 'seclabel'); + # The process of replacing the old archive must not be interrupted. local @SIG{'INT', 'TERM'} = ('IGNORE', 'IGNORE'); @@ -525,6 +530,11 @@ sub generate_archive ($prefix, $gentoo_prefix, $locale_dir, $do_update, @canonic my $interim_path = "$final_path.$$"; run('mv', '--', catfile($output_dir, 'locale-archive'), $interim_path); + # If a prior archive exists, attempt to preserve its SELinux label. + if ($has_archive && $has_seclabels) { + run('chcon', "--reference=$final_path", '--', $interim_path); + } + # Atomically replace the old archive. if (! rename $interim_path, $final_path) { { @@ -534,6 +544,11 @@ sub generate_archive ($prefix, $gentoo_prefix, $locale_dir, $do_update, @canonic die "$PROGRAM: Can't rename '$interim_path' to '$final_path': $!\n"; } + # If no prior archive existed, restore the appropriate SELinux label. + if (! $has_archive && $has_seclabels && $prefix =~ m/^\/?\z/ && can_run('restorecon')) { + run('restorecon', '-Fmv', '--', $final_path); + } + # Return the size of the archive, in bytes. if (! (my @stat = stat $final_path)) { die "$PROGRAM: Can't stat '$final_path': $!\n"; @@ -633,6 +648,27 @@ sub basename ($path) { return (splitpath($path))[2]; } +sub dirname ($path) { + return (splitpath($path))[1]; +} + +sub has_mount_option ($target, $option) { + if (! open my $pipe, '-|', qw( findmnt -no options -T ), $target) { + exit 1; + } else { + chomp(my $stdout = do { local $/; readline $pipe }); + if (! close $pipe && $! == 0) { + throw_child_error('findmnt'); + } + return ",$stdout," =~ m/\Q,$option,/; + } +} + +sub can_run ($bin) { + my @paths = path(); + return any(sub { -f $_ && -x _ }, map +( "$_/$bin" ), @paths); +} + END { if ($$ == $PID) { if (length $TEMPDIR) {
