jail revision 183100
1164298Srwatson#!/bin/sh
2164298Srwatson#
3164298Srwatson# $FreeBSD: head/etc/rc.d/jail 183100 2008-09-16 20:18:25Z thompsa $
4164298Srwatson#
5164298Srwatson
6164298Srwatson# PROVIDE: jail
7164298Srwatson# REQUIRE: LOGIN cleanvar
8164298Srwatson# BEFORE: securelevel
9164298Srwatson# KEYWORD: nojail shutdown
10164298Srwatson
11164298Srwatson# WARNING: This script deals with untrusted data (the data and
12164298Srwatson# processes inside the jails) and care must be taken when changing the
13164298Srwatson# code related to this!  If you have any doubt whether a change is
14164298Srwatson# correct and have security impact, please get the patch reviewed by
15164298Srwatson# the FreeBSD Security Team prior to commit.
16164298Srwatson
17164298Srwatson. /etc/rc.subr
18164298Srwatson
19164298Srwatsonname="jail"
20164298Srwatsonrcvar=`set_rcvar`
21164298Srwatsonstart_cmd="jail_start"
22164298Srwatsonstop_cmd="jail_stop"
23164298Srwatson
24164298Srwatson# init_variables _j
25164298Srwatson#	Initialize the various jail variables for jail _j.
26164298Srwatson#
27164298Srwatsoninit_variables()
28164298Srwatson{
29164298Srwatson	_j="$1"
30164298Srwatson
31164298Srwatson	if [ -z "$_j" ]; then
32164298Srwatson		warn "init_variables: you must specify a jail"
33164298Srwatson		return
34164298Srwatson	fi
35164298Srwatson
36164298Srwatson	eval _rootdir=\"\$jail_${_j}_rootdir\"
37164298Srwatson	_devdir="${_rootdir}/dev"
38164298Srwatson	_fdescdir="${_devdir}/fd"
39164298Srwatson	_procdir="${_rootdir}/proc"
40164298Srwatson	eval _hostname=\"\$jail_${_j}_hostname\"
41164298Srwatson	eval _ip=\"\$jail_${_j}_ip\"
42164298Srwatson	eval _interface=\"\${jail_${_j}_interface:-${jail_interface}}\"
43164298Srwatson	eval _exec=\"\$jail_${_j}_exec\"
44164298Srwatson	eval _exec_start=\"\${jail_${_j}_exec_start:-${jail_exec_start}}\"
45164298Srwatson
46164298Srwatson	i=1
47164298Srwatson	while [ true ]; do
48164298Srwatson		eval _exec_afterstart${i}=\"\${jail_${_j}_exec_afterstart${i}:-\${jail_exec_afterstart${i}}}\"
49164298Srwatson		[ -z "$(eval echo \"\$_exec_afterstart${i}\")" ] &&  break
50164298Srwatson		i=$((i + 1))
51164298Srwatson	done
52164298Srwatson	
53164298Srwatson	eval _exec_stop=\"\${jail_${_j}_exec_stop:-${jail_exec_stop}}\"
54164298Srwatson	if [ -n "${_exec}" ]; then
55164298Srwatson		#   simple/backward-compatible execution
56164298Srwatson		_exec_start="${_exec}"
57164298Srwatson		_exec_stop=""
58164298Srwatson	else
59164298Srwatson		#   flexible execution
60164298Srwatson		if [ -z "${_exec_start}" ]; then
61164298Srwatson			_exec_start="/bin/sh /etc/rc"
62164298Srwatson			if [ -z "${_exec_stop}" ]; then
63164298Srwatson				_exec_stop="/bin/sh /etc/rc.shutdown"
64164298Srwatson			fi
65164298Srwatson		fi
66164298Srwatson	fi
67164298Srwatson
68164298Srwatson	# The default jail ruleset will be used by rc.subr if none is specified.
69164298Srwatson	eval _ruleset=\"\${jail_${_j}_devfs_ruleset:-${jail_devfs_ruleset}}\"
70164298Srwatson	eval _devfs=\"\${jail_${_j}_devfs_enable:-${jail_devfs_enable}}\"
71164298Srwatson	[ -z "${_devfs}" ] && _devfs="NO"
72164298Srwatson	eval _fdescfs=\"\${jail_${_j}_fdescfs_enable:-${jail_fdescfs_enable}}\"
73164298Srwatson	[ -z "${_fdescfs}" ] && _fdescfs="NO"
74164298Srwatson	eval _procfs=\"\${jail_${_j}_procfs_enable:-${jail_procfs_enable}}\"
75164298Srwatson	[ -z "${_procfs}" ] && _procfs="NO"
76164298Srwatson
77164298Srwatson	eval _mount=\"\${jail_${_j}_mount_enable:-${jail_mount_enable}}\"
78164298Srwatson	[ -z "${_mount}" ] && _mount="NO"
79164298Srwatson	# "/etc/fstab.${_j}" will be used for {,u}mount(8) if none is specified.
80164298Srwatson	eval _fstab=\"\${jail_${_j}_fstab:-${jail_fstab}}\"
81164298Srwatson	[ -z "${_fstab}" ] && _fstab="/etc/fstab.${_j}"
82164298Srwatson	eval _flags=\"\${jail_${_j}_flags:-${jail_flags}}\"
83164298Srwatson	[ -z "${_flags}" ] && _flags="-l -U root"
84164298Srwatson	eval _consolelog=\"\${jail_${_j}_consolelog:-${jail_consolelog}}\"
85164298Srwatson	[ -z "${_consolelog}" ] && _consolelog="/var/log/jail_${_j}_console.log"
86164298Srwatson	eval _fib=\"\${jail_${_j}_fib:-${jail_fib}}\"
87164298Srwatson
88164298Srwatson	# Debugging aid
89164298Srwatson	#
90164298Srwatson	debug "$_j devfs enable: $_devfs"
91164298Srwatson	debug "$_j fdescfs enable: $_fdescfs"
92164298Srwatson	debug "$_j procfs enable: $_procfs"
93164298Srwatson	debug "$_j mount enable: $_mount"
94164298Srwatson	debug "$_j hostname: $_hostname"
95164298Srwatson	debug "$_j ip: $_ip"
96164298Srwatson	debug "$_j interface: $_interface"
97164298Srwatson	debug "$_j fib: $_fib"
98164298Srwatson	debug "$_j root: $_rootdir"
99164298Srwatson	debug "$_j devdir: $_devdir"
100164298Srwatson	debug "$_j fdescdir: $_fdescdir"
101164298Srwatson	debug "$_j procdir: $_procdir"
102164298Srwatson	debug "$_j ruleset: $_ruleset"
103164298Srwatson	debug "$_j fstab: $_fstab"
104164298Srwatson	debug "$_j exec start: $_exec_start"
105164298Srwatson	debug "$_j consolelog: $_consolelog"
106164298Srwatson
107164298Srwatson	i=1
108164298Srwatson	while [ true ]; do
109164298Srwatson		eval out=\"\${_exec_afterstart${i}:-''}\"
110164298Srwatson
111164298Srwatson		if [ -z "$out" ]; then
112164298Srwatson			break;
113164298Srwatson		fi
114164298Srwatson
115164298Srwatson		debug "$_j exec after start #${i}: ${out}"
116164298Srwatson		i=$((i + 1))
117164298Srwatson	done
118164298Srwatson
119164298Srwatson	debug "$_j exec stop: $_exec_stop"
120164298Srwatson	debug "$_j flags: $_flags"
121164298Srwatson	debug "$_j consolelog: $_consolelog"
122164298Srwatson
123164298Srwatson	if [ -z "${_hostname}" ]; then
124164298Srwatson		err 3 "$name: No hostname has been defined for ${_j}"
125164298Srwatson	fi
126164298Srwatson	if [ -z "${_rootdir}" ]; then
127164298Srwatson		err 3 "$name: No root directory has been defined for ${_j}"
128164298Srwatson	fi
129164298Srwatson	if [ -z "${_ip}" ]; then
130164298Srwatson		err 3 "$name: No IP address has been defined for ${_j}"
131164298Srwatson	fi
132164298Srwatson
133164298Srwatson}
134164298Srwatson
135164298Srwatson# set_sysctl rc_knob mib msg
136164298Srwatson#	If the mib sysctl is set according to what rc_knob
137164298Srwatson#	specifies, this function does nothing. However if
138164298Srwatson#	rc_knob is set differently than mib, then the mib
139164298Srwatson#	is set accordingly and msg is displayed followed by
140164298Srwatson#	an '=" sign and the word 'YES' or 'NO'.
141164298Srwatson#
142164298Srwatsonset_sysctl()
143164298Srwatson{
144164298Srwatson	_knob="$1"
145164298Srwatson	_mib="$2"
146164298Srwatson	_msg="$3"
147164298Srwatson
148164298Srwatson	_current=`${SYSCTL} -n $_mib 2>/dev/null`
149164298Srwatson	if checkyesno $_knob ; then
150164298Srwatson		if [ "$_current" -ne 1 ]; then
151164298Srwatson			echo -n " ${_msg}=YES"
152164298Srwatson			${SYSCTL_W} 1>/dev/null ${_mib}=1
153164298Srwatson		fi
154164298Srwatson	else
155164298Srwatson		if [ "$_current" -ne 0 ]; then
156164298Srwatson			echo -n " ${_msg}=NO"
157164298Srwatson			${SYSCTL_W} 1>/dev/null ${_mib}=0
158164298Srwatson		fi
159164298Srwatson	fi
160164298Srwatson}
161164298Srwatson
162164298Srwatson# is_current_mountpoint()
163164298Srwatson#	Is the directory mount point for a currently mounted file
164164298Srwatson#	system?
165164298Srwatson#
166164298Srwatsonis_current_mountpoint()
167164298Srwatson{
168164298Srwatson	local _dir _dir2
169164298Srwatson
170164298Srwatson	_dir=$1
171164298Srwatson
172164298Srwatson	_dir=`echo $_dir | sed -Ee 's#//+#/#g' -e 's#/$##'`
173164298Srwatson	[ ! -d "${_dir}" ] && return 1
174164298Srwatson	_dir2=`df ${_dir} | tail +2 | awk '{ print $6 }'`
175164298Srwatson	[ "${_dir}" = "${_dir2}" ]
176164298Srwatson	return $?
177164298Srwatson}
178164298Srwatson
179164298Srwatson# is_symlinked_mountpoint()
180164298Srwatson#	Is a mount point, or any of its parent directories, a symlink?
181164298Srwatson#
182164298Srwatsonis_symlinked_mountpoint()
183164298Srwatson{
184164298Srwatson	local _dir
185164298Srwatson
186164298Srwatson	_dir=$1
187164298Srwatson
188164298Srwatson	[ -L "$_dir" ] && return 0
189164298Srwatson	[ "$_dir" = "/" ] && return 1
190164298Srwatson	is_symlinked_mountpoint `dirname $_dir`
191164298Srwatson	return $?
192164298Srwatson}
193164298Srwatson
194164298Srwatson# secure_umount
195164298Srwatson#	Try to unmount a mount point without being vulnerable to
196164298Srwatson#	symlink attacks.
197164298Srwatson#
198164298Srwatsonsecure_umount()
199164298Srwatson{
200164298Srwatson	local _dir
201164298Srwatson
202164298Srwatson	_dir=$1
203164298Srwatson
204164298Srwatson	if is_current_mountpoint ${_dir}; then
205164298Srwatson		umount -f ${_dir} >/dev/null 2>&1
206164298Srwatson	else
207164298Srwatson		debug "Nothing mounted on ${_dir} - not unmounting"
208164298Srwatson	fi
209164298Srwatson}
210164298Srwatson
211164298Srwatson
212164298Srwatson# jail_umount_fs
213#	This function unmounts certain special filesystems in the
214#	currently selected jail. The caller must call the init_variables()
215#	routine before calling this one.
216#
217jail_umount_fs()
218{
219	local _device _mountpt _rest
220
221	if checkyesno _fdescfs; then
222		if [ -d "${_fdescdir}" ] ; then
223			secure_umount ${_fdescdir}
224		fi
225	fi
226	if checkyesno _devfs; then
227		if [ -d "${_devdir}" ] ; then
228			secure_umount ${_devdir}
229		fi
230	fi
231	if checkyesno _procfs; then
232		if [ -d "${_procdir}" ] ; then
233			secure_umount ${_procdir}
234		fi
235	fi
236	if checkyesno _mount; then
237		[ -f "${_fstab}" ] || warn "${_fstab} does not exist"
238		tail -r ${_fstab} | while read _device _mountpt _rest; do
239			case ":${_device}" in
240			:#* | :)
241				continue
242				;;
243			esac
244			secure_umount ${_mountpt}
245		done
246	fi
247}
248
249# jail_mount_fstab()
250#	Mount file systems from a per jail fstab while trying to
251#	secure against symlink attacks at the mount points.
252#
253#	If we are certain we cannot secure against symlink attacks we
254#	do not mount all of the file systems (since we cannot just not
255#	mount the file system with the problematic mount point).
256#
257#	The caller must call the init_variables() routine before
258#	calling this one.
259#
260jail_mount_fstab()
261{
262	local _device _mountpt _rest
263
264	while read _device _mountpt _rest; do
265		case ":${_device}" in
266		:#* | :)
267			continue
268			;;
269		esac
270		if is_symlinked_mountpoint ${_mountpt}; then
271			warn "${_mountpt} has symlink as parent - not mounting from ${_fstab}"
272			return
273		fi
274	done <${_fstab}
275	mount -a -F "${_fstab}"
276}
277
278jail_start()
279{
280	echo -n 'Configuring jails:'
281	set_sysctl jail_set_hostname_allow security.jail.set_hostname_allowed \
282	    set_hostname_allow
283	set_sysctl jail_socket_unixiproute_only \
284	    security.jail.socket_unixiproute_only unixiproute_only
285	set_sysctl jail_sysvipc_allow security.jail.sysvipc_allowed \
286	    sysvipc_allow
287	echo '.'
288
289	echo -n 'Starting jails:'
290	_tmp_dir=`mktemp -d /tmp/jail.XXXXXXXX` || \
291	    err 3 "$name: Can't create temp dir, exiting..."
292	for _jail in ${jail_list}
293	do
294		init_variables $_jail
295		if [ -f /var/run/jail_${_jail}.id ]; then
296			echo -n " [${_hostname} already running (/var/run/jail_${_jail}.id exists)]"
297			continue;
298		fi
299		if [ -n "${_interface}" ]; then
300			ifconfig ${_interface} alias ${_ip} netmask 255.255.255.255
301		fi
302		if [ -n "${_fib}" ]; then
303			_setfib="setfib -F '${_fib}'"
304		else
305			_setfib=""
306		fi
307		if checkyesno _mount; then
308			info "Mounting fstab for jail ${_jail} (${_fstab})"
309			if [ ! -f "${_fstab}" ]; then
310				err 3 "$name: ${_fstab} does not exist"
311			fi
312			jail_mount_fstab
313		fi
314		if checkyesno _devfs; then
315			# If devfs is already mounted here, skip it.
316			df -t devfs "${_devdir}" >/dev/null
317			if [ $? -ne 0 ]; then
318				if is_symlinked_mountpoint ${_devdir}; then
319					warn "${_devdir} has symlink as parent - not starting jail ${_jail}"
320					continue
321				fi
322				info "Mounting devfs on ${_devdir}"
323				devfs_mount_jail "${_devdir}" ${_ruleset}
324				# Transitional symlink for old binaries
325				if [ ! -L "${_devdir}/log" ]; then
326					__pwd="`pwd`"
327					cd "${_devdir}"
328					ln -sf ../var/run/log log
329					cd "$__pwd"
330				fi
331			fi
332
333			# XXX - It seems symlinks don't work when there
334			#	is a devfs(5) device of the same name.
335			# Jail console output
336			#	__pwd="`pwd`"
337			#	cd "${_devdir}"
338			#	ln -sf ../var/log/console console
339			#	cd "$__pwd"
340		fi
341		if checkyesno _fdescfs; then
342			if is_symlinked_mountpoint ${_fdescdir}; then
343				warn "${_fdescdir} has symlink as parent, not mounting"
344			else
345				info "Mounting fdescfs on ${_fdescdir}"
346				mount -t fdescfs fdesc "${_fdescdir}"
347			fi
348		fi
349		if checkyesno _procfs; then
350			if is_symlinked_mountpoint ${_procdir}; then
351				warn "${_procdir} has symlink as parent, not mounting"
352			else
353				info "Mounting procfs onto ${_procdir}"
354				if [ -d "${_procdir}" ] ; then
355					mount -t procfs proc "${_procdir}"
356				fi
357			fi
358		fi
359		_tmp_jail=${_tmp_dir}/jail.$$
360		eval ${_setfib} jail ${_flags} -i ${_rootdir} ${_hostname} \
361			${_ip} ${_exec_start} > ${_tmp_jail} 2>&1
362
363		if [ "$?" -eq 0 ] ; then
364			_jail_id=$(head -1 ${_tmp_jail})
365			i=1
366			while [ true ]; do
367				eval out=\"\${_exec_afterstart${i}:-''}\"
368
369				if [ -z "$out" ]; then
370					break;
371				fi
372
373				jexec "${_jail_id}" ${out}
374				i=$((i + 1))
375			done
376
377			echo -n " $_hostname"
378			tail +2 ${_tmp_jail} >${_consolelog}
379			echo ${_jail_id} > /var/run/jail_${_jail}.id
380		else
381			jail_umount_fs
382			if [ -n "${_interface}" ]; then
383				ifconfig ${_interface} -alias ${_ip}
384			fi
385			echo " cannot start jail \"${_jail}\": "
386			tail +2 ${_tmp_jail}
387		fi
388		rm -f ${_tmp_jail}
389	done
390	rmdir ${_tmp_dir}
391	echo '.'
392}
393
394jail_stop()
395{
396	echo -n 'Stopping jails:'
397	for _jail in ${jail_list}
398	do
399		if [ -f "/var/run/jail_${_jail}.id" ]; then
400			_jail_id=$(cat /var/run/jail_${_jail}.id)
401			if [ ! -z "${_jail_id}" ]; then
402				init_variables $_jail
403				if [ -n "${_exec_stop}" ]; then
404					eval env -i /usr/sbin/jexec ${_jail_id} ${_exec_stop} \
405						>> ${_consolelog} 2>&1
406				fi
407				killall -j ${_jail_id} -TERM > /dev/null 2>&1
408				sleep 1
409				killall -j ${_jail_id} -KILL > /dev/null 2>&1
410				jail_umount_fs
411				echo -n " $_hostname"
412			fi
413			if [ -n "${_interface}" ]; then
414				ifconfig ${_interface} -alias ${_ip}
415			fi
416			rm /var/run/jail_${_jail}.id
417		else
418			echo " cannot stop jail ${_jail}. No jail id in /var/run"
419		fi
420	done
421	echo '.'
422}
423
424load_rc_config $name
425cmd="$1"
426if [ $# -gt 0 ]; then
427	shift
428fi
429if [ -n "$*" ]; then
430	jail_list="$*"
431fi
432run_rc_command "${cmd}"
433