Subject: bug#20616: mkdir: -p breaks -Z

From: Pádraig Brady <[email-masked]>
Date: Thu, 21 May 2015 11:09:59 +0100

On 20/05/15 13:03, Hannes Reich wrote:
> The "-Z" (set SELinux context) option to mkdir appears to have no effect 
> when "-p" (no error if existing, create parents) is also specified.
> 
> For example, on my CentOS 7 system, the correct context for 
> subdirectories of "/home" is "user_home_dir_t", but when I create such a 
> directory with "mkdir -p -Z", it has the "home_root_t" context:
> 
>      # rm -rf /home/with-p
>      # mkdir -p -Z /home/with-p
>      # ls -Zd /home/with-p
>      drwxr-xr-x. root root unconfined_u:object_r:home_root_t:s0 /home/with-p
>      #
> 
> That's wrong. Running restorecon(1) fixes it:
> 
>      # restorecon -v /home/with-p
>      restorecon reset /home/with-p context 
> unconfined_u:object_r:home_root_t:s0->unconfined_u:object_r:user_home_dir_t:s0
>      #
> 
> Without the "-p" option, "-Z" works as expected:
> 
>      # mkdir -Z /home/without-p
>      # ls -Zd /home/without-p
>      drwxr-xr-x. root root unconfined_u:object_r:user_home_dir_t:s0 
> /home/without-p
>      # restorecon -v /home/without-p
>      #
> 
> The coreutils version in CentOS 7 is 8.22.
> 
> coreutils-8.23 compiled from source has the same behaviour.

I see the issue. I had assumed that defaultcon() for the
ancestors was not called if they existed.  That can't be
done without races, so we must call restorecon for the final
component, even if only creating a single dir.
Alternatively you could temp disable o->set_security_context around
make_dir_parents(), but that would be subject to TOCTOU races.

I'll apply something like the following.

thanks!
Pádraig.

diff --git a/src/mkdir.c b/src/mkdir.c
index 404a04a..ff51ae1 100644
--- a/src/mkdir.c
+++ b/src/mkdir.c
@@ -151,23 +151,11 @@ static int
 process_dir (char *dir, struct savewd *wd, void *options)
 {
   struct mkdir_options const *o = options;
-  bool set_defaultcon = false;

   /* If possible set context before DIR created.  */
   if (o->set_security_context)
     {
-      if (! o->make_ancestor_function)
-        set_defaultcon = true;
-      else
-        {
-          char *pdir = dir_name (dir);
-          struct stat st;
-          if (STREQ (pdir, ".")
-              || (stat (pdir, &st) == 0 && S_ISDIR (st.st_mode)))
-            set_defaultcon = true;
-          free (pdir);
-        }
-      if (set_defaultcon && defaultcon (dir, S_IFDIR) < 0
+      if (! o->make_ancestor_function && defaultcon (dir, S_IFDIR) < 0
           && ! ignorable_ctx_err (errno))
         error (0, errno, _("failed to set default creation context for %s"),
                quote (dir));
@@ -184,7 +172,8 @@ process_dir (char *dir, struct savewd *wd, void *options)
      final component of DIR is created.  So for now, create the
      final component with the context from previous component
      and here we set the context for the final component. */
-  if (ret == EXIT_SUCCESS && o->set_security_context && ! set_defaultcon)
+  if (ret == EXIT_SUCCESS && o->set_security_context
+      && o->make_ancestor_function)
     {
       if (! restorecon (last_component (dir), false, false)
           && ! ignorable_ctx_err (errno))