Subject: install-sh: avoid (low risk) race in /tmp
From: Pavel Raiskup <[email-masked]>
Date: Sat, 15 Aug 2015 04:40:57 -0400

Ensure that nobody can cross privilege boundaries by pre-creating
symlink on '$tmpdir' path.

Just testing 'mkdir -p' by creating '/tmp/ins$RANDOM-$$/d' is not
safe because '/tmp' directory is usually world-writeable and
'/tmp/ins$RANDOM-$$' content could be pretty easily guessed by
attacker (at least for shells where $RANDOM is not supported).
So, as the first step, create the '/tmp/ins$RANDOM-$$' without -p.
This step would fail early if somebody wanted catch us.

Note that systems that implement (and have enabled)
fs.protected_symlinks kernel feature are not affected even without
this commit.

References:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=760455
https://bugzilla.redhat.com/show_bug.cgi?id=1140725

* lib/install-sh: Implement safer 'mkdir -p' test by running
'$mkdirprog $mkdir_mode "$tmpdir"' first.
(scriptversion): Bump.

--- a/lib/install-sh
+++ b/lib/install-sh
@@ -345,34 +345,41 @@ do
 	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
 	    ;;
 	  *)
+	    # $RANDOM is not portable (e.g. dash);  use it when possible to
+	    # lower collision chance
 	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
-	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+	    trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0
 
+	    # As "mkdir -p" follows symlinks and we work in /tmp possibly;  so
+	    # create the $tmpdir first (and fail if unsuccessful) to make sure
+	    # that nobody tries to guess the $tmpdir name.
 	    if (umask $mkdir_umask &&
-		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+		$mkdirprog $mkdir_mode "$tmpdir" &&
+		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
 	    then
 	      if test -z "$dir_arg" || {
 		   # Check for POSIX incompatibilities with -m.
 		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
 		   # other-writeable bit of parent directory when it shouldn't.
 		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
-		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
+		   test_tmpdir="$tmpdir/a"
+		   ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
 		   case $ls_ld_tmpdir in
 		     d????-?r-*) different_mode=700;;
 		     d????-?--*) different_mode=755;;
 		     *) false;;
 		   esac &&
-		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
-		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+		   $mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
+		     ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
 		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
 		   }
 		 }
 	      then posix_mkdir=:
 	      fi
-	      rmdir "$tmpdir/d" "$tmpdir"
+	      rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
 	    else
 	      # Remove any dirs left behind by ancient mkdir implementations.
-	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+	      rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
 	    fi
 	    trap '' 0;;
 	esac;;