#!/bin/sh
# James Budiono 2011, 2013, 2015, 2017, 2018
# fatdog interactive secure sandbox
# this version uses tmpfs instead of an rw image.
# you can also choose which SFS to use.
# run this from terminal.
#
# version 1 - forked from version 9 of sandbox.sh
# version 2 - support user namespaces when available
# version 3 - updated support for lxc 1.0.4 (unprivileged container)
# version 4 - proper termination if accidentally closed
# version 5 - support command in arguments
# env: IDMAP=yes - enable use of user namespaces

# 0. directory locations
. $BOOTSTATE_PATH # AUFS_ROOT_ID, BASELINE_MOUNT
[ "$NOT_USING_AUFS" ] && exit 1
XTERM="defaultterm"
SANDBOX_ROOT=/mnt/sb
FAKEROOT=$SANDBOX_ROOT/fakeroot   # mounted chroot location of sandbox - ie, the fake root
SANDBOX_TMPFS=$SANDBOX_ROOT/sandbox # mounted rw location of tmpfs used for sandbox
SANDBOX_ID=
LXC_ID=sb-lxc	# LXC container ID
USE_DESKTOP=	# use desktop emulation? (less secure, must share /tmp & ipc)
TMPFILE=$(mktemp -p /tmp)

# user namespaces settings
#IDMAP=			# enable IDMAP=yes on env to use user namespaces
LXC_UID=${LXC_UID:-100000}          # UID and GID when running under user namespaces
LXC_GID=${LXC_GID:-100000}
LXC_IDRANGE=${LXC_IDRANGE:-65536}   # no of ids to map under user name spaces

# umount all if we are accidentally killed
trap '[ $CONSOLE_PID] && kill $CONSOLE_PID; 
[ ${LXC_ID} ] && lxc-stop -n ${LXC_ID}; umountall' HUP
umountall() {
	{
	lxc-wait -n $LXC_ID -s STOPPED
	umount -l $FAKEROOT/$SANDBOX_TMPFS
	if [ "$USE_DESKTOP" ]; then
		umount -l $FAKEROOT/tmp
		xauth remove ${LXC_ID}/unix:0
	fi
	
	umount -l $FAKEROOT
	umount -l $SANDBOX_TMPFS
	rmdir $FAKEROOT
	rmdir $SANDBOX_TMPFS
	rm ${FAKEROOT}.hold
	} 2> /dev/null
}

die() {
	echo "$1"
	exit 1
}

# 0.1 must be root
[ $(id -u) -ne 0 ] && die "You must be root to use sandbox."

# 0.2 cannot launch sandbox within sandbox
grep -q LXC_ID $BOOTSTATE_PATH &&
die "Cannot launch sandbox within sandbox."

# 0.3 help
case "$1" in
	--help|-h)
	echo "Usage: ${0##*/} [cmd [args]]"
	echo "Starts an in-memory (throwaway) sandbox using lxc. Type 'poweroff' to leave."
	exit
esac

# 0.4 - if not running from terminal but in Xorg, then launch via terminal
! [ -t 0 ] && [ -n "$DISPLAY" ] && exec $XTERM -e "$0" "$@"
! [ -t 0 ] && exit

