Commit 6298067d authored by Mate Toth-Pal's avatar Mate Toth-Pal Committed by Andrzej Puzdrowski
Browse files

travis: Add FIH test scripts



Add scripts that can run instruction skip FIH tests on QEMU.

Co-authored-by: default avatarRaef Coles <raef.coles@arm.com>
Change-Id: Ia6da00174115e1dabaf84fdfc0e40476dc1b7a10
Signed-off-by: default avatarMate Toth-Pal <mate.toth-pal@arm.com>
parent 5495f203
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -16,4 +16,4 @@

set -e

docker run mcuboot/fih-test /bin/sh -c '/root/execute_test.sh'
 No newline at end of file
docker run mcuboot/fih-test /bin/sh -c '/root/execute_test.sh $0 $1 $2' 2,4,6,8,10 RELEASE SIGNATURE
 No newline at end of file
+26 −19
Original line number Diff line number Diff line
@@ -18,21 +18,28 @@ set -e

WORKING_DIRECTORY=/root/work/tfm
MCUBOOT_PATH=$WORKING_DIRECTORY/mcuboot

TFM_DIR=/root/work/tfm/trusted-firmware-m
TFM_DIR=$WORKING_DIRECTORY/trusted-firmware-m
TFM_BUILD_DIR=$TFM_DIR/build
MCUBOOT_AXF=install/outputs/MPS2/AN521/bl2.axf
SIGNED_TFM_BIN=install/outputs/MPS2/AN521/tfm_s_ns_signed.bin
QEMU_LOG_FILE=qemu.log
QEMU_PID_FILE=qemu_pid.txt

SKIP_SIZE=$1
BUILD_TYPE=$2
DAMAGE_TYPE=$3
FIH_LEVEL=$4

source ~/.bashrc

if test -z "$FIH_LEVEL"; then
    # Use the default level
    CMAKE_FIH_LEVEL=""
else
    CMAKE_FIH_LEVEL="-DMCUBOOT_FIH_PROFILE=\"$FIH_LEVEL\""
fi

# build TF-M with MCUBoot
mkdir -p $TFM_BUILD_DIR
cd $TFM_DIR
cmake -B $TFM_BUILD_DIR \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
    -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \
    -DTFM_PLATFORM=mps2/an521 \
    -DTEST_NS=ON \
@@ -40,21 +47,21 @@ cmake -B $TFM_BUILD_DIR \
    -DTFM_PSA_API=ON \
    -DMCUBOOT_PATH=$MCUBOOT_PATH \
    -DMCUBOOT_LOG_LEVEL=INFO \
    $CMAKE_FIH_LEVEL \
    .
cd $TFM_BUILD_DIR
make -j install

# Run MCUBoot and TF-M in QEMU
/usr/bin/qemu-system-arm \
    -M mps2-an521 \
    -kernel $MCUBOOT_AXF \
    -device loader,file=$SIGNED_TFM_BIN,addr=0x10080000 \
    -chardev file,id=char0,path=$QEMU_LOG_FILE \
    -serial chardev:char0 \
    -display none \
    -pidfile $QEMU_PID_FILE \
    -daemonize
BOOTLOADER_AXF='./install/outputs/MPS2/AN521/bl2.axf'

$MCUBOOT_PATH/ci/fih_test_docker/run_fi_test.sh $BOOTLOADER_AXF $SKIP_SIZE $DAMAGE_TYPE> fih_test_output.yaml

sleep 7
echo ""
echo "test finished with"
echo "    - BUILD_TYPE: $BUILD_TYPE"
echo "    - FIH_LEVEL: $FIH_LEVEL"
echo "    - SKIP_SIZE: $SKIP_SIZE"
echo "    - DAMAGE_TYPE: $DAMAGE_TYPE"

cat $QEMU_LOG_FILE
 No newline at end of file
# TODO: Create human readable output
cat fih_test_output.yaml
+41 −0
Original line number Diff line number Diff line
#!/bin/bash

# Copyright (c) 2020 Arm Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

OBJDUMP=arm-none-eabi-objdump
GDB=gdb-multiarch

# Check if the ELF file specified is compatible
if test $# -eq 0 || ! file $1 | grep "ELF" | grep "ARM" | grep "32" &>/dev/null; then
    echo "Incompatible file: $1" 1>&2
    exit 1
fi

# Extract the full path
AXF_PATH=$(realpath $1)
#Dump all objects that have a name containing FIH_LABEL
ADDRESSES=$($OBJDUMP $AXF_PATH -t | grep "FIH_LABEL")
# strip all data except "address, label_name"
ADDRESSES=$(echo "$ADDRESSES" | sed "s/\([[:xdigit:]]*\).*\(FIH_LABEL_FIH_CALL_[a-zA-Z]*\)_.*/0x\1, \2/g")
# Sort by address in ascending order
ADDRESSES=$(echo "$ADDRESSES" | sort)
# In the case that there is a START followed by another START take the first one
ADDRESSES=$(echo "$ADDRESSES" | sed "N;s/\(.*START.*\)\n\(.*START.*\)/\1/;P;D")
# Same for END except take the second one
ADDRESSES=$(echo "$ADDRESSES" | sed "N;s/\(.*END.*\)\n\(.*END.*\)/\2/;P;D")

