#!/bin/bash

#
# kasan_install: set up a system to run the KASan kernel. Run with "--uninstall"
# to reverse the setup.
#
# Installs a symlink to the kernel kasan in /System/Library/Kernels/kernel.kasan
# and adds kcsuffix=kasan to boot-args.
#


kernel_name=kernel.kasan
kernel_src=/AppleInternal/CoreOS/xnu_kasan/${kernel_name}
SLK=/System/Library/Kernels/
kernel_dst=${SLK}${kernel_name}

if [[ `whoami` != root ]] ; then
	echo "Re-running with sudo"
	sudo "$0" "$@"
	exit $?
fi

sip_enabled() {
	csrutil status |grep -q enabled
}

prompt() {
	echo -n "$@ [y/N] "
	read ans
	case "$ans" in
		[yY]*) return 0 ;;
		*) return 1 ;;
	esac
}

kasan_install() {

	dosymlink=0
	dobootargs=0

	if [[ ! -f $kernel_src ]] ; then
		echo "No KASan kernel found at $kernel_src"
		exit 1
	fi

	echo -n "Installing KASan kernel... "

	if [[ -L $kernel_dst && $kernel_dst -ef $kernel_src ]] ; then
		echo "already installed."
	elif [[ -f $kernel_dst ]] ; then
		prompt "file exists. Overwrite?" && {
			echo -n "Overwriting KASan kernel... "
			dosymlink=1
		}
	else
		dosymlink=1
	fi

	# Use a temporary directory with a symlink to kernel.kasan. We can ditto
	# from there into /S/L/K, even with SIP enabled.
	[[ $dosymlink -eq 1 ]] && {
		tmp=$(mktemp -d) || exit $?
		ln -s "$kernel_src" "$tmp" || exit $?
		ditto "$tmp" "$SLK" || exit $?
		rm -r "$tmp"
		echo "done."
	}


	echo -n "Checking KASan boot args... "

	bootargs=$(nvram boot-args | cut -f2)
	cursuffix=$(echo $bootargs | sed -n 's/.*kcsuffix=\([^ ]\)/\1/p')

	if [[ "$cursuffix" == kasan ]] ; then
		echo "already set."
	elif [[ -n "$cursuffix" ]] ; then
		prompt "custom kcsuffix ($cursuffix) is set. Overwrite?" && {
			bootargs=$(echo "$bootargs" | sed 's/[ ]*kcsuffix=[^ ]*//')
			dobootargs=1
		}
	else
		prompt "not set. Modify?" && {
			dobootargs=1
		}
	fi

	[[ $dobootargs -eq 1 ]] && {
		echo -n "Adding boot arg kcsuffix=kasan... "
		newlen=$(echo -n "$bootargs kcsuffix=kasan" |wc -c)
		if [[ $newlen -ge 512 ]] ; then
			echo "boot-args too long. Bailing."
			exit 3
		fi

		nvram boot-args="$bootargs kcsuffix=kasan" || exit $?
		echo "done."
	}

	[[ $dosymlink -eq 1 ]] && {
		echo -n "Triggering kernel cache rebuild... "
		touch /System/Library/Extensions || exit $?
		echo "done."
	}

}


kasan_uninstall() {

	echo -n "Removing kasan kernel... "

	dorm=0

	if [[ -L $kernel_dst && $kernel_dst -ef $kernel_src ]] ; then
		dorm=1
	elif [[ -f $kernel_dst ]] ; then
		prompt "unexpected file. Remove anyway?" && {
			dorm=1
		}
	else
		echo "not installed."
	fi

	[[ $dorm -eq 1 ]] && {
		if rm "$kernel_dst" ; then
			echo "done."
		else
			if sip_enabled ; then
				echo "failed due to SIP - this is normal."
			fi
		fi
	}


	echo -n "Removing boot args... "

	bootargs=$(nvram boot-args | cut -f2)
	cursuffix=$(echo $bootargs | sed -n 's/.*kcsuffix=\([^ ]\)/\1/p')

	if [[ $cursuffix == "kasan" ]] ; then
		prompt "remove kcsuffix=kasan?" && {
			echo -n "Removing kcsuffix... "
			bootargs=$(echo "$bootargs" | sed 's/[ ]*kcsuffix=[^ ]*//')
			nvram boot-args="$bootargs"
			echo "done."
		}
	else
		echo "not set."
	fi

}

case "$1" in
	*uninstall|*del*|*remove|*rm)
		kasan_uninstall ;;
	*)
		kasan_install ;;
esac