# 0.5 is this the first sandbox? If not, then create another name for mountpoints
if grep -q $FAKEROOT /proc/mounts; then
	FAKEROOT=$(mktemp -d -p $SANDBOX_ROOT ${FAKEROOT##*/}-XXXXXXX)
	SANDBOX_ID="-${FAKEROOT##*-}"
	SANDBOX_TMPFS=$SANDBOX_ROOT/${SANDBOX_TMPFS##*/}${SANDBOX_ID}
	LXC_ID=${LXC_ID}${SANDBOX_ID}
	rmdir $FAKEROOT
fi

# 0.6 check for lxc
if ! type lxc-start > /dev/null 2>&1; then
	die "LXC not found, please use 'sandbox.sh' instead."
fi

# 1. get aufs system-id for the root filesystem
[ -z "$AUFS_ROOT_ID" ] && AUFS_ROOT_ID=$(
awk '{ if ($2 == "/" && $3 == "aufs") { match($4,/si=[0-9a-f]*/); print "si_" substr($4,RSTART+3,RLENGTH-3) } }' /proc/mounts
)

# 2. get branches, then map branches to mount types or loop devices 
items=$(
{ echo ==mount==; cat /proc/mounts; 
  echo ==losetup==; losetup-FULL -a; 
  echo ==branches==; ls -v /sys/fs/aufs/$AUFS_ROOT_ID/br[0-9]* | xargs sed 's/=.*//'; } | awk '
  /==mount==/ { mode=1 }
  /==losetup==/ { mode=2 }
  /==branches==/ { mode=3 }
  {
	if (mode == 1) {
		# get list of mount points, types, and devices - index is $3 (mount points)
		mountdev[$2]=$1
		mounttypes[$2]=$3
	} else if (mode == 2) {
		# get list of loop devices and files - index is $1 (loop devs)
		sub(/:/,"",$1)
		sub(/.*\//,"",$3); sub(/)/,"",$3)
		loopdev[$1]=$3
	} else if (mode == 3) {
		# map mount types to loop files if mount devices is a loop
		for (m in mountdev) {
			if ( loopdev[mountdev[m]] != "" ) mounttypes[m]=loopdev[mountdev[m]]
		}
		# for (m in mountdev) print m " on " mountdev[m] " type " mounttypes[m]
		mode=4
	} else if (mode == 4) {
		# print the branches and its mappings
		print $0, mounttypes[$0], "on"
	}
  }  
'
)
# '
# 3. Ask user to choose the SFS
[ -z "$items" ] && die "No layered filesystem found."
dialog --separate-output --backtitle "tmpfs sandbox" --title "sandbox config" \
	--checklist "Choose which SFS you want to use" 0 60 10 $items 2> $TMPFILE
chosen="$(cat $TMPFILE)"

clear
[ -z "$chosen" ] && die "Cancelled or no SFS is chosen, exiting."

# 3.1 lxc - enable desktop emulation?
dialog --backtitle "tmpfs sandbox" --title "use xorg" \
       --yesno "Enable desktop emulation? (less secure if yes)" 0 0 && USE_DESKTOP=yes
clear

# 4. convert chosen SFS to robranches
robranches=""
for a in $(cat $TMPFILE) ; do
	robranches=$robranches:$a=ro
done
rm $TMPFILE

# 5. make the mountpoints if not exist  yet
mkdir -p $FAKEROOT $SANDBOX_TMPFS

# 6. do the magic - mount the tmpfs first, and then the rest with aufs
if mount -t tmpfs none $SANDBOX_TMPFS; then
	chmod 0777 $SANDBOX_TMPFS
	if mount -t aufs -o "br:$SANDBOX_TMPFS=rw$robranches" aufs $FAKEROOT; then
		# 7. record our new aufs-root-id so tools don't hack real filesystem
		SANDBOX_AUFS_ID=$(grep $FAKEROOT /proc/mounts | sed 's/.*si=/si_/; s/ .*//') #'
		sed -i -e '/AUFS_ROOT_ID/ d; /LXC_ID/ d' $FAKEROOT/$BOOTSTATE_PATH 2> /dev/null
		echo AUFS_ROOT_ID=$SANDBOX_AUFS_ID >> $FAKEROOT/$BOOTSTATE_PATH
		echo LXC_ID=$LXC_ID >> $FAKEROOT/$BOOTSTATE_PATH
		
		# 8. sandbox is ready, now just need to mount other supports - pts, proc, sysfs, usb and tmp
		mkdir -p $FAKEROOT/$SANDBOX_TMPFS
		mount -o bind $SANDBOX_TMPFS $FAKEROOT/$SANDBOX_TMPFS	# so we can access it within sandbox

		# 9. desktop emulation preparation. We copy files regardless because /etc/profile always tries to start xwin
		cp /usr/share/sandbox/lxc/xwin $FAKEROOT/usr/bin
		cp /usr/share/sandbox/lxc/wmexit $FAKEROOT/usr/bin
		LXC_SHARE_IPC=""
		if [ "$USE_DESKTOP" ]; then
			LXC_SHARE_IPC="--share-ipc=1" # share ipc from root container
			mount -o bind /tmp $FAKEROOT/tmp		
			xauth add ${LXC_ID}/unix:0 $(xauth list 2> /dev/null | awk "/^$(hostname)\/unix/"' {print $2, $3; exit}') 2> /dev/null
			echo "[ -z \"\$DISPLAY\" ] && export DISPLAY=$DISPLAY" >> $FAKEROOT/etc/shinit
			rm $FAKEROOT/etc/.XLOADED 2> /dev/null	# hack because of Xephyr always crash the first time			
		fi
				
		# 10. make sure we identify ourself as in sandbox
		echo -e '\nexport PS1="sandbox-lxc'${SANDBOX_ID}'# "' >> $FAKEROOT/etc/shinit #fatdog 600
		sed -i -e '/^PS1/ s/^.*$/PS1="sandbox-lxc'${SANDBOX_ID}'# "/' $FAKEROOT/etc/profile # other puppies
		echo "export TERM=$TERM" >> $FAKEROOT/etc/shinit  # default TERM is screwed, so use host's 
		# hostname
		sed -i -e "/${LXC_ID}/ d" $FAKEROOT/etc/hosts
		echo "127.0.0.1 ${LXC_ID}" >> $FAKEROOT/etc/hosts
		echo "${LXC_ID}" > $FAKEROOT/etc/hostname
		# make sure we can shutdown, remove the acpi_poweroff tests
		sed -i -e '/acpi_poweroff/d' $FAKEROOT/sbin/poweroff $FAKEROOT/sbin/reboot

		# 11. prepare init config
		rm $FAKEROOT/sbin/init; cp $BASELINE_MOUNT/sbin/init $FAKEROOT/sbin/init
		cp /usr/share/sandbox/lxc/inittab $FAKEROOT/etc/inittab 

		# 12. make minimal /dev
		rm -rf $FAKEROOT/dev; mkdir $FAKEROOT/dev
		mknod $FAKEROOT/dev/tty c 5 0; mknod $FAKEROOT/dev/console c 5 1; mkdir $FAKEROOT/dev/pts
		for dev in "null-3" "zero-5" "full-7" "random-8" "urandom-9"; do
			mknod $FAKEROOT/dev/${dev%-*} c 1 ${dev#*-}
		done
		
		# 13. setup when running with user namespaces 
		if [ "$IDMAP" ] && zcat /proc/config.gz | grep -q CONFIG_USER_NS=y; then
			echo User namespaces enabled, running as uid/gid $LXC_UID/$LXC_GID
			LXC_AUTO_MOUNT="-s lxc.mount.auto='proc cgroup sys'"
			LXC_UID_MAP="-s lxc.id_map='u 0 $LXC_UID $LXC_IDRANGE'"
			LXC_GID_MAP="-s lxc.id_map='g 0 $LXC_GID $LXC_IDRANGE'"
			LXC_DROP_CAP=
			chown -Rh $LXC_UID:$LXC_GID $FAKEROOT/dev
			rm -rf $FAKEROOT/root; cp -a /root $FAKEROOT
			chown    $LXC_UID:$LXC_GID $FAKEROOT/
			chown -Rh $LXC_UID:$LXC_GID $FAKEROOT/etc $FAKEROOT/root
		else
			echo *Not* using user namespaces, dropping capabilities
			LXC_AUTO_MOUNT="-s lxc.mount.auto='proc cgroup sys'"
			LXC_UID_MAP= 
			LXC_GID_MAP=
			LXC_DROP_CAP="-s lxc.cap.drop='sys_module sys_time sys_rawio syslog'"
		fi

		# 14. adjust sysinit/shutdown commands
		sed -i -e '/rc.sysinit.lxc/d; /rc.shutdown.lxc/d' $FAKEROOT/etc/inittab

		# 14.1. append oneshot commands
		if [ $# -gt 0 ]; then
			{	echo "#!/bin/sh" 
				for p in "$@"; do printf "'%s' " "$p"; done
				echo
			} > $FAKEROOT/etc/rc.d/rc.oneshot
			chmod +x $FAKEROOT/etc/rc.d/rc.oneshot
		else
			rm -f $FAKEROOT/etc/rc.d/rc.oneshot
			sed -i -e '/rc.oneshot/d' $FAKEROOT/etc/inittab 
		fi
		
		
		#  We're good to go!
		echo "Starting sandbox (id: ${LXC_ID}) now."
		eval lxc-start -d -f /etc/lxc/default.conf -n $LXC_ID  \
					-s lxc.devttydir=lxc -s lxc.pts=10 -s lxc.tty=4 \
					-s lxc.rootfs=$FAKEROOT \
					-s lxc.utsname=${LXC_ID} -s lxc.network.type=veth -s lxc.network.veth.pair=${LXC_ID} \
					-s lxc.cgroup.devices.deny=a -s lxc.cgroup.devices.allow=\"c *:* rwm\" \
					-s lxc.autodev=0 $LXC_AUTO_MOUNT \
					$LXC_SHARE_IPC $LXC_DROP_CAP $LXC_UID_MAP $LXC_GID_MAP # -o /tmp/lxc-err.log -l TRACE
					#-- busybox getty -n -l /bin/autologin 38400 tty1
		sleep 1		# allow some time for container to start
		
		# start console and restart until container is terminated
		set -m # we need console's pid so we can kill it later		
		while true; do
			lxc-console -q -e g -n "$LXC_ID" -t 1 &
			CONSOLE_PID=$!
			fg %%
			lxc-info -n $LXC_ID >/dev/null 2>&1 || break
		done

		# 14. done - clean up everything 
		umountall
		echo "Leaving sandbox."
	else
		echo "Unable to mount aufs br:$SANDBOX_TMPFS=rw$robranches"
		umount -l $SANDBOX_TMPFS		
	fi
else
	echo "unable to mount tmpfs."
fi