# Output in CSV format with a label
echo "Address, Type"
echo "$ADDRESSES"
+199 −0
Original line number Diff line number Diff line
#!/bin/bash

# Copyright (c) 2020 Arm Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function skip_instruction {

    local SKIP_ADDRESS=$1
    local SKIP_SIZE=$2

    # Parse the ASM instruction from the address using gdb
    INSTR=$($GDB $AXF_FILE --batch -ex "disassemble $SKIP_ADDRESS" | grep "^ *$SKIP_ADDRESS" | sed "s/.*:[ \t]*\(.*\)$/\1/g")
    # Parse the C line from the address using gdb
    LINE=$($GDB $AXF_FILE --batch -ex "info line *$SKIP_ADDRESS" | sed "s/Line \([0-9]*\).*\"\(.*\)\".*/\2:\1/g")

    # Sometimes an address is in the middle of a 4 byte instruction. In that case
    # don't run the test
    if test "$INSTR" == ""; then
        return
    fi

    # Print out the meta-info about the test, in YAML
    echo "- skip_test:"
    echo "    addr: $SKIP_ADDRESS"
    echo "    asm:  \"$INSTR\""
    echo "    line: \"$LINE\""
    echo "    skip: $SKIP_SIZE"
    # echo -ne "$SKIP_ADDRESS | $INSTR...\t"

    cat >commands.gdb <<EOF
target remote localhost: 1234
b *$SKIP_ADDRESS
continue&
eval "shell sleep 0.5"
interrupt
if \$pc == $SKIP_ADDRESS
    echo "Stopped at breakpoint"
else
    echo "Failed to stop at breakpoint"
end
echo "PC before increase:"
print \$pc
set \$pc += $SKIP_SIZE
echo "PC after increase:"
print \$pc
detach
eval "shell sleep 0.5"
EOF

    echo -n '.' 1>&2

    # start qemu, dump the serial output to $QEMU_LOG_FILE
    QEMU_LOG_FILE=qemu.log
    QEMU_PID_FILE=qemu_pid.txt
    rm -f $QEMU_PID_FILE $QEMU_LOG_FILE
    /usr/bin/qemu-system-arm \
        -M mps2-an521 \
        -s -S \
        -kernel $IMAGE_DIR/bl2.axf \
        -device loader,file=$IMAGE_DIR/tfm_s_ns_signed.bin,addr=0x10080000 \
        -chardev file,id=char0,path=$QEMU_LOG_FILE \
        -serial chardev:char0 \
        -display none \
        -pidfile $QEMU_PID_FILE \
        -daemonize

    # start qemu, skip the instruction, and continue execution
    $GDB < ./commands.gdb &>gdb_out.txt

    # kill qemu
    kill -9 `cat $QEMU_PID_FILE`

    # If "Secure image initializing" is seen the TFM booted, which means that a skip
    # managed to defeat the signature check. Write out whether the image booted or
    # not to the log in YAML
    if cat $QEMU_LOG_FILE | grep -i "Starting bootloader" &>/dev/null; then
        # bootloader started successfully
        if cat gdb_out.txt | grep -i "Stopped at breakpoint" &>/dev/null; then
            # The target was stopped at the desired address
            if cat $QEMU_LOG_FILE | grep -i "Secure image initializing" &>/dev/null; then
                echo "    test_exec_ok: True"
                echo "    skipped: True"
                echo "    boot: True"

                #print the address that was skipped, and some context to the console
                echo "" 1>&2
                echo "Boot success: address: $SKIP_ADDRESS skipped: $SKIP_SIZE" 1>&2
                arm-none-eabi-objdump -d $IMAGE_DIR/bl2.axf --start-address=$SKIP_ADDRESS -S | tail -n +7 | head -n 14 1>&2
                echo "" 1>&2
                echo "" 1>&2
            else
                LAST_LINE=`tail -n 1 $QEMU_LOG_FILE | tr -dc '[:print:]'`
                echo "    test_exec_ok: True"
                echo "    skipped: True"
                echo "    boot: False"
                echo "    last_line: '$LAST_LINE' "
            fi
        else
            # The target was not stopped at the desired address.
            # The most probable reason is that the instruction for that address is
            # on a call path that is not taken in this run (e.g. error handling)
            if cat $QEMU_LOG_FILE | grep -i "Secure image initializing" &>/dev/null; then
                # The image booted, although it shouldn't happen as the test is to
                # be run with a corrupt image.
                echo "    test_exec_ok: False"
                echo "    test_exec_fail_reason: \"No instructions were skipped (e.g. branch was not executed), but booted successfully\""
            else
                # the execution didn't stop at the address (e.g. the instruction
                # is on a branch that is not taken)
                echo "    test_exec_ok: True"
                echo "    skipped: False"
            fi
        fi
    else
        # failed before the first printout
        echo "    test_exec_ok: True"
        echo "    skipped: True"
        echo "    boot: False"
        echo "    last_line: 'N/A' "
    fi
}

