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