COMMAND
patchadd
SYSTEMS AFFECTED
SunOS
PROBLEM
Jonathan Fortin found following. He was playing around with
patchadd and the bug was found when he issued a
truss -f -o patch.log patchadd patch
where patch was a tarball and then patchadd omitted an error
because of it being a tarball, so then when Jonathan went through
the debug output, he found out that there was a serious race
condition vulnerability.
Line Pid exec call
105: 12869: open64("/tmp/sh12869.1", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
136: 12869: open64("/tmp/sh12869.2", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
481: 12869: open64("/tmp/sh12869.3", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
file "/tmp/sh12869.1":
105: 12869: open64("/tmp/sh12869.1", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
106: 12869: write(3, "\n U s a g e : p a t c".., 482) = 482
107: 12869: close(3)
file "/tmp/sh12869.2":
136: 12869: open64("/tmp/sh12869.2", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
137: 12869: write(3, " m a i l =\n i n s t a n".., 145) = 145
138: 12869: close(3)
file "/tmp/sh12869.3:
481: 12869: open64("/tmp/sh12869.3", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
482: 12869: close(61) Err#9 EBADF
483: 12869: fcntl(3, F_DUPFD, 0x0000003D) = 61
484: 12869: close(3)
When patchadd is executed, It creates a temporary file called
"/tmp/sh<pidofpatchadd>.1" , "/tmp/sh<pidofpatchadd>.2 ,
"/tmp/sh<pidofpatchadd>.3 and assigns them mode 666 then gets
unlink'd upon exit.A vulnerability exist in patchadd, a patch
utility shipped with Solaris, where as if an attacker predicts
the correct pid of the next process before execution of patchadd
by another user or If he creates a fiew hundred symlinks to brute
force the pid before execution of patchadd, he can with a symbolic
link pointing to a specific key system file, overwrite contents of
the file, he can do up to 3 file simultaneously, and user will be
able to do his own modifications to this file since this file
would have world-write permissions resulting in a increase of
privilege and host compromise.
Exploit:
1. Email admin telling him theirs a new patch out there that needs
to be installed.
2. Create a perl/C script that will copy /etc/passwd and
/etc/shadow to a hidden file that you will want to be appended
to /etc/shadow/passwd later on, get the next current available
process, create 2 symlinks and when the current process id is
taken, then stat for /etc/passwd and /etc/shadow to be 666, if
not avail, do it again, when avail, append a user with id 0 no
password to those hidden files, then those files will truncate
/etc/passwd and /etc/shadow then will be appended to them and
send ya an email to login and take advantage!
3. su trojand_user
4. #
SOLUTION
Sun Microsystems does recommend to only install patches at
single-user mode (runlevel S). So no other possibly malicious
user can exploit this ksh behaviour.
Since patchadd is a script the bug it pretty easy to fix, Sun does
intend to release patches. So here is a set of diffs to patchadd
for those that really can't wait. Please note these are personal
diffs and they do not reflect the official opinion of those in
Sun responsible for the patchadd command and may not reflect the
fix that is eventually released.
*** patchadd.orig Wed Dec 20 11:21:38 2000
--- patchadd Wed Dec 20 11:21:42 2000
***************
*** 161,182 ****
function set_globals
{
! EXISTFILES=/tmp/existfiles.$$
! PATCHFILES=/tmp/patchfiles.$$
! PKGCOFILE=/tmp/pkgchk.out.$$
! VALERRFILE=/tmp/valerr.$$
! VALWARNFILE=/tmp/valwarn.$$
! ADMINTFILE=/tmp/admin.tmp.$$
! ADMINFILE=/tmp/admin.$$
! LOGFILE=/tmp/pkgaddlog.$$
! TMP_ARCHIVE=/tmp/TmpArchive.$$
! TMP_FILELIST=/tmp/FileList.$$
! TMP_LIB_DIR=/tmp/TmpLibDir.$$
! INSTPATCHES_FILE=/tmp/MyShowrevFile.$$
! PARAMS_FILE=/tmp/ParamsFile.$$
! RESPONSE_FILE=/tmp/response.$$
! TEMP_REMOTE=/tmp/temp_remote.$$
Obsoletes=
Incompat=
Requires=
--- 161,192 ----
function set_globals
{
! if [ -z "$WORKDIR" ]; then
! safedir=${TMPDIR:-/tmp}/patchadd${RANDOM}$$
! mkdir -m 700 $safedir
! if [ $? != 0 ]; then
! exit 1;
! fi
! WORKDIR=$safedir
! unset safedir
! fi
+ EXISTFILES=${WORKDIR}/existfiles.$$
+ PATCHFILES=${WORKDIR}/patchfiles.$$
+ PKGCOFILE=${WORKDIR}/pkgchk.out.$$
+ VALERRFILE=${WORKDIR}/valerr.$$
+ VALWARNFILE=${WORKDIR}/valwarn.$$
+ ADMINTFILE=${WORKDIR}/admin.tmp.$$
+ ADMINFILE=${WORKDIR}/admin.$$
+ LOGFILE=${WORKDIR}/pkgaddlog.$$
+ TMP_ARCHIVE=${WORKDIR}/TmpArchive.$$
+ TMP_FILELIST=${WORKDIR}/FileList.$$
+ TMP_LIB_DIR=${WORKDIR}/TmpLibDir.$$
+ INSTPATCHES_FILE=${WORKDIR}/MyShowrevFile.$$
+ PARAMS_FILE=${WORKDIR}/ParamsFile.$$
+ RESPONSE_FILE=${WORKDIR}/response.$$
+ TEMP_REMOTE=${WORKDIR}/temp_remote.$$
+
Obsoletes=
Incompat=
Requires=
***************
*** 305,314 ****
fi
$RM -f $INSTPATCHES_FILE
- $RM -f /tmp/*.$$.1
- $RM -f /tmp/archive.cpio*
- $RM -fr /tmp/*.$$
$RM -f $patchFileStripped
}
#
--- 315,322 ----
fi
$RM -f $INSTPATCHES_FILE
$RM -f $patchFileStripped
+ $RM -rf ${WORKDIR}
}
#
***************
*** 435,441 ****
/usr/bin/gettext "The postpatch script exited with return code $retcode.\n"
if [[ "$isapplied" = "no" ]]
then
! $CP $1/$2/log /tmp/log.$2
/usr/bin/gettext "Backing out patch:\n"
cd $3
if [[ "$ROOTDIR" != "/" ]]
--- 443,449 ----
/usr/bin/gettext "The postpatch script exited with return code $retcode.\n"
if [[ "$isapplied" = "no" ]]
then
! $CP $1/$2/log ${WORKDIR}/log.$2
/usr/bin/gettext "Backing out patch:\n"
cd $3
if [[ "$ROOTDIR" != "/" ]]
***************
*** 444,450 ****
else
/usr/sbin/patchrm $2
fi
! /usr/bin/gettext "See /tmp/log.$2 for more details.\n"
else
/usr/bin/gettext "Not backing out patch because this is a re-installation.\nThe system may be in an unstable state!\nSee $1/$2/log for more details.\n" |tee -a $1/$2/log
fi
--- 452,458 ----
else
/usr/sbin/patchrm $2
fi
! /usr/bin/gettext "See ${WORKDIR}/log.$2 for more details.\n"
else
/usr/bin/gettext "Not backing out patch because this is a re-installation.\nThe system may be in an unstable state!\nSee $1/$2/log for more details.\n" |tee -a $1/$2/log
fi
***************
*** 1527,1533 ****
#
function check_for_symbolic_link
{
! $RM -f /tmp/symlink.$$ > /dev/null 2>&1
olddir=$(pwd)
cd $patchdir
for ii in * X
--- 1535,1541 ----
#
function check_for_symbolic_link
{
! $RM -f ${WORKDIR}/symlink.$$ > /dev/null 2>&1
olddir=$(pwd)
cd $patchdir
for ii in * X
***************
*** 1551,1562 ****
symlinks=
symlinks=$($SED -n '/^[^ ]*[ ]*s[ ]/p' $1/$2/$ii/pkgmap)
if [[ "$symlinks" != "" ]]; then
! /usr/bin/gettext "Symbolic link in package $ii.\n" >> /tmp/symlink.$$
fi
done
! if [[ -s /tmp/symlink.$$ ]]
then
! cat /tmp/symlink.$$
/usr/bin/gettext "Symbolic links cannot be part of a patch.\n"
patch_quit 13 "no"
return 0
--- 1559,1570 ----
symlinks=
symlinks=$($SED -n '/^[^ ]*[ ]*s[ ]/p' $1/$2/$ii/pkgmap)
if [[ "$symlinks" != "" ]]; then
! /usr/bin/gettext "Symbolic link in package $ii.\n" >> ${WORKDIR}/symlink.$$
fi
done
! if [[ -s ${WORKDIR}/symlink.$$ ]]
then
! cat ${WORKDIR}/symlink.$$
/usr/bin/gettext "Symbolic links cannot be part of a patch.\n"
patch_quit 13 "no"
return 0
***************
*** 1840,1848 ****
return
fi
! pkgfiles=/tmp/pkgfiles.$$
! resfiles=/tmp/resolvedfiles.$$
! macrofiles=/tmp/pkgmacros.$$
pkginst=
pkginfofile=
patchpkg=
--- 1848,1856 ----
return
fi
! pkgfiles=${WORKDIR}/pkgfiles.$$
! resfiles=${WORKDIR}/resolvedfiles.$$
! macrofiles=${WORKDIR}/pkgmacros.$$
pkginst=
pkginfofile=
patchpkg=
***************
*** 2343,2350 ****
/usr/bin/gettext "Pkgadd of $i package failed with error code $pkgadderr.\n" |tee -a $1/$2/log
if [ "$isapplied" = "no" ]
then
! /usr/bin/gettext "See /tmp/log.$2 for reason for failure.\n"
! $CP $1/$2/log /tmp/log.$2
/usr/bin/gettext "Backing out patch:\n"
cd $3
if [ "$ROOTDIR" != "/" ]
--- 2351,2358 ----
/usr/bin/gettext "Pkgadd of $i package failed with error code $pkgadderr.\n" |tee -a $1/$2/log
if [ "$isapplied" = "no" ]
then
! /usr/bin/gettext "See ${WORKDIR}/log.$2 for reason for failure.\n"
! $CP $1/$2/log ${WORKDIR}/log.$2
/usr/bin/gettext "Backing out patch:\n"
cd $3
if [ "$ROOTDIR" != "/" ]
***************
*** 2988,2998 ****
$FIND . -print > $TMP_FILELIST
cd $ROOTDIR
! cpio -oL -O /tmp/archive.cpio < $EXISTFILES >/dev/null 2>&1
exit_code=$?
cd $TMP_ARCHIVE
! cpio -oAL -O /tmp/archive.cpio < $TMP_FILELIST >/dev/null 2>&1
exit_code=exit_code+$?
cd $ROOTDIR
--- 2996,3006 ----
$FIND . -print > $TMP_FILELIST
cd $ROOTDIR
! cpio -oL -O ${WORKDIR}/archive.cpio < $EXISTFILES >/dev/null 2>&1
exit_code=$?
cd $TMP_ARCHIVE
! cpio -oAL -O ${WORKDIR}/archive.cpio < $TMP_FILELIST >/dev/null 2>&1
exit_code=exit_code+$?
cd $ROOTDIR
***************
*** 3022,3028 ****
then
compress $archive_path/archive.cpio
else
! compress /tmp/archive.cpio
fi
if [ $? = 0 ]
then
--- 3030,3036 ----
then
compress $archive_path/archive.cpio
else
! compress ${WORKDIR}/archive.cpio
fi
if [ $? = 0 ]
then
***************
*** 3035,3041 ****
fi
if [ "$isapplied" = "yes" ]
then
! $CP /tmp/archive.cpio* $1/$2/save
fi
chmod 600 $archive_path/archive.cpio*
$TOUCH $1/$2/.oldfilessaved
--- 3043,3049 ----
fi
if [ "$isapplied" = "yes" ]
then
! $CP ${WORKDIR}/archive.cpio* $1/$2/save
fi
chmod 600 $archive_path/archive.cpio*
$TOUCH $1/$2/.oldfilessaved
***************
*** 3057,3063 ****
then
$MD -m 750 -p $1/$2
fi
! $MV -f /tmp/ACTION.$PatchNum $1/$2 >/dev/null 2>&1
$CP -p README.$2 $1/$2 >/dev/null 2>&1
# Note the following line should be removed for 2.7.
--- 3065,3071 ----
then
$MD -m 750 -p $1/$2
fi
! $MV -f ${WORKDIR}/ACTION.$PatchNum $1/$2 >/dev/null 2>&1
$CP -p README.$2 $1/$2 >/dev/null 2>&1
# Note the following line should be removed for 2.7.
***************
*** 3092,3102 ****
else
if [[ "$PATCH_UNDO_ARCHIVE" != "none" ]]
then
! $CP /tmp/archive.cpio* $PATCH_UNDO_ARCHIVE/$2
else
! $CP /tmp/archive.cpio* $1/$2/save
fi
! $RM -f /tmp/archive.cpio*
/usr/bin/gettext "Patchadd Interrupted.\n" >> $1/$2/log
fi
patch_quit 12 "yes"
--- 3100,3110 ----
else
if [[ "$PATCH_UNDO_ARCHIVE" != "none" ]]
then
! $CP ${WORKDIR}/archive.cpio* $PATCH_UNDO_ARCHIVE/$2
else
! $CP ${WORKDIR}/archive.cpio* $1/$2/save
fi
! $RM -f ${WORKDIR}/archive.cpio*
/usr/bin/gettext "Patchadd Interrupted.\n" >> $1/$2/log
fi
patch_quit 12 "yes"
***************
*** 3119,3125 ****
fi
if [[ "$isapplied" = "yes" ]]
then
! $RM -f /tmp/archive.cpio*
fi
patch_quit 12 "yes"
}
--- 3127,3133 ----
fi
if [[ "$isapplied" = "yes" ]]
then
! $RM -f ${WORKDIR}/archive.cpio*
fi
patch_quit 12 "yes"
}
***************
*** 3132,3138 ****
function trap_notinstalled
{
/usr/bin/gettext "Interrupt signal detected. Patch not installed.\n"
! $RM -fr /tmp/*.$$
$RM -f $INSTPATCHES_FILE
if [[ "$isapplied" = "no" ]]
then
--- 3140,3146 ----
function trap_notinstalled
{
/usr/bin/gettext "Interrupt signal detected. Patch not installed.\n"
! $RM -fr ${WORKDIR}/*.$$
$RM -f $INSTPATCHES_FILE
if [[ "$isapplied" = "no" ]]
then
***************
*** 3411,3417 ****
typeset -i insPs=0
patchFile=""
! patchFileStripped=/tmp/patchDBstripped.$$
if [[ "$validate" = "no" ]]
then
--- 3419,3425 ----
typeset -i insPs=0
patchFile=""
! patchFileStripped=${WORKDIR}/patchDBstripped.$$
if [[ "$validate" = "no" ]]
then
***************
*** 3796,3802 ****
else
if [[ "$netImage" = "boot" ]]
then
! backout_dir=$ROOTDIR/tmp
else
backout_dir=$PKGDB
fi
--- 3804,3810 ----
else
if [[ "$netImage" = "boot" ]]
then
! backout_dir=$ROOTDIR${WORKDIR}
else
backout_dir=$PKGDB
fi
***************
*** 3850,3856 ****
dryrunExit=
dryrunDir=
! dryrunDir="/tmp/$PatchNum.$$"
if [[ ! -d "$dryrunDir" || "$1" != "0" ]]; then
dryrunFailure="yes"
--- 3858,3864 ----
dryrunExit=
dryrunDir=
! dryrunDir="${WORKDIR}/$PatchNum.$$"
if [[ ! -d "$dryrunDir" || "$1" != "0" ]]; then
dryrunFailure="yes"
***************
*** 3955,3967 ****
restore_net_image
fi
! # The .../Boot/.tmp_proto/root needs to be re-mapped to .../Boot/tmp in order
# for the boot image to be patched successfully.
if [[ -z "$RE_MINIROOT_PATCH" ]]; then
! $MOUNT -F lofs -O $ROOTDIR/tmp $ROOTDIR/mnt
! $MOUNT -F lofs -O $ROOTDIR/.tmp_proto $ROOTDIR/tmp
! $MOUNT -F lofs -O $ROOTDIR/mnt/root/var $ROOTDIR/tmp/root/var
fi
# At this point patchadd thinks the net install image is just like
--- 3963,3975 ----
restore_net_image
fi
! # The .../Boot/.tmp_proto/root needs to be re-mapped to .../Boot${WORKDIR} in order
# for the boot image to be patched successfully.
if [[ -z "$RE_MINIROOT_PATCH" ]]; then
! $MOUNT -F lofs -O $ROOTDIR${WORKDIR} $ROOTDIR/mnt
! $MOUNT -F lofs -O $ROOTDIR/.tmp_proto $ROOTDIR${WORKDIR}
! $MOUNT -F lofs -O $ROOTDIR/mnt/root/var $ROOTDIR${WORKDIR}/root/var
fi
# At this point patchadd thinks the net install image is just like
***************
*** 3983,3990 ****
fi
if [[ -z "$RE_MINIROOT_PATCH" ]]; then
! $UMOUNT $ROOTDIR/tmp/root/var
! $UMOUNT $ROOTDIR/tmp
$UMOUNT $ROOTDIR/mnt
fi
}
--- 3991,3998 ----
fi
if [[ -z "$RE_MINIROOT_PATCH" ]]; then
! $UMOUNT $ROOTDIR${WORKDIR}/root/var
! $UMOUNT $ROOTDIR${WORKDIR}
$UMOUNT $ROOTDIR/mnt
fi
}
***************
*** 4168,4176 ****
if [[ $pkgadd_code == 5 ]] # administration
then
! mv $LOGFILE /var/tmp/$PatchNum.log.$$ > /dev/null 2>&1
[ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var/tmp/$PatchNum.log.$$ for details\n"
remove_patch_meta_data "$pkg"
[ -n "$pkgsAlreadyInstalled" ] && remove_patch
--- 4176,4184 ----
if [[ $pkgadd_code == 5 ]] # administration
then
! mv $LOGFILE /var${WORKDIR}/$PatchNum.log.$$ > /dev/null 2>&1
[ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var${WORKDIR}/$PatchNum.log.$$ for details\n"
remove_patch_meta_data "$pkg"
[ -n "$pkgsAlreadyInstalled" ] && remove_patch
***************
*** 4188,4196 ****
fi
elif [[ $pkgadd_code != 0 ]]
then
! mv $LOGFILE /var/tmp/$PatchNum.log.$$ > /dev/null 2>&1
[ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var/tmp/$PatchNum.log.$$ for details\n"
# If there are more pkgs in the list skip them
# since this patch will not be installed.
--- 4196,4204 ----
fi
elif [[ $pkgadd_code != 0 ]]
then
! mv $LOGFILE /var${WORKDIR}/$PatchNum.log.$$ > /dev/null 2>&1
[ $? = 0 ] && ! /usr/bin/gettext "\nPkgadd failed. See /var${WORKDIR}/$PatchNum.log.$$ for details\n"
# If there are more pkgs in the list skip them
# since this patch will not be installed.
***************
*** 4321,4331 ****
if [[ "$firstTimeThru" = "yes" ]]
then
if [[ "$PKGADD_DEBUG" = "yes" ]]; then
! pkgadd -v -D /tmp/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist
else
! pkgadd -D /tmp/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist 1>>$LOGFILE </dev/null 2>&1
--- 4329,4339 ----
if [[ "$firstTimeThru" = "yes" ]]
then
if [[ "$PKGADD_DEBUG" = "yes" ]]; then
! pkgadd -v -D ${WORKDIR}/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist
else
! pkgadd -D ${WORKDIR}/$PatchNum.$$ -S -n -a $ADMINTFILE $MOPTION -r $RESPONSE_FILE.1 -R $ROOTDIR -d . $pkglist 1>>$LOGFILE </dev/null 2>&1