]> git.saurik.com Git - apple/xnu.git/blob - tools/tests/personas/persona_test_run_src.sh
xnu-7195.50.7.100.1.tar.gz
[apple/xnu.git] / tools / tests / personas / persona_test_run_src.sh
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
16 PERSONA_MGR="${PWD}/persona_mgr"
17 PERSONA_SPAWN="${PWD}/persona_spawn"
18 PERSONA_SPAWN_UNENTITLED="${PWD}/persona_spawn_unentitled"
19
20 TEST_DEFAULT_PERSONA=0
21
22 if [ ! -d "$TMPDIR" ]; then
23 echo "Couldn't find temp directory '$TMPDIR': check permissions/environment?"
24 exit 255
25 fi
26
27 if [ ! -e "${PERSONA_MGR}" ] || [ ! -x "${PERSONA_MGR}" ]; then
28 echo "Can't find '${PERSONA_MGR}': skipping test"
29 exit 0
30 fi
31 if [ ! -e "${PERSONA_SPAWN}" ] || [ ! -x "${PERSONA_SPAWN}" ]; then
32 echo "Can't find '${PERSONA_SPAWN}': skipping test"
33 exit 0
34 fi
35
36 function 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 }
45 check_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 #
54 function 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 #
69 function 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 #
90 function 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 #
109 function 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
140 function 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 #
217 function 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 #
273 function 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 #
334 function 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 #
347 function 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 #
432 function 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 #
449 echo "Running persona tests [$LINENO] ($TMPDIR)"
450
451 ##
452 ## Test Group 0: basic creation + spawn tests
453 ##
454
455 create_persona "test_default_persona" "guest" 9999
456 TEST_DEFAULT_PERSONA=$_ID
457
458 # default group, specific ID
459 create_persona "test0_1" "guest" 1001
460 P0ID=$_ID
461
462 spawn_child $P0ID
463 spawn_child $P0ID 1100
464 spawn_child $P0ID 0
465 spawn_child $P0ID -1 1101
466 spawn_child $P0ID 1100 1101
467 spawn_child $P0ID 1100 1101 1000,2000,3000
468 spawn_child $P0ID 1100 -1 1000,2000,3000
469 spawn_child $P0ID -1 -1 1000,2000,3000
470 destroy_persona $P0ID
471
472 # specific ID, non-default group
473 create_persona "test0_2" "guest" 1002 2000
474 P0ID=$_ID
475 spawn_child $P0ID
476 spawn_child $P0ID 1100
477 spawn_child $P0ID 0
478 spawn_child $P0ID -1 1101
479 spawn_child $P0ID 1100 1101
480 spawn_child $P0ID 1100 1101 1000,2000,3000
481 spawn_child $P0ID 1100 -1 1000,2000,3000
482 spawn_child $P0ID -1 -1 1000,2000,3000
483 destroy_persona $P0ID
484
485 # non-default set of groups
486 create_persona "test0_3" "guest" 1003 2000 2000,3000,4000
487 P0ID=$_ID
488 spawn_child $P0ID
489 spawn_child $P0ID 1100
490 spawn_child $P0ID 0
491 spawn_child $P0ID -1 1101
492 spawn_child $P0ID 1100 1101
493 spawn_child $P0ID 1100 1101 1111,2222,3333
494 spawn_child $P0ID 1100 -1 1111,2222,3333
495 spawn_child $P0ID -1 -1 1111,2222,3333
496 destroy_persona $P0ID
497
498
499 ##
500 ## Test Group 1: persona creation / re-creation
501 ##
502
503 # Create 3 personas with auto-assigned IDs
504 create_persona "test1_1" "guest"
505 P1ID=$_ID
506 create_persona "test1_2" "managed"
507 P2ID=$_ID
508 create_persona "test1_3" "priv"
509 P3ID=$_ID
510 create_persona "test1_4" "guest"
511 P4ID=$_ID
512
513 D1=$(($P2ID - $P1ID))
514 D2=$(($P3ID - $P2ID))
515 D3=$(($P4ID - $P3ID))
516 if [ $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)"
518 fi
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)
532 P5ID_REQ=$(($P4ID + $D2))
533 create_persona "test2_1" "guest" ${P5ID_REQ}
534 P5ID=$_ID
535 if [ ! $P5ID -eq ${P5ID_REQ} ]; then
536 bail "test2_1: ${P5ID_REQ} != $P5ID"
537 fi
538
539 # try to create a persona with auto-assigned ID
540 # (resulting persona should have ID != P5ID)
541 create_persona "test2_2" "guest"
542 P6ID=$_ID
543 if [ $P6ID -eq $P5ID ]; then
544 bail "created duplicate persona IDs: $P6ID == $P5ID"
545 fi
546
547 ##
548 ## Test Group 3: persona destruction
549 ##
550
551 destroy_persona $P1ID
552 destroy_persona $P2ID
553 destroy_persona $P3ID
554 destroy_persona $P4ID
555 destroy_persona $P5ID
556 destroy_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
568 destroy_persona ${TEST_DEFAULT_PERSONA}
569
570 # cleanup
571 rm -rf "${TMPDIR}"
572
573 echo ""
574 echo "${0##/}: SUCCESS"
575 exit 0