jail revision 180563
1#!/bin/sh
2#
3# $FreeBSD: head/etc/rc.d/jail 180563 2008-07-16 19:22:48Z dougb $
4#
5
6# PROVIDE: jail
7# REQUIRE: LOGIN cleanvar
8# BEFORE: securelevel
9# KEYWORD: nojail shutdown
10
11# WARNING: This script deals with untrusted data (the data and
12# processes inside the jails) and care must be taken when changing the
13# code related to this!  If you have any doubt whether a change is
14# correct and have security impact, please get the patch reviewed by
15# the FreeBSD Security Team prior to commit.
16
17. /etc/rc.subr
18
19name="jail"
20rcvar=`set_rcvar`
21start_cmd="jail_start"
22stop_cmd="jail_stop"
23
24# init_variables _j
25#	Initialize the various jail variables for jail _j.
26#
27init_variables()
28{
29	_j="$1"
30
31	if [ -z "$_j" ]; then
32		warn "init_variables: you must specify a jail"
33		return
34	fi
35
36	eval _rootdir=\"\$jail_${_j}_rootdir\"
37	_devdir="${_rootdir}/dev"
38	_fdescdir="${_devdir}/fd"
39	_procdir="${_rootdir}/proc"
40	eval _hostname=\"\$jail_${_j}_hostname\"
41	eval _ip=\"\$jail_${_j}_ip\"
42	eval _interface=\"\${jail_${_j}_interface:-${jail_interface}}\"
43	eval _exec=\"\$jail_${_j}_exec\"
44	eval _exec_start=\"\${jail_${_j}_exec_start:-${jail_exec_start}}\"
45
46	i=1
47	while [ true ]; do
48		eval _exec_afterstart${i}=\"\${jail_${_j}_exec_afterstart${i}:-\${jail_exec_afterstart${i}}}\"
49		[ -z "$(eval echo \"\$_exec_afterstart${i}\")" ] &&  break
50		i=$((i + 1))
51	done
52	
53	eval _exec_stop=\"\${jail_${_j}_exec_stop:-${jail_exec_stop}}\"
54	if [ -n "${_exec}" ]; then
55		#   simple/backward-compatible execution
56		_exec_start="${_exec}"
57		_exec_stop=""
58	else
59		#   flexible execution
60		if [ -z "${_exec_start}" ]; then
61			_exec_start="/bin/sh /etc/rc"
62			if [ -z "${_exec_stop}" ]; then
63				_exec_stop="/bin/sh /etc/rc.shutdown"
64			fi
65		fi
66	fi
67
68	# The default jail ruleset will be used by rc.subr if none is specified.
69	eval _ruleset=\"\${jail_${_j}_devfs_ruleset:-${jail_devfs_ruleset}}\"
70	eval _devfs=\"\${jail_${_j}_devfs_enable:-${jail_devfs_enable}}\"
71	[ -z "${_devfs}" ] && _devfs="NO"
72	eval _fdescfs=\"\${jail_${_j}_fdescfs_enable:-${jail_fdescfs_enable}}\"
73	[ -z "${_fdescfs}" ] && _fdescfs="NO"
74	eval _procfs=\"\${jail_${_j}_procfs_enable:-${jail_procfs_enable}}\"
75	[ -z "${_procfs}" ] && _procfs="NO"
76
77	eval _mount=\"\${jail_${_j}_mount_enable:-${jail_mount_enable}}\"
78	[ -z "${_mount}" ] && _mount="NO"
79	# "/etc/fstab.${_j}" will be used for {,u}mount(8) if none is specified.
80	eval _fstab=\"\${jail_${_j}_fstab:-${jail_fstab}}\"
81	[ -z "${_fstab}" ] && _fstab="/etc/fstab.${_j}"
82	eval _flags=\"\${jail_${_j}_flags:-${jail_flags}}\"
83	[ -z "${_flags}" ] && _flags="-l -U root"
84	eval _consolelog=\"\${jail_${_j}_consolelog:-${jail_consolelog}}\"
85	[ -z "${_consolelog}" ] && _consolelog="/var/log/jail_${_j}_console.log"
86
87	# Debugging aid
88	#
89	debug "$_j devfs enable: $_devfs"
90	debug "$_j fdescfs enable: $_fdescfs"
91	debug "$_j procfs enable: $_procfs"
92	debug "$_j mount enable: $_mount"
93	debug "$_j hostname: $_hostname"
94	debug "$_j ip: $_ip"
95	debug "$_j interface: $_interface"
96	debug "$_j root: $_rootdir"
97	debug "$_j devdir: $_devdir"
98	debug "$_j fdescdir: $_fdescdir"
99	debug "$_j procdir: $_procdir"
100	debug "$_j ruleset: $_ruleset"
101	debug "$_j fstab: $_fstab"
102	debug "$_j exec start: $_exec_start"
103	debug "$_j consolelog: $_consolelog"
104
105	i=1
106	while [ true ]; do
107		eval out=\"\${_exec_afterstart${i}:-''}\"
108
109		if [ -z "$out" ]; then
110			break;
111		fi
112
113		debug "$_j exec after start #${i}: ${out}"
114		i=$((i + 1))
115	done
116
117	debug "$_j exec stop: $_exec_stop"
118	debug "$_j flags: $_flags"
119	debug "$_j consolelog: $_consolelog"
120
121	if [ -z "${_hostname}" ]; then
122		err 3 "$name: No hostname has been defined for ${_j}"
123	fi
124	if [ -z "${_rootdir}" ]; then
125		err 3 "$name: No root directory has been defined for ${_j}"
126	fi
127	if [ -z "${_ip}" ]; then
128		err 3 "$name: No IP address has been defined for ${_j}"
129	fi
130
131}
132
133# set_sysctl rc_knob mib msg
134#	If the mib sysctl is set according to what rc_knob
135#	specifies, this function does nothing. However if
136#	rc_knob is set differently than mib, then the mib
137#	is set accordingly and msg is displayed followed by
138#	an '=" sign and the word 'YES' or 'NO'.
139#
140set_sysctl()
141{
142	_knob="$1"
143	_mib="$2"
144	_msg="$3"
145
146	_current=`${SYSCTL} -n $_mib 2>/dev/null`
147	if checkyesno $_knob ; then
148		if [ "$_current" -ne 1 ]; then
149			echo -n " ${_msg}=YES"
150			${SYSCTL_W} 1>/dev/null ${_mib}=1
151		fi
152	else
153		if [ "$_current" -ne 0 ]; then
154			echo -n " ${_msg}=NO"
155			${SYSCTL_W} 1>/dev/null ${_mib}=0
156		fi
157	fi
158}
159
160# is_current_mountpoint()
161#	Is the directory mount point for a currently mounted file
162#	system?
163#
164is_current_mountpoint()
165{
166	local _dir _dir2
167
168	_dir=$1
169
170	_dir=`echo $_dir | sed -Ee 's#//+#/#g' -e 's#/$##'`
171	[ ! -d "${_dir}" ] && return 1
172	_dir2=`df ${_dir} | tail +2 | awk '{ print $6 }'`
173	[ "${_dir}" = "${_dir2}" ]
174	return $?
175}
176
177# is_symlinked_mountpoint()
178#	Is a mount point, or any of its parent directories, a symlink?
179#
180is_symlinked_mountpoint()
181{
182	local _dir
183
184	_dir=$1
185
186	[ -L "$_dir" ] && return 0
187	[ "$_dir" = "/" ] && return 1
188	is_symlinked_mountpoint `dirname $_dir`
189	return $?
190}
191
192# secure_umount
193#	Try to unmount a mount point without being vulnerable to
194#	symlink attacks.
195#
196secure_umount()
197{
198	local _dir
199
200	_dir=$1
201
202	if is_current_mountpoint ${_dir}; then
203		umount -f ${_dir} >/dev/null 2>&1
204	else
205		debug "Nothing mounted on ${_dir} - not unmounting"
206	fi
207}
208
209
210# jail_umount_fs
211#	This function unmounts certain special filesystems in the
212#	currently selected jail. The caller must call the init_variables()
213#	routine before calling this one.
214#
215jail_umount_fs()
216{
217	local _device _mountpt _rest
218
219	if checkyesno _fdescfs; then
220		if [ -d "${_fdescdir}" ] ; then
221			secure_umount ${_fdescdir}
222		fi
223	fi
224	if checkyesno _devfs; then
225		if [ -d "${_devdir}" ] ; then
226			secure_umount ${_devdir}
227		fi
228	fi
229	if checkyesno _procfs; then
230		if [ -d "${_procdir}" ] ; then
231			secure_umount ${_procdir}
232		fi
233	fi
234	if checkyesno _mount; then
235		[ -f "${_fstab}" ] || warn "${_fstab} does not exist"
236		tail -r ${_fstab} | while read _device _mountpt _rest; do
237			case ":${_device}" in
238			:#* | :)
239				continue
240				;;
241			esac
242			secure_umount ${_mountpt}
243		done
244	fi
245}
246
247# jail_mount_fstab()
248#	Mount file systems from a per jail fstab while trying to
249#	secure against symlink attacks at the mount points.
250#
251#	If we are certain we cannot secure against symlink attacks we
252#	do not mount all of the file systems (since we cannot just not
253#	mount the file system with the problematic mount point).
254#
255#	The caller must call the init_variables() routine before
256#	calling this one.
257#
258jail_mount_fstab()
259{
260	local _device _mountpt _rest
261
262	while read _device _mountpt _rest; do
263		case ":${_device}" in
264		:#* | :)
265			continue
266			;;
267		esac
268		if is_symlinked_mountpoint ${_mountpt}; then
269			warn "${_mountpt} has symlink as parent - not mounting from ${_fstab}"
270			return
271		fi
272	done <${_fstab}
273	mount -a -F "${_fstab}"
274}
275
276jail_start()
277{
278	echo -n 'Configuring jails:'
279	set_sysctl jail_set_hostname_allow security.jail.set_hostname_allowed \
280	    set_hostname_allow
281	set_sysctl jail_socket_unixiproute_only \
282	    security.jail.socket_unixiproute_only unixiproute_only
283	set_sysctl jail_sysvipc_allow security.jail.sysvipc_allowed \
284	    sysvipc_allow
285	echo '.'
286
287	echo -n 'Starting jails:'
288	_tmp_dir=`mktemp -d /tmp/jail.XXXXXXXX` || \
289	    err 3 "$name: Can't create temp dir, exiting..."
290	for _jail in ${jail_list}
291	do
292		init_variables $_jail
293		if [ -f /var/run/jail_${_jail}.id ]; then
294			echo -n " [${_hostname} already running (/var/run/jail_${_jail}.id exists)]"
295			continue;
296		fi
297		if [ -n "${_interface}" ]; then
298			ifconfig ${_interface} alias ${_ip} netmask 255.255.255.255
299		fi
300		if checkyesno _mount; then
301			info "Mounting fstab for jail ${_jail} (${_fstab})"
302			if [ ! -f "${_fstab}" ]; then
303				err 3 "$name: ${_fstab} does not exist"
304			fi
305			jail_mount_fstab
306		fi
307		if checkyesno _devfs; then
308			# If devfs is already mounted here, skip it.
309			df -t devfs "${_devdir}" >/dev/null
310			if [ $? -ne 0 ]; then
311				if is_symlinked_mountpoint ${_devdir}; then
312					warn "${_devdir} has symlink as parent - not starting jail ${_jail}"
313					continue
314				fi
315				info "Mounting devfs on ${_devdir}"
316				devfs_mount_jail "${_devdir}" ${_ruleset}
317				# Transitional symlink for old binaries
318				if [ ! -L "${_devdir}/log" ]; then
319					__pwd="`pwd`"
320					cd "${_devdir}"
321					ln -sf ../var/run/log log
322					cd "$__pwd"
323				fi
324			fi
325
326			# XXX - It seems symlinks don't work when there
327			#	is a devfs(5) device of the same name.
328			# Jail console output
329			#	__pwd="`pwd`"
330			#	cd "${_devdir}"
331			#	ln -sf ../var/log/console console
332			#	cd "$__pwd"
333		fi
334		if checkyesno _fdescfs; then
335			if is_symlinked_mountpoint ${_fdescdir}; then
336				warn "${_fdescdir} has symlink as parent, not mounting"
337			else
338				info "Mounting fdescfs on ${_fdescdir}"
339				mount -t fdescfs fdesc "${_fdescdir}"
340			fi
341		fi
342		if checkyesno _procfs; then
343			if is_symlinked_mountpoint ${_procdir}; then
344				warn "${_procdir} has symlink as parent, not mounting"
345			else
346				info "Mounting procfs onto ${_procdir}"
347				if [ -d "${_procdir}" ] ; then
348					mount -t procfs proc "${_procdir}"
349				fi
350			fi
351		fi
352		_tmp_jail=${_tmp_dir}/jail.$$
353		eval jail ${_flags} -i ${_rootdir} ${_hostname} \
354			${_ip} ${_exec_start} > ${_tmp_jail} 2>&1
355
356		if [ "$?" -eq 0 ] ; then
357			_jail_id=$(head -1 ${_tmp_jail})
358			i=1
359			while [ true ]; do
360				eval out=\"\${_exec_afterstart${i}:-''}\"
361
362				if [ -z "$out" ]; then
363					break;
364				fi
365
366				jexec "${_jail_id}" ${out}
367				i=$((i + 1))
368			done
369
370			echo -n " $_hostname"
371			tail +2 ${_tmp_jail} >${_consolelog}
372			echo ${_jail_id} > /var/run/jail_${_jail}.id
373		else
374			jail_umount_fs
375			if [ -n "${_interface}" ]; then
376				ifconfig ${_interface} -alias ${_ip}
377			fi
378			echo " cannot start jail \"${_jail}\": "
379			tail +2 ${_tmp_jail}
380		fi
381		rm -f ${_tmp_jail}
382	done
383	rmdir ${_tmp_dir}
384	echo '.'
385}
386
387jail_stop()
388{
389	echo -n 'Stopping jails:'
390	for _jail in ${jail_list}
391	do
392		if [ -f "/var/run/jail_${_jail}.id" ]; then
393			_jail_id=$(cat /var/run/jail_${_jail}.id)
394			if [ ! -z "${_jail_id}" ]; then
395				init_variables $_jail
396				if [ -n "${_exec_stop}" ]; then
397					eval env -i /usr/sbin/jexec ${_jail_id} ${_exec_stop} \
398						>> ${_consolelog} 2>&1
399				fi
400				killall -j ${_jail_id} -TERM > /dev/null 2>&1
401				sleep 1
402				killall -j ${_jail_id} -KILL > /dev/null 2>&1
403				jail_umount_fs
404				echo -n " $_hostname"
405			fi
406			if [ -n "${_interface}" ]; then
407				ifconfig ${_interface} -alias ${_ip}
408			fi
409			rm /var/run/jail_${_jail}.id
410		else
411			echo " cannot stop jail ${_jail}. No jail id in /var/run"
412		fi
413	done
414	echo '.'
415}
416
417load_rc_config $name
418cmd="$1"
419if [ $# -gt 0 ]; then
420	shift
421fi
422if [ -n "$*" ]; then
423	jail_list="$*"
424fi
425run_rc_command "${cmd}"
426