#! /bin/bash # # Divided into four section: # ## USAGE ## Helper Variables ## Helper Functions ## MAINLINE ## ## USAGE ## USAGE="USAGE: `basename ${0}` [--help] [--serial ] [options] adb remount tests -c --color Dress output with highlighting colors -h --help This help -D --no-wait-screen Do not wait for display screen to settle -t --print-time Report the test duration -s --serial Specify device (must if multiple are present)" if [ -n "`which timeout`" ]; then USAGE="${USAGE} -a --wait-adb adb wait timeout -f --wait-fastboot fastboot wait timeout" fi USAGE="${USAGE} Conditions: - Must be a userdebug build. - Must be in adb mode. - Also tests overlayfs - Kernel must have overlayfs enabled and patched to support override_creds. - Must have either erofs, squashfs, ext4-dedupe or full partitions. - Minimum expectation system and vender are overlayfs covered partitions. " ## ## Helper Variables ## EMPTY="" SPACE=" " # Line up wrap to [ XXXXXXX ] messages. INDENT=" " # A _real_ embedded tab character TAB="`echo | tr '\n' '\t'`" # A _real_ embedded escape character ESCAPE="`echo | tr '\n' '\033'`" # A _real_ embedded carriage return character CR="`echo | tr '\n' '\r'`" RED= GREEN= YELLOW= BLUE= NORMAL= color=false # Assume support color if stdout is terminal. [ -t 1 ] && color=true print_time=true start_time=`date +%s` ACTIVE_SLOT= OVERLAYFS_BACKING="cache mnt/scratch" ADB_WAIT=4m FASTBOOT_WAIT=2m screen_wait=true ## ## Helper Functions ## [ "USAGE: LOG [RUN|OK|PASSED|WARNING|ERROR|FAILED|INFO] [message]..." ] LOG() { if ${print_time}; then echo -n "$(date '+%m-%d %T') " fi >&2 case "${1}" in R*) shift echo "${GREEN}[ RUN ]${NORMAL}" "${@}" ;; OK) shift echo "${GREEN}[ OK ]${NORMAL}" "${@}" ;; P*) shift echo "${GREEN}[ PASSED ]${NORMAL}" "${@}" ;; W*) shift echo "${YELLOW}[ WARNING ]${NORMAL}" "${@}" ;; E*) shift echo "${RED}[ ERROR ]${NORMAL}" "${@}" ;; F*) shift echo "${RED}[ FAILED ]${NORMAL}" "${@}" ;; I*) shift echo "${BLUE}[ INFO ]${NORMAL}" "${@}" ;; *) echo "${BLUE}[ INFO ]${NORMAL}" "${@}" ;; esac >&2 } [ "USAGE: inFastboot Returns: true if device is in fastboot mode" ] inFastboot() { fastboot devices | if [ -n "${ANDROID_SERIAL}" ]; then grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null else wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null fi } [ "USAGE: inAdb Returns: true if device is in adb mode" ] inAdb() { adb devices | grep -v -e 'List of devices attached' -e '^$' -e "[${SPACE}${TAB}]recovery\$" | if [ -n "${ANDROID_SERIAL}" ]; then grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null else wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null fi } [ "USAGE: inRecovery Returns: true if device is in recovery mode" ] inRecovery() { local list="`adb devices | grep -v -e 'List of devices attached' -e '^$'`" if [ -n "${ANDROID_SERIAL}" ]; then echo "${list}" | grep "^${ANDROID_SERIAL}[${SPACE}${TAB}][${SPACE}${TAB}]*recovery\$" >/dev/null return ${?} fi if echo "${list}" | wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null; then echo "${list}" | grep "[${SPACE}${TAB}]recovery\$" >/dev/null return ${?} fi false } [ "USAGE: adb_sh /dev/stdout 2>/dev/stderr Returns: true if the command succeeded" ] adb_sh() { local args= for i in "${@}"; do [ -z "${args}" ] || args="${args} " if [ X"${i}" != X"${i#\'}" ]; then args="${args}${i}" elif [ X"${i}" != X"${i#* }" ]; then args="${args}'${i}'" elif [ X"${i}" != X"${i#*${TAB}}" ]; then args="${args}'${i}'" else args="${args}${i}" fi done adb shell "${args}" } [ "USAGE: adb_date >/dev/stdout Returns: report device epoch time (suitable for logcat -t)" ] adb_date() { adb_sh date +%s.%N /dev/stdout Returns: the logcat output" ] adb_logcat() { LOG INFO "logcat ${*}" adb logcat "${@}" /dev/stderr Returns: worrisome avc violations" ] avc_check() { if ! ${overlayfs_needed:-false}; then return fi local L=`adb_logcat -b all -v brief -d \ -e 'context=u:object_r:unlabeled:s0' 2>/dev/null | sed -n 's/.*avc: //p' | sort -u` if [ -z "${L}" ]; then return fi LOG WARNING "unlabeled sepolicy violations:" echo "${L}" | sed "s/^/${INDENT}/" >&2 } [ "USAGE: get_property Returns the property value" ] get_property() { adb_sh getprop ${1} /dev/stdout 2>/dev/stderr Returns: true if the command running as root succeeded" ] adb_su() { adb_sh su root "${@}" } [ "USAGE: adb_cat >stdout Returns: content of file to stdout with carriage returns skipped, true if the file exists" ] adb_cat() { local OUTPUT="`adb_sh cat ${1} &1`" local ret=${?} echo "${OUTPUT}" | tr -d '\r' return ${ret} } [ "USAGE: adb_test Returns: exit status of the test expression" ] adb_test() { adb_sh test "${@}" |s|m|h|d] human readable output whole seconds, whole minutes or mm:ss" ] format_duration() { if [ -z "${1}" ]; then echo unknown return fi local duration="${1}" if [ X"${duration}" != X"${duration%s}" ]; then duration=${duration%s} elif [ X"${duration}" != X"${duration%m}" ]; then duration=$(( ${duration%m} * 60 )) elif [ X"${duration}" != X"${duration%h}" ]; then duration=$(( ${duration%h} * 3600 )) elif [ X"${duration}" != X"${duration%d}" ]; then duration=$(( ${duration%d} * 86400 )) fi local seconds=$(( ${duration} % 60 )) local minutes=$(( ( ${duration} / 60 ) % 60 )) local hours=$(( ${duration} / 3600 )) if [ 0 -eq ${minutes} -a 0 -eq ${hours} ]; then if [ 1 -eq ${duration} ]; then echo 1 second return fi echo ${duration} seconds return elif [ 60 -eq ${duration} ]; then echo 1 minute return elif [ 0 -eq ${seconds} -a 0 -eq ${hours} ]; then echo ${minutes} minutes return fi if [ 0 -eq ${hours} ]; then echo ${minutes}:$(( ${seconds} / 10 ))$(( ${seconds} % 10 )) return fi echo ${hours}:$(( ${minutes} / 10 ))$(( ${minutes} % 10 )):$(( ${seconds} / 10 ))$(( ${seconds} % 10)) } [ "USAGE: USB_DEVICE=\`usb_devnum [--next]\` USB_DEVICE contains cache. Update if system changes. Returns: the devnum for the USB_SERIAL device" ] usb_devnum() { if [ -n "${USB_SERIAL}" ]; then local usb_device=`cat ${USB_SERIAL%/serial}/devnum 2>/dev/null | tr -d ' \t\r\n'` if [ -n "${usb_device}" ]; then USB_DEVICE=dev${usb_device} elif [ -n "${USB_DEVICE}" -a "${1}" ]; then USB_DEVICE=dev$(( ${USB_DEVICE#dev} + 1 )) fi echo "${USB_DEVICE}" fi } [ "USAGE: adb_wait [timeout] Returns: waits until the device has returned for adb or optional timeout" ] adb_wait() { local start=`date +%s` local duration= local ret if [ -n "${1}" -a -n "`which timeout`" ]; then USB_DEVICE=`usb_devnum --next` duration=`format_duration ${1}` echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" >&2 timeout --preserve-status --signal=KILL ${1} adb wait-for-device 2>/dev/null ret=${?} echo -n " ${CR}" >&2 else adb wait-for-device ret=${?} fi USB_DEVICE=`usb_devnum` if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then local active_slot=`get_active_slot` if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" fi fi local end=`date +%s` local diff_time=$(( ${end} - ${start} )) local _print_time=${print_time} if [ ${diff_time} -lt 15 ]; then _print_time=false fi diff_time=`format_duration ${diff_time}` if [ "${diff_time}" = "${duration}" ]; then _print_time=false fi local reason= if inAdb; then reason=`get_property ro.boot.bootreason` fi case ${reason} in reboot*) reason= ;; ${EMPTY}) ;; *) reason=" for boot reason ${reason}" ;; esac if ${_print_time} || [ -n "${reason}" ]; then LOG INFO "adb wait duration ${diff_time}${reason}" fi return ${ret} } [ "USAGE: adb_user > /dev/stdout Returns: the adb daemon user" ] adb_user() { adb_sh echo '${USER}' stdout 2> stderr Assumes referenced right after adb_wait or fastboot_wait failued. If wait failed, check if device is in adb, recovery or fastboot mode and report status strings like \"(USB stack borken?)\", \"(In fastboot mode)\", \"(In recovery mode)\" or \"(in adb mode)\". Additional diagnostics may be provided to the stderr output. Returns: USB status string" ] usb_status() { if inFastboot; then echo "(In fastboot mode)" elif inRecovery; then echo "(In recovery mode)" elif inAdb; then echo "(In adb mode `adb_user`)" else echo "(USB stack borken for ${USB_ADDRESS})" if [ -n "`which usb_devnum`" ]; then USB_DEVICE=`usb_devnum` if [ -n "`which lsusb`" ]; then if [ -n "${USB_DEVICE}" ]; then echo "# lsusb -v -s ${USB_DEVICE#dev}" local D=`lsusb -v -s ${USB_DEVICE#dev} 2>&1` if [ -n "${D}" ]; then echo "${D}" else lsusb -v fi else echo "# lsusb -v (expected device missing)" lsusb -v fi fi fi >&2 fi } [ "USAGE: fastboot_wait [timeout] Returns: waits until the device has returned for fastboot or optional timeout" ] fastboot_wait() { local ret # fastboot has no wait-for-device, but it does an automatic # wait and requires (even a nonsensical) command to do so. if [ -n "${1}" -a -n "`which timeout`" ]; then USB_DEVICE=`usb_devnum --next` echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" timeout --preserve-status --signal=KILL ${1} fastboot wait-for-device >/dev/null 2>/dev/null ret=${?} echo -n " ${CR}" ( exit ${ret} ) else fastboot wait-for-device >/dev/null 2>/dev/null fi || inFastboot ret=${?} USB_DEVICE=`usb_devnum` if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then local active_slot=`get_active_slot` if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" fi fi return ${ret} } [ "USAGE: recovery_wait [timeout] Returns: waits until the device has returned for recovery or optional timeout" ] recovery_wait() { local ret if [ -n "${1}" -a -n "`which timeout`" ]; then USB_DEVICE=`usb_devnum --next` echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" timeout --preserve-status --signal=KILL ${1} adb wait-for-recovery 2>/dev/null ret=${?} echo -n " ${CR}" else adb wait-for-recovery ret=${?} fi USB_DEVICE=`usb_devnum` if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then local active_slot=`get_active_slot` if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" fi fi return ${ret} } [ "any_wait [timeout] Returns: waits until a device has returned or optional timeout" ] any_wait() { ( adb_wait ${1} & adb_pid=${!} fastboot_wait ${1} & fastboot_pid=${!} recovery_wait ${1} & recovery_pid=${!} wait -n kill "${adb_pid}" "${fastboot_pid}" "${recovery_pid}" ) >/dev/null 2>/dev/null inFastboot || inAdb || inRecovery } wait_for_screen_timeout=900 [ "USAGE: wait_for_screen [-n] [TIMEOUT] -n - echo newline at exit TIMEOUT - default `format_duration ${wait_for_screen_timeout}`" ] wait_for_screen() { if ! ${screen_wait}; then adb_wait return fi exit_function=true if [ X"-n" = X"${1}" ]; then exit_function=echo shift fi timeout=${wait_for_screen_timeout} if [ ${#} -gt 0 ]; then timeout=${1} shift fi counter=0 while true; do if inFastboot; then fastboot reboot elif inAdb; then if [ 0 != ${counter} ]; then adb_wait fi if [ "1" = "`get_property sys.boot_completed`" ]; then sleep 1 break fi fi counter=$(( ${counter} + 1 )) if [ ${counter} -gt ${timeout} ]; then ${exit_function} LOG ERROR "wait_for_screen() timed out ($(format_duration ${timeout}))" return 1 fi sleep 1 done ${exit_function} } [ "USAGE: adb_root NB: This can be flakey on devices due to USB state Returns: true if device in root state" ] adb_root() { [ root != "`adb_user`" ] || return 0 adb root >/dev/null /dev/null sleep 2 adb_wait ${ADB_WAIT} && [ root = "`adb_user`" ] } [ "USAGE: adb_unroot NB: This can be flakey on devices due to USB state Returns: true if device in un root state" ] adb_unroot() { [ root = "`adb_user`" ] || return 0 adb unroot >/dev/null /dev/null sleep 2 adb_wait ${ADB_WAIT} && [ root != "`adb_user`" ] } [ "USAGE: fastboot_getvar var expected >/dev/stderr Returns: true if var output matches expected" ] fastboot_getvar() { local O=`fastboot getvar ${1} 2>&1` local ret=${?} O="${O#< waiting for * >?}" O="${O%%?Finished. Total time: *}" if [ 0 -ne ${ret} ]; then echo ${O} >&2 false return fi if [ "${O}" != "${O#*FAILED}" ]; then O="${1}: " fi if [ -n "${2}" -a "${1}: ${2}" != "${O}" ]; then echo "${2} != ${O}" false return fi >&2 echo ${O} >&2 } [ "USAGE: get_active_slot >/dev/stdout Returns: with a or b string reporting active slot" ] get_active_slot() { if inAdb || inRecovery; then get_property ro.boot.slot_suffix | tr -d _ elif inFastboot; then fastboot_getvar current-slot 2>&1 | sed -n 's/current-slot: //p' else false fi } [ "USAGE: restore Do nothing: should be redefined when necessary. Returns: reverses configurations" ] restore() { true } [ "USAGE: test_duration >/dev/stderr Prints the duration of the test Returns: reports duration" ] test_duration() { if ${print_time}; then LOG INFO "end $(date)" [ -n "${start_time}" ] || return end_time=`date +%s` local diff_time=$(( ${end_time} - ${start_time} )) LOG INFO "duration $(format_duration ${diff_time})" fi } [ "USAGE: die [-d|-t ] [message] >/dev/stderr If -d, or -t argument is supplied, dump logcat. Returns: exit failure, report status" ] die() { if [ X"-d" = X"${1}" ]; then adb_logcat -b all -v nsec -d shift elif [ X"-t" = X"${1}" ]; then if [ -n "${2}" ]; then adb_logcat -b all -v nsec -t ${2} else adb_logcat -b all -v nsec -d fi shift 2 fi >&2 LOG FAILED "${@}" exit 1 } [ "USAGE: check_eq [--warning [message]] Exits if (regex) lval mismatches rval. Returns: true if lval matches rval" ] check_eq() { local lval="${1}" local rval="${2}" shift 2 if [[ "${rval}" =~ ^${lval}$ ]]; then return 0 fi local error=true local logt=ERROR if [ X"${1}" = X"--warning" ]; then shift 1 error=false logt=WARNING fi if [ $(( ${#lval} + ${#rval} )) -gt 40 ]; then LOG "${logt}" "expected \"${lval}\" ${INDENT}got \"${rval}\"" else LOG "${logt}" "expected \"${lval}\" got \"${rval}\"" fi ${error} && die "${*}" [ -n "${*}" ] && LOG "${logt}" "${*}" return 1 } [ "USAGE: check_ne [--warning [message]] Exits if (regex) lval matches rval. Returns: true if lval mismatches rval" ] check_ne() { local lval="${1}" local rval="${2}" shift 2 if ! [[ "${rval}" =~ ^${lval}$ ]]; then return 0 fi local error=true local logt=ERROR if [ X"${1}" = X"--warning" ]; then shift 1 error=false logt=WARNING fi LOG "${logt}" "unexpected \"${rval}\"" ${error} && die "${*}" [ -n "${*}" ] && LOG "${logt}" "${*}" return 1 } [ "USAGE: join_with Joins strings with delimiter" ] join_with() { if [ "${#}" -lt 2 ]; then echo return fi local delimiter="${1}" local result="${2}" shift 2 for element in "${@}"; do result+="${delimiter}${element}" done echo "${result}" } [ "USAGE: skip_administrative_mounts < /proc/mounts Filters out all administrative (eg: sysfs) mounts uninteresting to the test" ] skip_administrative_mounts() { local exclude_filesystems=( "overlay" "tmpfs" "none" "sysfs" "proc" "selinuxfs" "debugfs" "bpf" "binfmt_misc" "cg2_bpf" "pstore" "tracefs" "adb" "mtp" "ptp" "devpts" "ramdumpfs" "binder" "securityfs" "functionfs" "rootfs" "fuse" ) local exclude_devices=( "\/sys\/kernel\/debug" "\/data\/media" "\/dev\/block\/loop[0-9]*" "\/dev\/block\/vold\/[^ ]+" "${exclude_filesystems[@]}" ) local exclude_mount_points=( "\/cache" "\/mnt\/scratch" "\/mnt\/vendor\/persist" "\/persist" "\/metadata" "\/apex\/[^ ]+" ) awk '$1 !~ /^('"$(join_with "|" "${exclude_devices[@]}")"')$/ && $2 !~ /^('"$(join_with "|" "${exclude_mount_points[@]}")"')$/ && $3 !~ /^('"$(join_with "|" "${exclude_filesystems[@]}")"')$/' } [ "USAGE: surgically_wipe_overlayfs Surgically wipe any mounted overlayfs scratch files. Returns: true if wiped anything" ] surgically_wipe_overlayfs() { local wiped_anything=false for d in ${OVERLAYFS_BACKING}; do if adb_su test -d "/${d}/overlay" /dev/null 2>/dev/null ( echo "${df_header_line}" echo "${overlay_mounts}" ) >&2 if [ "${#}" -gt 0 ] && ! ( echo "${overlay_mounts}" | grep -qE " ${1}\$" ); then return 1 fi >/dev/null 2>/dev/null return 0 } ## ## MAINLINE ## HOSTOS=`uname` GETOPTS="--alternative --unquoted --longoptions help,serial:,colour,color,no-colour,no-color --longoptions wait-adb:,wait-fastboot: --longoptions wait-screen,wait-display --longoptions no-wait-screen,no-wait-display --longoptions gtest_print_time,print-time,no-print-time --" if [ "Darwin" = "${HOSTOS}" ]; then GETOPTS= USAGE="`echo \"${USAGE}\" | sed 's/--color/ /g 1s/--help/-h/ s/--help/ /g s/--no-wait-screen/ /g s/--print-time/ /g 1s/--serial/-s/ s/--serial/ /g s/--wait-adb/ /g s/--wait-fastboot/ /g'`" fi OPTIONS=`getopt ${GETOPTS} "?a:cCdDf:hs:tT" ${*}` || ( echo "${USAGE}" >&2 ; false ) || die "getopt failure" set -- ${OPTIONS} while [ ${#} -gt 0 ]; do case ${1} in -h | --help | -\?) echo "${USAGE}" >&2 exit 0 ;; -s | --serial) export ANDROID_SERIAL=${2} shift ;; -c | --color | --colour) color=true ;; -C | --no-color | --no-colour) color=false ;; -D | --no-wait-display | --no-wait-screen) screen_wait=false ;; -d | --wait-display | --wait-screen) screen_wait=true ;; -t | --print-time | --gtest_print_time) print_time=true ;; -T | --no-print-time) print_time=false ;; -a | --wait-adb) ADB_WAIT=${2} shift ;; -f | --wait-fastboot) FASTBOOT_WAIT=${2} shift ;; --) shift break ;; -*) echo "${USAGE}" >&2 die "${0}: error unknown option ${1}" ;; *) break ;; esac shift done if ${color}; then RED="${ESCAPE}[31m" GREEN="${ESCAPE}[32m" YELLOW="${ESCAPE}[33m" BLUE="${ESCAPE}[34m" NORMAL="${ESCAPE}[0m" fi TMPDIR= exit_handler() { [ -n "${TMPDIR}" ] && rm -rf "${TMPDIR}" local err=0 if ! restore; then LOG ERROR "restore failed" err=1 fi >&2 test_duration || true if [ "${err}" != 0 ]; then exit "${err}" fi } trap 'exit_handler' EXIT TMPDIR=$(mktemp -d) if ${print_time}; then LOG INFO "start $(date)" fi if [ -z "${ANDROID_SERIAL}" ]; then inAdb || die "no device or more than one device in adb mode" D=$(adb devices | awk '$2 == "device" { print $1; exit }') [ -n "${D}" ] || die "cannot get device serial" ANDROID_SERIAL="${D}" fi export ANDROID_SERIAL inFastboot && die "device in fastboot mode" inRecovery && die "device in recovery mode" if ! inAdb; then LOG WARNING "device not in adb mode" adb_wait ${ADB_WAIT} fi inAdb || die "specified device not in adb mode" [ "1" = "$(get_property ro.debuggable)" ] || die "device not a debug build" [ "orange" = "$(get_property ro.boot.verifiedbootstate)" ] || die "device not bootloader unlocked" ################################################################################ # Collect characteristics of the device and report. can_restore_verity=true if [ "2" != "$(get_property partition.system.verified)" ]; then LOG WARNING "device might not support verity" can_restore_verity=false fi enforcing=true if ! adb_su getenforce /dev/null; then LOG WARNING "device does not have sepolicy in enforcing mode" enforcing=false fi USB_SERIAL= if [ -n "${ANDROID_SERIAL}" -a "Darwin" != "${HOSTOS}" ]; then USB_SERIAL="`find /sys/devices -name serial | grep usb || true`" if [ -n "${USB_SERIAL}" ]; then USB_SERIAL=`echo "${USB_SERIAL}" | xargs grep -l ${ANDROID_SERIAL} || true` fi fi USB_ADDRESS= if [ -n "${USB_SERIAL}" ]; then USB_ADDRESS=${USB_SERIAL%/serial} USB_ADDRESS=usb${USB_ADDRESS##*/} fi USB_DEVICE=$(usb_devnum) [ -z "${ANDROID_SERIAL}${USB_ADDRESS}${USB_DEVICE}" ] || LOG INFO "${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE}" BUILD_DESCRIPTION=`get_property ro.build.description` [ -z "${BUILD_DESCRIPTION}" ] || LOG INFO "${BUILD_DESCRIPTION}" KERNEL_VERSION="`adb_su cat /proc/version /dev/null`" [ -z "${KERNEL_VERSION}" ] || LOG INFO "${KERNEL_VERSION}" ACTIVE_SLOT=`get_active_slot` [ -z "${ACTIVE_SLOT}" ] || LOG INFO "active slot is ${ACTIVE_SLOT}" # Acquire list of system partitions FSTAB_SUFFIXES=( "$(get_property ro.boot.fstab_suffix)" "$(get_property ro.boot.hardware)" "$(get_property ro.boot.hardware.platform)" ) FSTAB_PATTERN='\.('"$(join_with "|" "${FSTAB_SUFFIXES[@]}")"')$' FSTAB_FILE=$(adb_su ls -1 '/vendor/etc/fstab*' ") if [ -n "${FSTAB_FILE}" ]; then PARTITIONS=$(adb_su grep -v "^[#${SPACE}${TAB}]" "${FSTAB_FILE}" | skip_administrative_mounts | awk '$1 ~ /^[^\/]+$/ && "/"$1 == $2 && $4 ~ /(^|,)ro(,|$)/ { print $1 }' | sort -u | tr '\n' ' ') else PARTITIONS="system vendor" fi # KISS (we do not support sub-mounts for system partitions currently) # Ensure /system and /vendor mountpoints are in mounts list MOUNTS=$(for i in system vendor ${PARTITIONS}; do echo "/${i}" done | sort -u | tr '\n' ' ') LOG INFO "System Partitions list: ${PARTITIONS}" # Report existing partition sizes adb_sh ls -l /dev/block/by-name/ /dev/block/mapper/ /dev/null | sed -n 's@.* \([^ ]*\) -> /dev/block/\([^ ]*\)$@\1 \2@p' | while read name device; do [ super = ${name} -o cache = ${name} ] || ( for i in ${PARTITIONS}; do [ ${i} = ${name} -o ${i} = ${name%_[ab]} ] && exit done exit 1 ) || continue case ${device} in sd*) device=${device%%[0-9]*}/${device} ;; esac size=`adb_su cat /sys/block/${device}/size 2>/dev/null &2 if ${reboot}; then adb_reboot fi } # If reboot too soon after fresh flash, could trip device update failure logic if ${screen_wait}; then LOG INFO "waiting for screen to come up. Consider --no-wait-screen option" fi if ! wait_for_screen && ${screen_wait}; then screen_wait=false LOG WARNING "not healthy, no launcher, skipping wait for screen" fi ################################################################################ LOG RUN "Checking current overlayfs status" adb_wait || die "wait for device failed" adb_root || die "adb root failed" # We can not universally use adb enable-verity to ensure device is # in a overlayfs disabled state since it can prevent reboot on # devices that remount the physical content rather than overlayfs. # So lets do our best to surgically wipe the overlayfs state without # having to go through enable-verity transition. if surgically_wipe_overlayfs; then LOG WARNING "rebooting before test" adb_reboot || die "lost device after reboot after overlay wipe $(usb_status)" adb_root || die "lost device after elevation to root after wipe `usb_status`" fi is_overlayfs_mounted && die "overlay takeover unexpected at this phase" overlayfs_needed=true data_device=$(adb_sh awk '$2 == "/data" { print $1; exit }' /proc/mounts) D=$(adb_sh grep " ro," /proc/mounts &1 | grep "Filesystem features:.*shared_blocks" >/dev/null && no_dedupe=false done D=$(adb_sh df -k ${D} &2 if [ X"${D}" = X"${D##* 100[%] }" ] && ${no_dedupe} ; then overlayfs_needed=false # if device does not need overlays, then adb enable-verity will brick device can_restore_verity=false fi LOG OK "no overlay present before setup" ################################################################################ # Precondition is overlayfs *not* setup. LOG RUN "Testing adb disable-verity -R" T=$(adb_date) adb_su disable-verity -R >&2 err=${?} [[ ${err} -eq 0 || ${err} -eq 255 ]] || die -t "${T}" "disable-verity -R failed" sleep 2 adb_wait "${ADB_WAIT}" || die "lost device after adb disable-verity -R $(usb_status)" if [ "2" = "$(get_property partition.system.verified)" ]; then LOG ERROR "partition.system.verified=$(get_property partition.system.verified)" die "verity not disabled after adb disable-verity -R" fi if ${overlayfs_needed}; then is_overlayfs_mounted || die -d "no overlay takeover after adb disable-verity -R" LOG OK "overlay takeover after adb disable-verity -R" fi LOG OK "adb disable-verity -R" ################################################################################ LOG RUN "Checking kernel has overlayfs required patches" adb_root || die "adb root" if adb_test -d /sys/module/overlay || adb_sh grep -q "nodev${TAB}overlay" /proc/filesystems; then LOG OK "overlay module present" else LOG INFO "overlay module not present" fi if is_overlayfs_mounted 2>/dev/null; then if adb_test -f /sys/module/overlay/parameters/override_creds; then LOG OK "overlay module supports override_creds" else case "$(adb_sh uname -r &2 || die -t "${T}" "adb remount vendor" adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts &2 || die -t "${T}" "adb remount vendor from scratch" if ${overlayfs_needed}; then is_overlayfs_mounted /vendor || die -t "${T}" "expected overlay takeover /vendor" is_overlayfs_mounted /system 2>/dev/null && die -t "${T}" "unexpected overlay takeover /system" fi adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts &2 err=${?} [[ ${err} -eq 0 || ${err} -eq 255 ]] || die -t "${T}" "adb remount -R failed" sleep 2 adb_wait "${ADB_WAIT}" || die "lost device after adb remount -R $(usb_status)" if [ "2" = "$(get_property partition.system.verified)" ]; then LOG ERROR "partition.system.verified=$(get_property partition.system.verified)" die "verity not disabled after adb remount -R" fi if ${overlayfs_needed}; then is_overlayfs_mounted /system || die -d "expected overlay takeover /system" is_overlayfs_mounted /vendor 2>/dev/null || die -d "expected overlay takeover /vendor" LOG OK "overlay takeover after adb remount -R" fi LOG OK "adb remount -R" # For devices using overlayfs, remount -R should reboot after overlayfs setup. # For legacy device, manual reboot to ensure device clean state. if ! ${overlayfs_needed}; then LOG WARNING "Reboot to RO (device doesn't use overlayfs)" adb_reboot || die "lost device after reboot to RO $(usb_status)" fi ################################################################################ # Precondition is a verity-disabled device with overlayfs already setup. LOG RUN "Testing adb remount RW" # Feed log with selinux denials as baseline before overlays adb_unroot adb_sh find ${MOUNTS} /dev/null 2>/dev/null || true adb_root adb_sh grep -qE " (/system|/) [^ ]* rw," /proc/mounts &2 || die -t "${T}" "adb remount" adb_sh grep -qE " (/system|/) [^ ]* rw," /proc/mounts RW RW=$(adb_sh grep " rw," /proc/mounts &2 for d in ${D}; do if adb_sh tune2fs -l "${d}" &1 | grep -q "Filesystem features:.*shared_blocks" || adb_sh df -k "${d}" | grep -q " 100% "; then die "remount overlayfs missed a spot (rw)" fi done else is_overlayfs_mounted && die -t "${T}" "unexpected overlay takeover" fi echo -n "${RW}" | grep -v noatime && die "mounts (rw) are not noatime" LOG OK "adb remount RW" ################################################################################ LOG RUN "push content to ${MOUNTS}" adb_root || die "adb root" A="Hello World! $(date)" for i in ${MOUNTS} /system/priv-app; do echo "${A}" | adb_sh cat - ">${i}/hello" B="`adb_cat ${i}/hello`" || die "${i#/} hello" check_eq "${A}" "${B}" ${i} before reboot done SYSTEM_INO=`adb_sh stat --format=%i /system/hello /dev/null || die "adb pull /system/build.prop" # Prepend with extra newline in case the original file doesn't end with a newline. cat "${system_build_prop_original}" - <"${system_build_prop_modified}" # Properties added by adb remount test test.adb.remount.system.build.prop=true EOF adb push "${system_build_prop_modified}" /system/build.prop >/dev/null || die "adb push /system/build.prop" adb pull /system/build.prop "${system_build_prop_fromdevice}" >/dev/null || die "adb pull /system/build.prop" diff "${system_build_prop_modified}" "${system_build_prop_fromdevice}" >/dev/null || die "/system/build.prop differs from pushed content" ################################################################################ LOG RUN "reboot to confirm content persistent" fixup_from_recovery() { inRecovery || return 1 LOG ERROR "Device in recovery" adb reboot /dev/null 2>/dev/null || true fi # If overlayfs has a nested security problem, this will fail. adb_sh ls /system >/dev/null || die "ls /system" adb_test -d /system/priv-app || die "[ -d /system/priv-app ]" B="`adb_cat /system/priv-app/hello`" check_eq "${A}" "${B}" /system/priv-app after reboot # Only root can read vendor if sepolicy permissions are as expected. adb_root || die "adb root" for i in ${MOUNTS}; do B="`adb_cat ${i}/hello`" check_eq "${A}" "${B}" ${i#/} after reboot LOG OK "${i} content remains after reboot" done check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello /dev/null 2>/dev/null || true # Check if the updated build.prop is persistent after reboot. check_eq "true" "$(get_property 'test.adb.remount.system.build.prop')" "load modified build.prop" adb pull /system/build.prop "${system_build_prop_fromdevice}" >/dev/null || die "adb pull /system/build.prop" diff "${system_build_prop_modified}" "${system_build_prop_fromdevice}" >/dev/null || die "/system/build.prop differs from pushed content" LOG OK "/system/build.prop content remains after reboot" ################################################################################ LOG RUN "flash vendor, and confirm vendor override disappears" is_bootloader_fastboot=true # virtual device? case "$(get_property ro.product.vendor.device)" in vsoc_* | emulator_* | emulator64_*) is_bootloader_fastboot=false ;; esac is_userspace_fastboot=false if ! ${is_bootloader_fastboot}; then LOG WARNING "does not support fastboot flash, skipping" else wait_for_screen adb_root || die "adb root" VENDOR_DEVICE_CANDIDATES=( "/dev/block/mapper/vendor"{_${ACTIVE_SLOT},} "/dev/block/by-name/vendor"{_${ACTIVE_SLOT},} ) for b in "${VENDOR_DEVICE_CANDIDATES[@]}"; do if adb_test -e "${b}"; then adb pull "${b}" "${TMPDIR}/vendor.img" || die "adb pull ${b}" LOG INFO "pulled ${b} from device as vendor.img" break fi done [ -f "${TMPDIR}/vendor.img" ] || die "cannot find block device of vendor partition" avc_check adb reboot fastboot /dev/null; then if ${is_userspace_fastboot}; then die "overlay supposed to be minus /vendor takeover after flash vendor" else LOG WARNING "fastbootd missing required to invalidate, ignoring a failure" LOG WARNING "overlay supposed to be minus /vendor takeover after flash vendor" fi fi fi check_eq "${A}" "$(adb_cat /system/hello)" "/system content after flash vendor" check_eq "${SYSTEM_INO}" "$(adb_sh stat --format=%i /system/hello /dev/null || die "ls /system" adb_test -d /system/priv-app || die "[ -d /system/priv-app ]" check_eq "${A}" "$(adb_cat /system/priv-app/hello)" "/system/priv-app content after flash vendor" adb_root || die "adb root" if adb_test -e /vendor/hello; then if ${is_userspace_fastboot} || ! ${overlayfs_needed}; then die "vendor content after flash vendor" else LOG WARNING "fastbootd missing required to invalidate, ignoring a failure" LOG WARNING "vendor content after flash vendor" fi fi LOG OK "vendor override destroyed after flash verdor" fi >&2 wait_for_screen ################################################################################ LOG RUN "Clean up test content" adb_root || die "adb root" T=$(adb_date) D=$(adb remount 2>&1) || die -t "${T}" "adb remount" echo "${D}" >&2 if [[ "${D}" =~ [Rr]eboot ]]; then LOG OK "adb remount calls for a reboot after partial flash" # but we don't really want to, since rebooting just recreates the already tore # down vendor overlay. fi for i in ${MOUNTS} /system/priv-app; do adb_sh rm "${i}/hello" 2>/dev/null || true adb_test -e "${i}/hello" && die -t "${T}" "/${i}/hello lingers after rm" done ################################################################################ if ${is_bootloader_fastboot} && ${scratch_on_super}; then LOG RUN "test fastboot flash to scratch recovery" avc_check adb reboot fastboot /dev/null && fastboot_wait ${FASTBOOT_WAIT} || die "reboot into fastboot to flash scratch `usb_status`" fastboot flash --force scratch ${img} err=${?} fastboot reboot || die "can not reboot out of fastboot" [ 0 -eq ${err} ] || die "fastboot flash scratch" adb_wait ${ADB_WAIT} && adb_root || die "did not reboot after flashing empty scratch $(usb_status)" T=`adb_date` D=`adb disable-verity 2>&1` err=${?} if [ X"${D}" != "${D%?Now reboot your device for settings to take effect*}" ] then LOG WARNING "adb disable-verity requires a reboot after partial flash" adb_reboot && adb_root || die "failed to reboot" T=`adb_date` D="${D} `adb disable-verity 2>&1`" err=${?} fi echo "${D}" >&2 [ ${err} = 0 ] && [ X"${D}" = X"${D##*setup failed}" ] && [ X"${D}" != X"${D##*[Uu]sing overlayfs}" ] && LOG OK "recreated scratch" || die -t ${T} "setup for overlayfs" adb remount >&2 || die -t ${T} "remount failed" fi LOG PASSED "adb remount test"