xref: /xnu-8019.80.24/tools/tests/personas/persona_test_run_src.sh (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1#!/bin/bash
2# persona_test_run.sh
3#
4# This file aims to be a comprehensive test suite for the persona subsystem.
5# It uses two tools:
6#   1. persona_mgr - create, destroy, lookup personas
7#   2. persona_spawn - spawn processes into personas with a variety of options
8# The script relies heavily on the particular output of these tools, so if you
9# are modifying / extending those tools, this file also need to be updated to
10# properly capture the new output. Specifically, the get_persona_info function
11# needs to be maintained / updated.
12#
13# NOTE: the function get_persona_info() also needs to be kept up to date with
14# the types of personas found in bsd/sys/persona.h
15
16PERSONA_MGR="${PWD}/persona_mgr"
17PERSONA_SPAWN="${PWD}/persona_spawn"
18PERSONA_SPAWN_UNENTITLED="${PWD}/persona_spawn_unentitled"
19
20TEST_DEFAULT_PERSONA=0
21
22if [ ! -d "$TMPDIR" ]; then
23	echo "Couldn't find temp directory '$TMPDIR': check permissions/environment?"
24	exit 255
25fi
26
27if [ ! -e "${PERSONA_MGR}" ] ||  [ ! -x "${PERSONA_MGR}" ]; then
28	echo "Can't find '${PERSONA_MGR}': skipping test"
29	exit 0
30fi
31if [ ! -e "${PERSONA_SPAWN}" ] || [ ! -x "${PERSONA_SPAWN}" ]; then
32	echo "Can't find '${PERSONA_SPAWN}': skipping test"
33	exit 0
34fi
35
36function check_for_persona_support() {
37	local errno=0
38	${PERSONA_MGR} support || errno=$?
39	if [ $errno -eq 78 ]; then
40		echo "Persona subsystem is not supported - skipping tests"
41		exit 0
42	fi
43	return 0
44}
45check_for_persona_support
46
47
48## bail [failure_msg]
49#
50# exit the script with an error code that corresponds to the line number
51# from which this function was invoked. Because we want to exit with a
52# non-zero exit code, we use: 1 + (254 % line).
53#
54function bail() {
55	local msg="$1"
56	local line=$2
57	if [ -z "$line" ]; then
58		line=${BASH_LINENO[0]}
59	fi
60	echo "[$line] ERROR: $msg" 1>&2
61	exit $((1 + $line % 254))
62}
63
64## check_return [message_on_failure]
65#
66# Check the return value of the previous command or script line. If the
67# value of '$?' is not 0, then call bail() with an appropriate message.
68#
69function check_return() {
70	local err=$?
71	local msg=$1
72	local line=$2
73	if [ -z "$line" ]; then
74		line=${BASH_LINENO[0]}
75	fi
76	echo "CHECK: $msg"
77	if [ $err -ne 0 ]; then
78		bail "e=$err: $msg" $line
79	fi
80
81	return 0
82}
83
84## expect_failure [message_on_success]
85#
86# Check the return value of the previous command or script line. If the
87# value of '$?' is 0 (success), then call bail() with a message saying
88# that we expected this previous command/line to fail.
89#
90function expect_failure() {
91	local err=$?
92	local msg=$1
93	local line=$2
94	if [ -z "$line" ]; then
95		line=${BASH_LINENO[0]}
96	fi
97	if [ $err -eq 0 ]; then
98		bail "found success, expected failure: $msg" $line
99	fi
100
101	echo "EXPECT: failure: $msg"
102	return 0
103}
104
105## test_num [debug_info] [number]
106#
107# Check that a variable value is a number, bail() on error.
108#
109function test_num() {
110	local type=$1
111	local num=$2
112	local line=$3
113	if [ -z "$line" ]; then
114		line=${BASH_LINENO[0]}
115	fi
116	if [ -z "$num" ]; then
117		bail "invalid (NULL) $type" $line
118	fi
119	[ "$num" -eq "$num" ] 2>/dev/null
120	if [ $? -ne 0 ]; then
121		bail "invalid $type: $num" $line
122	fi
123
124	return 0
125}
126
127## global variables used to return values to callers
128_ID=-1
129_TYPE="invalid"
130_LOGIN=""
131_UID=-1
132_GID=-1
133_NGROUPS=-1
134_GROUPS=""
135
136## get_persona_info {persona_id} {persona_login}
137#
138# Lookup persona info for the given ID/login. At least one of the ID/login
139# parameters must be valid
140function get_persona_info() {
141	local pna_id=${1:-1}
142	local pna_login=${2:- }
143	local line=$3
144	if [ -z "$line" ]; then
145		line=${BASH_LINENO[0]}
146	fi
147
148	local largs="-u ${pna_id}"
149	if [ "${pna_login}" != " " ]; then
150		largs+=" -l ${pna_login}"
151	fi
152
153	_ID=-1
154	_TYPE=-1
155	_LOGIN=""
156	_UID=-1
157	_GID=-1
158	_NGROUPS=-1
159	_GROUPS=()
160
161	local file="${TMPDIR}/plookup"
162
163	${PERSONA_MGR} lookup ${largs} > "${file}"
164	check_return "persona lookup of: ${largs}" $line
165
166	_ID=$(cat "${file}" | grep "+id: " | head -1 | sed 's/.*+id:[ ]*\([0-9][0-9]*\).*/\1/')
167	test_num "Persona ID lookup:${largs}" "$_ID"
168
169	local type=$(cat "${file}" | grep "+type: " | head -1 | sed 's/.*+type:[ ]*\([0-9][0-9]*\).*/\1/')
170	test_num "+type lookup:${largs}" "$type"
171	##
172	## NOTE: keep in sync with bsd/sys/persona.h types!
173	##
174	if [ $type -eq 1 ]; then
175		_TYPE=guest
176	elif [ $type -eq 2 ]; then
177		_TYPE=managed
178	elif [ $type -eq 3 ]; then
179		_TYPE=priv
180	elif [ $type -eq 4 ]; then
181		_TYPE=system
182	else
183		_TYPE=invalid
184	fi
185
186	_LOGIN=$(cat "${file}" | grep "+login: " | head -1 | sed 's/.*+login:[ ]*"\([^"]*\)".*/\1/')
187	if [ -z "$_LOGIN" ]; then
188		bail "invalid login for pna_id:$_ID: '$_LOGIN'" $line
189	fi
190
191	# these are always the same
192	_UID=$_ID
193
194	_GID=$(cat "${file}" | grep "+gid: " | head -1 | sed 's/.*+gid:[ ]*\([0-9][0-9]*\).*/\1/')
195	test_num "GID lookup:${largs}" "$_GID"
196
197	_NGROUPS=$(cat "${file}" | grep "ngroups: " | head -1 | sed 's/.*ngroups:[ ]*\([0-9][0-9]*\)[ ][ ]*{.*}.*/\1/')
198	test_num "NGROUPS lookup:${largs}" "$_NGROUPS"
199
200	_GROUPS=( $(cat "${file}" | grep "ngroups: " | head -1 | sed 's/.*ngroups:[ ]*[0-9][0-9]*[ ][ ]*{[ ]*\([^ ].*\)[ ][ ]*}.*/\1/') )
201	if [ $_NGROUPS -gt 0 ]; then
202		if [ -z "${_GROUPS}" ]; then
203			bail "lookup:${largs}: missing $_NGROUPS groups" $line
204		fi
205		if [ ${#_GROUPS[@]} -ne $_NGROUPS ]; then
206			bail "lookup:${largs} wrong number of groups ${#_GROUPS[@]} != $_NGROUPS" $line
207		fi
208	fi
209}
210
211## validate_child_info [output_file] [persona_id] {uid} {gid} {groups}
212#
213# Parse the output of the 'persona_spawn' command and validate that
214# the new child process is in the correct persona with the correct
215# process attributes.
216#
217function validate_child_info() {
218	local file=$1
219	local pna_id=$2
220	local uid=${3:--1}
221	local gid=${4:--1}
222	local groups=${5:- }
223	local line=$6
224	if [ -z "$line" ]; then
225		line=${BASH_LINENO[0]}
226	fi
227	local l=( )
228
229	# get the child's PID
230	local cpid="$(cat "$file" | grep "Child: PID:" | sed 's/.*Child: PID:\([0-9][0-9]*\).*/\1/')"
231	test_num "Child PID" "$cpid" $line
232
233	# validate the child's persona
234	l=( $(cat "$file" | grep "Child: Persona:" | sed 's/.*Child: Persona: \([0-9][0-9]*\) (err:\([0-9][0-9]*\))/\1 \2/') )
235	if [ ${#l[@]} -ne 2 ]; then
236		bail "Invalid Child[$cpid] Persona line" $line
237	fi
238	test_num "Child Persona ID" "${l[0]}" $line
239	test_num "kpersona_info retval" "${l[1]}" $line
240
241	if [ ${l[0]} -ne $pna_id ]; then
242		bail "Child[$cpid] persona:${l[0]} != specified persona:$pna_id" $line
243	fi
244
245	# Validate the UID/GID
246	l=( $(cat "$file" | grep "Child: UID:" | sed 's/.*UID:\([0-9][0-9]*\), GID:\([0-9][0-9]*\).*/\1 \2/') )
247	if [ ${#l[@]} -ne 2 ]; then
248		bail "Invalid Child[$cpid] UID/GID output" $line
249	fi
250	if [ $uid -ge 0 ]; then
251		if [ $uid -ne ${l[0]} ]; then
252			bail "Child[$cpid] UID:${l[0]} != specified UID:$uid" $line
253		fi
254	fi
255	if [ $gid -ge 0 ]; then
256		if [ $gid -ne ${l[1]} ]; then
257			bail "Child[$cpid] GID:${l[1]} != specified GID:$gid" $line
258		fi
259	fi
260
261	# TODO: validate / verify groups?
262
263	return 0
264}
265
266
267## spawn_child [persona_id] {uid} {gid} {group_spec}
268#
269# Create a child process that is spawn'd into the persona given by
270# the first argument (pna_id). The new process can have its UID, GID,
271# and group membership properties overridden.
272#
273function spawn_child() {
274	local pna_id=$1
275	local uid=${2:--1}
276	local gid=${3:--1}
277	local groups=${4:- }
278	local line=$5
279	if [ -z "$line" ]; then
280		line=${BASH_LINENO[0]}
281	fi
282
283	local file="child.${pna_id}"
284	local spawn_args="-I $pna_id"
285	if [ $uid -ge 0 ]; then
286		spawn_args+=" -u $uid"
287		file+=".u$uid"
288	fi
289	if [ $gid -ge 0 ]; then
290		spawn_args+=" -g $gid"
291		file+=".g$gid"
292	fi
293	if [ "$groups" != " " ]; then
294		spawn_args+=" -G $groups"
295		file+="._groups"
296	fi
297
298	echo "SPAWN: $file"
299	${PERSONA_SPAWN} -v $spawn_args ${PERSONA_SPAWN} child -v -E > "${TMPDIR}/$file"
300	check_return "child info: $file" $line
301
302	# Grab the specified persona's info so we can
303	# verify the child's info against it.
304	# This function puts data into global variables, e.g. _ID, _GID, etc.
305	get_persona_info ${pna_id} " " $line
306	if [ $uid -lt 0 ]; then
307		uid=$_UID
308	fi
309	if [ $gid -lt 0 ]; then
310		gid=$_GID
311	fi
312	if [ "$groups" == " " ]; then
313		# convert a bash array into a comma-separated list for validation
314		local _g="${_GROUPS[@]}"
315		groups="${_g// /,}"
316	fi
317
318	validate_child_info "${TMPDIR}/$file" "$pna_id" "$uid" "$gid" "$groups" $line
319
320	## validate that the first child spawned into a persona *cannot* spawn
321	## into a different persona...
322	if [ $uid -eq 0 ]; then
323		${PERSONA_SPAWN} -v $spawn_args ${PERSONA_SPAWN_UNENTITLED} child -v -E -R spawn -v $spawn_args -I ${TEST_DEFAULT_PERSONA} /bin/echo "This is running in the system persona"
324		expect_failure "Spawned child that re-execs into non-default persona" $line
325	fi
326	return 0
327}
328
329## get_created_id [output_file]
330#
331# Parse the output of the 'persona_mgr' command to determine the ID
332# of the newly created persona.
333#
334function get_created_id() {
335	local file=$1
336	local o=$(cat "$file" | grep "Created persona" | sed 's/.*Created persona \([0-9][0-9]*\):/\1/')
337	echo $o
338	return 0
339}
340
341## create_persona [login_name] [persona_type] {persona_id} {gid} {group_spec}
342#
343# Create a new persona with given parameters.
344#
345# Returns: the newly created persona ID via the global variable, $_ID
346#
347function create_persona() {
348	local name=${1}
349	local type=${2}
350	local pna_id=${3:--1}
351	local gid=${4:--1}
352	local groups=${5:- }
353	local line=$6
354	if [ -z "$line" ]; then
355		line=${BASH_LINENO[0]}
356	fi
357
358	if [ -z "$name" -o -z "$type" ]; then
359		bail "Invalid arguments to create_persona '$name' '$type'" $line
360	fi
361
362	local file="persona.at${line}"
363	# persona ID of '-1' is auto-assigned
364	local spawn_args="-v -l $name -i $pna_id"
365	if [ $pna_id -eq -1 ]; then
366		file+=".auto"
367	else
368		file+=".${pna_id}"
369	fi
370
371	spawn_args+=" -t $type"
372	file+=".$type"
373
374	if [ $gid -ge 0 ]; then
375		spawn_args+=" -g $gid"
376		file+=".g$gid"
377	fi
378	if [ "$groups" != " " ]; then
379		spawn_args+=" -G $groups"
380		file+="._groups"
381	fi
382
383	echo "CREATE: $file"
384	${PERSONA_MGR} create ${spawn_args} > "${TMPDIR}/${file}"
385	check_return "persona creation: ${file}" $line
386	# test output should include persona creation output for later debugging
387	cat "${TMPDIR}/${file}"
388
389	# validate the output of the persona_mgr tool (what we think we created)
390	_ID=`get_created_id "${TMPDIR}/${file}"`
391	test_num "persona_id for $file" "$_ID" $line
392	if [ ${pna_id} -gt 0 ]; then
393		if [ $_ID -ne ${pna_id} ]; then
394			bail "Created persona doesn't have expected ID $_ID != ${pna_id}" $line
395		fi
396	fi
397
398	# validate the entire persona information (what a kpersona_lookup says we created)
399	# This function puts data into global variables, e.g. _ID, _LOGIN, _GID, etc.
400	echo "VALIDATE: ${file}"
401	get_persona_info ${pna_id} "$name" $line
402	if [ "$name" != "$_LOGIN" ]; then
403		bail "${file}: unexpected login '$_LOGIN' != '$name'" $line
404	fi
405	if [ "$type" != "$_TYPE" ]; then
406		bail "${file}: unexpected type '$_TYPE' != '$type'" $line
407	fi
408	if [ ${pna_id} -gt 0 ]; then
409		if [ ${pna_id} -ne $_ID ]; then
410			bail "${file}: unexpected ID '$_ID' != '${pna_id}'" $line
411		fi
412	fi
413	if [ $gid -ge 0 ]; then
414		if [ $gid -ne $_GID ]; then
415			bail "${file}: unexpected GID '$_GID' != '$gid'" $line
416		fi
417	fi
418	if [ "$groups" != " " ]; then
419		local _g="${_GROUPS[@]}"
420		if [ "${_g// /,}" != "$groups" ]; then
421			bail "${file}: unexpected groups '${_g// /,}' != '$groups'" $line
422		fi
423	fi
424
425	return 0
426}
427
428## destroy_persona [persona_id]
429#
430# Destroy the given persona.
431#
432function destroy_persona() {
433	local pna_id=$1
434	local line=$2
435	if [ -z "$line" ]; then
436		line=${BASH_LINENO[0]}
437	fi
438
439	echo "DESTROY: ${pna_id}"
440	${PERSONA_MGR} destroy -v -i ${pna_id}
441	check_return "destruction of ${pna_id}" $line
442}
443
444#
445#
446# Begin Tests!
447#
448#
449echo "Running persona tests [$LINENO] ($TMPDIR)"
450
451##
452## Test Group 0: basic creation + spawn tests
453##
454
455create_persona "test_default_persona" "guest" 99999
456TEST_DEFAULT_PERSONA=$_ID
457
458# default group, specific ID
459create_persona "test0_1" "guest" 10001
460P0ID=$_ID
461
462spawn_child $P0ID
463spawn_child $P0ID 1100
464spawn_child $P0ID 0
465spawn_child $P0ID -1 1101
466spawn_child $P0ID 1100 1101
467spawn_child $P0ID 1100 1101 1000,2000,3000
468spawn_child $P0ID 1100 -1 1000,2000,3000
469spawn_child $P0ID -1 -1 1000,2000,3000
470destroy_persona $P0ID
471
472# specific ID, non-default group
473create_persona "test0_2" "guest" 10002 2000
474P0ID=$_ID
475spawn_child $P0ID
476spawn_child $P0ID 1100
477spawn_child $P0ID 0
478spawn_child $P0ID -1 1101
479spawn_child $P0ID 1100 1101
480spawn_child $P0ID 1100 1101 1000,2000,3000
481spawn_child $P0ID 1100 -1 1000,2000,3000
482spawn_child $P0ID -1 -1 1000,2000,3000
483destroy_persona $P0ID
484
485# non-default set of groups
486create_persona "test0_3" "guest" 10003 2000 2000,3000,4000
487P0ID=$_ID
488spawn_child $P0ID
489spawn_child $P0ID 1100
490spawn_child $P0ID 0
491spawn_child $P0ID -1 1101
492spawn_child $P0ID 1100 1101
493spawn_child $P0ID 1100 1101 1111,2222,3333
494spawn_child $P0ID 1100 -1 1111,2222,3333
495spawn_child $P0ID -1 -1 1111,2222,3333
496destroy_persona $P0ID
497
498
499##
500## Test Group 1: persona creation / re-creation
501##
502
503# Create 3 personas with auto-assigned IDs
504create_persona "test1_1" "guest"
505P1ID=$_ID
506create_persona "test1_2" "managed"
507P2ID=$_ID
508create_persona "test1_3" "priv"
509P3ID=$_ID
510create_persona "test1_4" "guest"
511P4ID=$_ID
512
513D1=$(($P2ID - $P1ID))
514D2=$(($P3ID - $P2ID))
515D3=$(($P4ID - $P3ID))
516if [ $D1 -ne $D2 -o $D1 -ne $D3 -o $D2 -ne $D3 ]; then
517	bail "inconsistent automatic Persona ID increment: $D1,$D2,$D3 ($P1ID,$P2ID,$P3ID,$P4ID)"
518fi
519
520# make sure we can't re-allocate the same name / ID
521${PERSONA_MGR} create -v -l test1_1 -t guest -i -1 && expect_failure "re-create same name:test1_1 type:guest"
522${PERSONA_MGR} create -v -l test1_1 -t managed -i -1 && expect_failure "re-create same name:test1_1 type:managed"
523${PERSONA_MGR} create -v -l test1_1_new -t managed -i $P1ID && expect_failure "re-create $P1ID with new name:test1_1_new type:managed"
524
525##
526## Test Group 2: auto-assigned ID tricks
527##
528
529# Notice the difference in IDs, then try to create a persona by
530# specifying an ID that will match the next auto-assigned ID
531# (should succeed)
532P5ID_REQ=$(($P4ID + $D2))
533create_persona "test2_1" "guest" ${P5ID_REQ}
534P5ID=$_ID
535if [ ! $P5ID -eq ${P5ID_REQ} ]; then
536	bail "test2_1: ${P5ID_REQ} != $P5ID"
537fi
538
539# try to create a persona with auto-assigned ID
540# (resulting persona should have ID != P5ID)
541create_persona "test2_2" "guest"
542P6ID=$_ID
543if [ $P6ID -eq $P5ID ]; then
544	bail "created duplicate persona IDs: $P6ID == $P5ID"
545fi
546
547##
548## Test Group 3: persona destruction
549##
550
551destroy_persona $P1ID
552destroy_persona $P2ID
553destroy_persona $P3ID
554destroy_persona $P4ID
555destroy_persona $P5ID
556destroy_persona $P6ID
557
558# try to re-destroy the personas
559# (should fail)
560${PERSONA_MGR} destroy -v -i $P1ID && expect_failure "re-destroy (1/2) $P1ID"
561${PERSONA_MGR} destroy -v -i $P1ID && expect_failure "re-destroy (2/2) $P1ID"
562${PERSONA_MGR} destroy -v -i $P2ID && expect_failure "re-destroy $P2ID"
563${PERSONA_MGR} destroy -v -i $P3ID && expect_failure "re-destroy $P3ID"
564${PERSONA_MGR} destroy -v -i $P4ID && expect_failure "re-destroy $P4ID"
565${PERSONA_MGR} destroy -v -i $P5ID && expect_failure "re-destroy $P5ID"
566${PERSONA_MGR} destroy -v -i $P6ID && expect_failure "re-destroy $P6ID"
567
568destroy_persona ${TEST_DEFAULT_PERSONA}
569
570# cleanup
571rm -rf "${TMPDIR}"
572
573echo ""
574echo "${0##/}: SUCCESS"
575exit 0
576