Subject: Re: bug#25378: cp does not preserve SElinx context of sub folder
From: Pádraig Brady <[email-masked]>
To: HE Henry <[email-masked]>, [email-masked]
Date: Mon, 20 Feb 2017 19:11:15 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

On 06/01/17 00:19, HE Henry wrote:
> Hi coreutils team,
> 
> When using cp with --parents options, the SELinux context of sub folders are not preserved.
> 
> Example as below:
> 
> 1. Before using cp:
> [root@oame0 etc]# pwd
> /etc
> [root@oame0 etc]# ls -Z selinux/
> -rw-r--r--. root root system_u:object_r:selinux_config_t:s0 config
> -rw-r--r--. root root system_u:object_r:selinux_config_t:s0 semanage.conf
> drwxr-xr-x. root root system_u:object_r:selinux_config_t:s0 targeted
> [root@oame0 etc]#  ls -Z -d selinux/
> drwxr-xr-x. root root system_u:object_r:selinux_config_t:s0 selinux/
> 
> 
> 2. Using cp to copy /etc/selinux/targeted/seusers with full path to /tmp
> [root@oame0 etc]# cp -r --preserve=context --parents selinux/targeted/seusers  /tmp
> 
> 3. After using cp, the SELinux context of sub folder are changed, like selinux, targeted 
> 
> [root@oame0 etc]# ls -Z /tmp/selinux/
> drwx------. root root unconfined_u:object_r:user_tmp_t:s0 targeted
> [root@oame0 etc]# ls -Z -d /tmp/selinux/
> drwx------. root root unconfined_u:object_r:user_tmp_t:s0 /tmp/selinux/
> [root@oame0 etc]# ls -Z -d /tmp/selinux/targeted/
> drwx------. root root unconfined_u:object_r:user_tmp_t:s0 /tmp/selinux/targeted/
> [root@oame0 etc]# ls -Z /tmp/selinux/targeted/   
> -rw-------. root root system_u:object_r:selinux_config_t:s0 seusers

The attached should fix that.

thanks!
Pádraig

From d17ca013f3aadcf697663830fa9ec34cba551f66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Mon, 20 Feb 2017 18:46:49 -0800
Subject: [PATCH] cp: set SElinux context for --parents directories

* src/copy.h (set_process_security_ctx, set_file_security_ctx):
Export for use in cp.c.
* src/copy.h: Likewise.
* src/cp.c (make_dir_parents_private): Call the exported functions
to set the security context for new and updated directories.
* tests/cp/cp-a-selinux.sh: Add a test case.

Fixes http://bugs.gnu.org/25378
---
 NEWS                     |  4 ++++
 src/copy.c               |  4 ++--
 src/copy.h               |  9 +++++++++
 src/cp.c                 | 20 ++++++++++++++++++++
 tests/cp/cp-a-selinux.sh | 23 +++++++++++++++++++++--
 5 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 7473e6e..167c376 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Bug fixes
 
+  cp --parents will now set an SELinux context for created directories,
+  as appropriate for the -a, --preseve=context, or -Z options.
+  [bug present since SELinux support added in coreutils-6.10]
+
   date again converts from a specified time zone.  Previously output was
   not converted to the local time zone, and remained in the specified one.
   [bug introduced in coreutils-8.26]
diff --git a/src/copy.c b/src/copy.c
index 9dbd536..3711539 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -889,7 +889,7 @@ set_author (const char *dst_name, int dest_desc, const struct stat *src_sb)
    Based on CP_OPTIONS, diagnose warnings and fail when appropriate.
    Return FALSE on failure, TRUE on success.  */
 
