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) {

Reply via email to