# Inform how the script is used
usage() {
    echo "$0 <image_dir> <start_addr> [<end_addr>] [(-s | --skip) <skip_len>]"
}

#defaults
SKIP=2
BIN_DIR=$(pwd)/install/outputs/MPS2/AN521
AXF_FILE=$BIN_DIR/bl2.axf
GDB=gdb-multiarch
BOOTLOADER=true

# Parse arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -s|--skip)
        SKIP="$2"
        shift
        shift
        ;;
        -h|--help)
        usage
        exit 0
        ;;
        *)
        if test -z "$IMAGE_DIR"; then
            IMAGE_DIR=$1
        elif test -z "$START"; then
            START=$1
        elif test -z "$END"; then
            END=$1
        else
            usage
            exit 1
        fi
        shift
        ;;
    esac
done

# Check that image directory, start and end address have been supplied
if test -z "$IMAGE_DIR"; then
    usage
    exit 2
fi

if test -z "$START"; then
    usage
    exit 2
fi

if test -z "$END"; then
    END=$START
fi

if test -z "$SKIP"; then
    SKIP='2'
fi

# Create the start-end address range (step 2)
ADDRS=$(printf '0x%x\n' $(seq "$START" 2 "$END"))

# For each address run the skip_instruction function on it
for ADDR in $ADDRS; do
    skip_instruction $ADDR $SKIP
done
+88 −0
Original line number Diff line number Diff line
#!/bin/bash

# Copyright (c) 2020 Arm Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e

# Get the dir this is running in and the dir the script is in.
PWD=$(pwd)
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )

# PAD is the amount of extra instructions that should be tested on each side of
# the critical region
PAD=6

MCUBOOT_AXF=$1
SKIP_SIZES=$2
DAMAGE_TYPE=$3

# Take an image and make it unbootable. This is done by replacing one of the
# strings in the image with a different string. This causes the signature check
# to fail
function damage_image
{
    IMAGEDIR=$(dirname $MCUBOOT_AXF)
    local IMAGE_NAME=tfm_s_ns_signed.bin
    local BACKUP_IMAGE_NAME=tfm_s_ns_signed.bin.orig
    local IMAGE=$IMAGEDIR/$IMAGE_NAME
    mv $IMAGE $IMAGEDIR/$BACKUP_IMAGE_NAME

    if [ "$DAMAGE_TYPE" = "SIGNATURE" ]; then
        DAMAGE_PARAM="--signature"
    elif [ "$DAMAGE_TYPE" = "IMAGE_HASH" ]; then
        DAMAGE_PARAM="--image-hash"
    else
        echo "Failed to damage image $IMAGE with param $DAMAGE_TYPE" 1>&2
        exit -1
    fi

    # TODO: damage image here
    cp $IMAGEDIR/$BACKUP_IMAGE_NAME $IMAGE
}

function run_test
{
    local SKIP_SIZE=$1

    $DIR/fi_make_manifest.sh $MCUBOOT_AXF > $PWD/fih_manifest.csv

    # Load the CSV FI manifest file, and output in START, END lines. Effectively
    # join START and END lines together with a comma seperator.
    REGIONS=$(sed "N;s/\(0x[[:xdigit:]]*\).*START\n\(0x[[:xdigit:]]*\).*END.*/\1,\2/g;P;D" $PWD/fih_manifest.csv)
    # Ignore the first line, which includes the CSV header
    REGIONS=$(echo "$REGIONS" | tail -n+2)

    for REGION in $REGIONS; do
        #Split the START,END pairs into the two variables
        START=$(echo $REGION | cut -d"," -f 1)
        END=$(echo $REGION | cut -d"," -f 2)

        # Apply padding, converting back to hex
        START=$(printf "0x%X" $((START - PAD)))
        END=$(printf "0x%X" $((END + PAD)))

        # Invoke the fi tester script
        $DIR/fi_tester_gdb.sh $IMAGEDIR $START $END --skip $SKIP_SIZE
    done
}

damage_image $MCUBOOT_AXF
# Run the run_test function with each skip length between min and max in turn.

IFS=', ' read -r -a sizes <<< "$SKIP_SIZES"
for size in "${sizes[@]}"; do
    echo "Run tests with skip size $size" 1>&2
    run_test $size
done