-static bool
+extern bool
 set_process_security_ctx (char const *src_name, char const *dst_name,
                           mode_t mode, bool new_dst, const struct cp_options *x)
 {
@@ -951,7 +951,7 @@ set_process_security_ctx (char const *src_name, char const *dst_name,
    failure, when allowed by various settings in CP_OPTIONS.
    Return FALSE on failure, TRUE on success.  */
 
-static bool
+extern bool
 set_file_security_ctx (char const *dst_name, bool process_local,
                        bool recurse, const struct cp_options *x)
 {
diff --git a/src/copy.h b/src/copy.h
index f7047bc..db65c4a 100644
--- a/src/copy.h
+++ b/src/copy.h
@@ -285,6 +285,15 @@ bool copy (char const *src_name, char const *dst_name,
            bool nonexistent_dst, const struct cp_options *options,
            bool *copy_into_self, bool *rename_succeeded);
 
+bool
+set_process_security_ctx (char const *src_name, char const *dst_name,
+                          mode_t mode, bool new_dst,
+                          const struct cp_options *x);
+
+bool
+set_file_security_ctx (char const *dst_name, bool process_local,
+                       bool recurse, const struct cp_options *x);
+
 void dest_info_init (struct cp_options *);
 void src_info_init (struct cp_options *);
 
diff --git a/src/cp.c b/src/cp.c
index 88db3a3..6e18263 100644
--- a/src/cp.c
+++ b/src/cp.c
@@ -394,6 +394,8 @@ make_dir_parents_private (char const *const_dir, size_t src_offset,
 
   *attr_list = NULL;
 
+  /* XXX: If all dirs present at the destination,
+     no permissions or security contexts will be updated.  */
   if (stat (dst_dir, &stats) != 0)
     {
       /* A parent of CONST_DIR does not exist.
@@ -437,6 +439,12 @@ make_dir_parents_private (char const *const_dir, size_t src_offset,
               *attr_list = new;
             }
 
+          /* If required set the default context for created dirs.  */
+          if (! set_process_security_ctx (src, dir,
+                                          missing_dir ? new->st.st_mode : 0,
+                                          missing_dir, x))
+            return false;
+
           if (missing_dir)
             {
               mode_t src_mode;
@@ -524,6 +532,18 @@ make_dir_parents_private (char const *const_dir, size_t src_offset,
             }
           else
             *new_dst = false;
+
+          /* For existing dirs, set the security context as per that already
+             set for the process global context.  */
+          if (! *new_dst
+              && (x->set_security_context || x->preserve_security_context))
+            {
+              if (! set_file_security_ctx (dir, x->preserve_security_context,
+                                           false, x)
+                  && x->require_preserve_context)
+                  return false;
+            }
+
           *slash++ = '/';
 
           /* Avoid unnecessary calls to 'stat' when given
diff --git a/tests/cp/cp-a-selinux.sh b/tests/cp/cp-a-selinux.sh
index 19a9e64..de07406 100755
--- a/tests/cp/cp-a-selinux.sh
+++ b/tests/cp/cp-a-selinux.sh
@@ -48,7 +48,6 @@ rm -f f
 # due to recursion, and was handled incorrectly in coreutils-8.22
 # Note standard permissions are updated for existing directories
 # in the destination, so SELinux contexts should be updated too.
-chmod o+rw restore/existing_dir
 mkdir -p backup/existing_dir/ || framework_failure_
 ls -Zd backup/existing_dir > ed_ctx || fail=1
 grep $ctx ed_ctx && framework_failure_
@@ -57,11 +56,31 @@ chcon $ctx backup/existing_dir/file || framework_failure_
 # Set the dir context to ensure it is reset
 mkdir -p --context="$ctx" restore/existing_dir || framework_failure_
 # Copy and ensure existing directories updated
-cp -a backup/. restore/
+cp -a backup/. restore/ || fail=1
 ls -Zd restore/existing_dir > ed_ctx || fail=1
 grep $ctx ed_ctx &&
   { ls -lZd restore/existing_dir; fail=1; }
 
+# Check context preserved with directories created with --parents,
+# which was not handled before coreutils-8.27
+mkdir -p parents/a/b || framework_failure_
+ls -Zd parents/a/b > ed_ctx || fail=1
+grep $ctx ed_ctx && framework_failure_
+touch parents/a/b/file || framework_failure_
+chcon $ctx parents/a/b || framework_failure_
+# Set the dir context to ensure it is reset
+mkdir -p --context="$ctx" parents_dest/parents/a || framework_failure_
+# Copy and ensure existing directories updated
+cp -r --parents --preserve=context parents/a/b/file parents_dest || fail=1
+# Check new context
+ls -Zd parents_dest/parents/a/b > ed_ctx || fail=1
+grep $ctx ed_ctx ||
+  { ls -lZd parents_dest/parents/a/b; fail=1; }
+# Check updated context
+ls -Zd parents_dest/parents/a > ed_ctx || fail=1
+grep $ctx ed_ctx &&
+  { ls -lZd parents_dest/parents/a; fail=1; }
+
 # Check restorecon (-Z) functionality for file and directory
 # Also make a dir with our known context
 mkdir c_d || framework_failure_
-- 
2.5.5