summaryrefslogtreecommitdiffstats
path: root/etc/rc.d/jail
blob: d35f3f70f139283fa742237eb9b02e04e459f5a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#!/bin/sh
#
# $FreeBSD$
#

# PROVIDE: jail
# REQUIRE: LOGIN cleanvar
# BEFORE: securelevel
# KEYWORD: nojail shutdown

# WARNING: This script deals with untrusted data (the data and
# processes inside the jails) and care must be taken when changing the
# code related to this!  If you have any doubt whether a change is
# correct and have security impact, please get the patch reviewed by
# the FreeBSD Security Team prior to commit.

. /etc/rc.subr

name="jail"
rcvar=`set_rcvar`
start_cmd="jail_start"
stop_cmd="jail_stop"

# init_variables _j
#	Initialize the various jail variables for jail _j.
#
init_variables()
{
	_j="$1"

	if [ -z "$_j" ]; then
		warn "init_variables: you must specify a jail"
		return
	fi

	eval _rootdir=\"\$jail_${_j}_rootdir\"
	_devdir="${_rootdir}/dev"
	_fdescdir="${_devdir}/fd"
	_procdir="${_rootdir}/proc"
	eval _hostname=\"\$jail_${_j}_hostname\"
	eval _ip=\"\$jail_${_j}_ip\"
	eval _interface=\"\${jail_${_j}_interface:-${jail_interface}}\"
	eval _exec=\"\$jail_${_j}_exec\"
	eval _exec_start=\"\${jail_${_j}_exec_start:-${jail_exec_start}}\"

	i=1
	while [ true ]; do
		eval _exec_afterstart${i}=\"\${jail_${_j}_exec_afterstart${i}:-\${jail_exec_afterstart${i}}}\"
		[ -z "$(eval echo \"\$_exec_afterstart${i}\")" ] &&  break
		i=$((i + 1))
	done
	
	eval _exec_stop=\"\${jail_${_j}_exec_stop:-${jail_exec_stop}}\"
	if [ -n "${_exec}" ]; then
		#   simple/backward-compatible execution
		_exec_start="${_exec}"
		_exec_stop=""
	else
		#   flexible execution
		if [ -z "${_exec_start}" ]; then
			_exec_start="/bin/sh /etc/rc"
			if [ -z "${_exec_stop}" ]; then
				_exec_stop="/bin/sh /etc/rc.shutdown"
			fi
		fi
	fi

	# The default jail ruleset will be used by rc.subr if none is specified.
	eval _ruleset=\"\${jail_${_j}_devfs_ruleset:-${jail_devfs_ruleset}}\"
	eval _devfs=\"\${jail_${_j}_devfs_enable:-${jail_devfs_enable}}\"
	[ -z "${_devfs}" ] && _devfs="NO"
	eval _fdescfs=\"\${jail_${_j}_fdescfs_enable:-${jail_fdescfs_enable}}\"
	[ -z "${_fdescfs}" ] && _fdescfs="NO"
	eval _procfs=\"\${jail_${_j}_procfs_enable:-${jail_procfs_enable}}\"
	[ -z "${_procfs}" ] && _procfs="NO"

	eval _mount=\"\${jail_${_j}_mount_enable:-${jail_mount_enable}}\"
	[ -z "${_mount}" ] && _mount="NO"
	# "/etc/fstab.${_j}" will be used for {,u}mount(8) if none is specified.
	eval _fstab=\"\${jail_${_j}_fstab:-${jail_fstab}}\"
	[ -z "${_fstab}" ] && _fstab="/etc/fstab.${_j}"
	eval _flags=\"\${jail_${_j}_flags:-${jail_flags}}\"
	[ -z "${_flags}" ] && _flags="-l -U root"
	eval _consolelog=\"\${jail_${_j}_consolelog:-${jail_consolelog}}\"
	[ -z "${_consolelog}" ] && _consolelog="/var/log/jail_${_j}_console.log"

	# Debugging aid
	#
	debug "$_j devfs enable: $_devfs"
	debug "$_j fdescfs enable: $_fdescfs"
	debug "$_j procfs enable: $_procfs"
	debug "$_j mount enable: $_mount"
	debug "$_j hostname: $_hostname"
	debug "$_j ip: $_ip"
	debug "$_j interface: $_interface"
	debug "$_j root: $_rootdir"
	debug "$_j devdir: $_devdir"
	debug "$_j fdescdir: $_fdescdir"
	debug "$_j procdir: $_procdir"
	debug "$_j ruleset: $_ruleset"
	debug "$_j fstab: $_fstab"
	debug "$_j exec start: $_exec_start"
	debug "$_j consolelog: $_consolelog"

	i=1
	while [ true ]; do
		eval out=\"\${_exec_afterstart${i}:-''}\"

		if [ -z "$out" ]; then
			break;
		fi

		debug "$_j exec after start #${i}: ${out}"
		i=$((i + 1))
	done

	debug "$_j exec stop: $_exec_stop"
	debug "$_j flags: $_flags"
	debug "$_j consolelog: $_consolelog"

	if [ -z "${_hostname}" ]; then
		err 3 "$name: No hostname has been defined for ${_j}"
	fi
	if [ -z "${_rootdir}" ]; then
		err 3 "$name: No root directory has been defined for ${_j}"
	fi
	if [ -z "${_ip}" ]; then
		err 3 "$name: No IP address has been defined for ${_j}"
	fi

}

# set_sysctl rc_knob mib msg
#	If the mib sysctl is set according to what rc_knob
#	specifies, this function does nothing. However if
#	rc_knob is set differently than mib, then the mib
#	is set accordingly and msg is displayed followed by
#	an '=" sign and the word 'YES' or 'NO'.
#
set_sysctl()
{
	_knob="$1"
	_mib="$2"
	_msg="$3"

	_current=`${SYSCTL} -n $_mib 2>/dev/null`
	if checkyesno $_knob ; then
		if [ "$_current" -ne 1 ]; then
			echo -n " ${_msg}=YES"
			${SYSCTL_W} 1>/dev/null ${_mib}=1
		fi
	else
		if [ "$_current" -ne 0 ]; then
			echo -n " ${_msg}=NO"
			${SYSCTL_W} 1>/dev/null ${_mib}=0
		fi
	fi
}

# is_current_mountpoint()
#	Is the directory mount point for a currently mounted file
#	system?
#
is_current_mountpoint()
{
	local _dir _dir2

	_dir=$1

	_dir=`echo $_dir | sed -Ee 's#//+#/#g' -e 's#/$##'`
	[ ! -d "${_dir}" ] && return 1
	_dir2=`df ${_dir} | tail +2 | awk '{ print $6 }'`
	[ "${_dir}" = "${_dir2}" ]
	return $?
}

# is_symlinked_mountpoint()
#	Is a mount point, or any of its parent directories, a symlink?
#
is_symlinked_mountpoint()
{
	local _dir

	_dir=$1

	[ -L "$_dir" ] && return 0
	[ "$_dir" = "/" ] && return 1
	is_symlinked_mountpoint `dirname $_dir`
	return $?
}

# secure_umount
#	Try to unmount a mount point without being vulnerable to
#	symlink attacks.
#
secure_umount()
{
	local _dir

	_dir=$1

	if is_current_mountpoint ${_dir}; then
		umount -f ${_dir} >/dev/null 2>&1
	else
		debug "Nothing mounted on ${_dir} - not unmounting"
	fi
}


# jail_umount_fs
#	This function unmounts certain special filesystems in the
#	currently selected jail. The caller must call the init_variables()
#	routine before calling this one.
#
jail_umount_fs()
{
	local _device _mountpt _rest

	if checkyesno _fdescfs; then
		if [ -d "${_fdescdir}" ] ; then
			secure_umount ${_fdescdir}
		fi
	fi
	if checkyesno _devfs; then
		if [ -d "${_devdir}" ] ; then
			secure_umount ${_devdir}
		fi
	fi
	if checkyesno _procfs; then
		if [ -d "${_procdir}" ] ; then
			secure_umount ${_procdir}
		fi
	fi
	if checkyesno _mount; then
		[ -f "${_fstab}" ] || warn "${_fstab} does not exist"
		tail -r ${_fstab} | while read _device _mountpt _rest; do
			case ":${_device}" in
			:#* | :)
				continue
				;;
			esac
			secure_umount ${_mountpt}
		done
	fi
}

# jail_mount_fstab()
#	Mount file systems from a per jail fstab while trying to
#	secure against symlink attacks at the mount points.
#
#	If we are certain we cannot secure against symlink attacks we
#	do not mount all of the file systems (since we cannot just not
#	mount the file system with the problematic mount point).
#
#	The caller must call the init_variables() routine before
#	calling this one.
#
jail_mount_fstab()
{
	local _device _mountpt _rest

	while read _device _mountpt _rest; do
		case ":${_device}" in
		:#* | :)
			continue
			;;
		esac
		if is_symlinked_mountpoint ${_mountpt}; then
			warn "${_mountpt} has symlink as parent - not mounting from ${_fstab}"
			return
		fi
	done <${_fstab}
	mount -a -F "${_fstab}"
}

jail_start()
{
	echo -n 'Configuring jails:'
	set_sysctl jail_set_hostname_allow security.jail.set_hostname_allowed \
	    set_hostname_allow
	set_sysctl jail_socket_unixiproute_only \
	    security.jail.socket_unixiproute_only unixiproute_only
	set_sysctl jail_sysvipc_allow security.jail.sysvipc_allowed \
	    sysvipc_allow
	echo '.'

	echo -n 'Starting jails:'
	_tmp_dir=`mktemp -d /tmp/jail.XXXXXXXX` || \
	    err 3 "$name: Can't create temp dir, exiting..."
	for _jail in ${jail_list}
	do
		init_variables $_jail
		if [ -f /var/run/jail_${_jail}.id ]; then
			echo -n " [${_hostname} already running (/var/run/jail_${_jail}.id exists)]"
			continue;
		fi
		if [ -n "${_interface}" ]; then
			ifconfig ${_interface} alias ${_ip} netmask 255.255.255.255
		fi
		if checkyesno _mount; then
			info "Mounting fstab for jail ${_jail} (${_fstab})"
			if [ ! -f "${_fstab}" ]; then
				err 3 "$name: ${_fstab} does not exist"
			fi
			jail_mount_fstab
		fi
		if checkyesno _devfs; then
			# If devfs is already mounted here, skip it.
			df -t devfs "${_devdir}" >/dev/null
			if [ $? -ne 0 ]; then
				if is_symlinked_mountpoint ${_devdir}; then
					warn "${_devdir} has symlink as parent - not starting jail ${_jail}"
					continue
				fi
				info "Mounting devfs on ${_devdir}"
				devfs_mount_jail "${_devdir}" ${_ruleset}
				# Transitional symlink for old binaries
				if [ ! -L "${_devdir}/log" ]; then
					__pwd="`pwd`"
					cd "${_devdir}"
					ln -sf ../var/run/log log
					cd "$__pwd"
				fi
			fi

			# XXX - It seems symlinks don't work when there
			#	is a devfs(5) device of the same name.
			# Jail console output
			#	__pwd="`pwd`"
			#	cd "${_devdir}"
			#	ln -sf ../var/log/console console
			#	cd "$__pwd"
		fi
		if checkyesno _fdescfs; then
			if is_symlinked_mountpoint ${_fdescdir}; then
				warn "${_fdescdir} has symlink as parent, not mounting"
			else
				info "Mounting fdescfs on ${_fdescdir}"
				mount -t fdescfs fdesc "${_fdescdir}"
			fi
		fi
		if checkyesno _procfs; then
			if is_symlinked_mountpoint ${_procdir}; then
				warn "${_procdir} has symlink as parent, not mounting"
			else
				info "Mounting procfs onto ${_procdir}"
				if [ -d "${_procdir}" ] ; then
					mount -t procfs proc "${_procdir}"
				fi
			fi
		fi
		_tmp_jail=${_tmp_dir}/jail.$$
		eval jail ${_flags} -i ${_rootdir} ${_hostname} \
			${_ip} ${_exec_start} > ${_tmp_jail} 2>&1

		if [ "$?" -eq 0 ] ; then
			_jail_id=$(head -1 ${_tmp_jail})
			i=1
			while [ true ]; do
				eval out=\"\${_exec_afterstart${i}:-''}\"

				if [ -z "$out" ]; then
					break;
				fi

				jexec "${_jail_id}" ${out}
				i=$((i + 1))
			done

			echo -n " $_hostname"
			tail +2 ${_tmp_jail} >${_consolelog}
			echo ${_jail_id} > /var/run/jail_${_jail}.id
		else
			jail_umount_fs
			if [ -n "${_interface}" ]; then
				ifconfig ${_interface} -alias ${_ip}
			fi
			echo " cannot start jail \"${_jail}\": "
			tail +2 ${_tmp_jail}
		fi
		rm -f ${_tmp_jail}
	done
	rmdir ${_tmp_dir}
	echo '.'
}

jail_stop()
{
	echo -n 'Stopping jails:'
	for _jail in ${jail_list}
	do
		if [ -f "/var/run/jail_${_jail}.id" ]; then
			_jail_id=$(cat /var/run/jail_${_jail}.id)
			if [ ! -z "${_jail_id}" ]; then
				init_variables $_jail
				if [ -n "${_exec_stop}" ]; then
					eval env -i /usr/sbin/jexec ${_jail_id} ${_exec_stop} \
						>> ${_consolelog} 2>&1
				fi
				killall -j ${_jail_id} -TERM > /dev/null 2>&1
				sleep 1
				killall -j ${_jail_id} -KILL > /dev/null 2>&1
				jail_umount_fs
				echo -n " $_hostname"
			fi
			if [ -n "${_interface}" ]; then
				ifconfig ${_interface} -alias ${_ip}
			fi
			rm /var/run/jail_${_jail}.id
		else
			echo " cannot stop jail ${_jail}. No jail id in /var/run"
		fi
	done
	echo '.'
}

load_rc_config $name
cmd="$1"
if [ $# -gt 0 ]; then
	shift
fi
if [ -n "$*" ]; then
	jail_list="$*"
fi
run_rc_command "${cmd}"
OpenPOWER on IntegriCloud