Initial commit
Initial commit.
This commit is contained in:
211
bootloader/mcuboot/ci/fih_test_docker/damage_image.py
Normal file
211
bootloader/mcuboot/ci/fih_test_docker/damage_image.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# 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.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from imgtool.image import (IMAGE_HEADER_SIZE, IMAGE_MAGIC,
|
||||
TLV_INFO_MAGIC, TLV_PROT_INFO_MAGIC, TLV_VALUES)
|
||||
from shutil import copyfile
|
||||
|
||||
|
||||
def get_tlv_type_string(tlv_type):
|
||||
tlvs = {v: f"IMAGE_TLV_{k}" for k, v in TLV_VALUES.items()}
|
||||
return tlvs.get(tlv_type, "UNKNOWN({:d})".format(tlv_type))
|
||||
|
||||
|
||||
class ImageHeader:
|
||||
|
||||
def __init__(self):
|
||||
self.ih_magic = 0
|
||||
self.ih_load_addr = 0
|
||||
self.ih_hdr_size = 0
|
||||
self.ih_protect_tlv_size = 0
|
||||
self.ih_img_size = 0
|
||||
self.ih_flags = 0
|
||||
self.iv_major = 0
|
||||
self.iv_minor = 0
|
||||
self.iv_revision = 0
|
||||
self.iv_build_num = 0
|
||||
self._pad1 = 0
|
||||
|
||||
@staticmethod
|
||||
def read_from_binary(in_file):
|
||||
h = ImageHeader()
|
||||
|
||||
(h.ih_magic, h.ih_load_addr, h.ih_hdr_size, h.ih_protect_tlv_size, h.ih_img_size,
|
||||
h.ih_flags, h.iv_major, h.iv_minor, h.iv_revision, h.iv_build_num, h._pad1
|
||||
) = struct.unpack('<IIHHIIBBHII', in_file.read(IMAGE_HEADER_SIZE))
|
||||
return h
|
||||
|
||||
def __repr__(self):
|
||||
return "\n".join([
|
||||
" ih_magic = 0x{:X}".format(self.ih_magic),
|
||||
" ih_load_addr = " + str(self.ih_load_addr),
|
||||
" ih_hdr_size = " + str(self.ih_hdr_size),
|
||||
" ih_protect_tlv_size = " + str(self.ih_protect_tlv_size),
|
||||
" ih_img_size = " + str(self.ih_img_size),
|
||||
" ih_flags = " + str(self.ih_flags),
|
||||
" iv_major = " + str(self.iv_major),
|
||||
" iv_minor = " + str(self.iv_minor),
|
||||
" iv_revision = " + str(self.iv_revision),
|
||||
" iv_build_num = " + str(self.iv_build_num),
|
||||
" _pad1 = " + str(self._pad1)])
|
||||
|
||||
|
||||
class ImageTLVInfo:
|
||||
def __init__(self):
|
||||
self.format_string = '<HH'
|
||||
|
||||
self.it_magic = 0
|
||||
self.it_tlv_tot = 0
|
||||
|
||||
@staticmethod
|
||||
def read_from_binary(in_file):
|
||||
i = ImageTLVInfo()
|
||||
|
||||
(i.it_magic, i.it_tlv_tot) = struct.unpack('<HH', in_file.read(4))
|
||||
return i
|
||||
|
||||
def __repr__(self):
|
||||
return "\n".join([
|
||||
" it_magic = 0x{:X}".format(self.it_magic),
|
||||
" it_tlv_tot = " + str(self.it_tlv_tot)])
|
||||
|
||||
def __len__(self):
|
||||
return struct.calcsize(self.format_string)
|
||||
|
||||
|
||||
class ImageTLV:
|
||||
def __init__(self):
|
||||
self.it_value = 0
|
||||
self.it_type = 0
|
||||
self.it_len = 0
|
||||
|
||||
@staticmethod
|
||||
def read_from_binary(in_file):
|
||||
tlv = ImageTLV()
|
||||
(tlv.it_type, _, tlv.it_len) = struct.unpack('<BBH', in_file.read(4))
|
||||
(tlv.it_value) = struct.unpack('<{:d}s'.format(tlv.it_len), in_file.read(tlv.it_len))
|
||||
return tlv
|
||||
|
||||
def __len__(self):
|
||||
round_to = 1
|
||||
return int((4 + self.it_len + round_to - 1) // round_to) * round_to
|
||||
|
||||
|
||||
def get_arguments():
|
||||
parser = argparse.ArgumentParser(description='Corrupt an MCUBoot image')
|
||||
parser.add_argument("-i", "--in-file", required=True, help='The input image to be corrupted (read only)')
|
||||
parser.add_argument("-o", "--out-file", required=True, help='the corrupted image')
|
||||
parser.add_argument('-a', '--image-hash',
|
||||
default=False,
|
||||
action="store_true",
|
||||
required=False,
|
||||
help='Corrupt the image hash')
|
||||
parser.add_argument('-s', '--signature',
|
||||
default=False,
|
||||
action="store_true",
|
||||
required=False,
|
||||
help='Corrupt the signature of the image')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def damage_tlv(image_offset, tlv_off, tlv, out_file_content):
|
||||
damage_offset = image_offset + tlv_off + 4
|
||||
logging.info(" Damaging TLV at offset 0x{:X}...".format(damage_offset))
|
||||
value = bytearray(tlv.it_value[0])
|
||||
value[0] = (value[0] + 1) % 256
|
||||
out_file_content[damage_offset] = value[0]
|
||||
|
||||
|
||||
def is_valid_signature(tlv):
|
||||
return tlv.it_type == TLV_VALUES['RSA2048'] or tlv.it_type == TLV_VALUES['RSA3072']
|
||||
|
||||
|
||||
def damage_image(args, in_file, out_file_content, image_offset):
|
||||
in_file.seek(image_offset, 0)
|
||||
|
||||
# Find the Image header
|
||||
image_header = ImageHeader.read_from_binary(in_file)
|
||||
if image_header.ih_magic != IMAGE_MAGIC:
|
||||
raise Exception("Invalid magic in image_header: 0x{:X} instead of 0x{:X}".format(image_header.ih_magic, IMAGE_MAGIC))
|
||||
|
||||
# Find the TLV header
|
||||
tlv_info_offset = image_header.ih_hdr_size + image_header.ih_img_size
|
||||
in_file.seek(image_offset + tlv_info_offset, 0)
|
||||
|
||||
tlv_info = ImageTLVInfo.read_from_binary(in_file)
|
||||
if tlv_info.it_magic == TLV_PROT_INFO_MAGIC:
|
||||
logging.debug("Protected TLV found at offset 0x{:X}".format(tlv_info_offset))
|
||||
if image_header.ih_protect_tlv_size != tlv_info.it_tlv_tot:
|
||||
raise Exception("Invalid prot TLV len ({:d} vs. {:d})".format(image_header.ih_protect_tlv_size, tlv_info.it_tlv_tot))
|
||||
|
||||
# seek to unprotected TLV
|
||||
tlv_info_offset += tlv_info.it_tlv_tot
|
||||
in_file.seek(image_offset + tlv_info_offset)
|
||||
tlv_info = ImageTLVInfo.read_from_binary(in_file)
|
||||
|
||||
else:
|
||||
if image_header.ih_protect_tlv_size != 0:
|
||||
raise Exception("No prot TLV was found.")
|
||||
|
||||
logging.debug("Unprotected TLV found at offset 0x{:X}".format(tlv_info_offset))
|
||||
if tlv_info.it_magic != TLV_INFO_MAGIC:
|
||||
raise Exception("Invalid magic in tlv info: 0x{:X} instead of 0x{:X}".format(tlv_info.it_magic, TLV_INFO_MAGIC))
|
||||
|
||||
tlv_off = tlv_info_offset + len(ImageTLVInfo())
|
||||
tlv_end = tlv_info_offset + tlv_info.it_tlv_tot
|
||||
|
||||
# iterate over the TLV entries
|
||||
while tlv_off < tlv_end:
|
||||
in_file.seek(image_offset + tlv_off, 0)
|
||||
tlv = ImageTLV.read_from_binary(in_file)
|
||||
|
||||
logging.debug(" tlv {:24s} len = {:4d}, len = {:4d}".format(get_tlv_type_string(tlv.it_type), tlv.it_len, len(tlv)))
|
||||
|
||||
if is_valid_signature(tlv) and args.signature:
|
||||
damage_tlv(image_offset, tlv_off, tlv, out_file_content)
|
||||
elif tlv.it_type == TLV_VALUES['SHA256'] and args.image_hash:
|
||||
damage_tlv(image_offset, tlv_off, tlv, out_file_content)
|
||||
|
||||
tlv_off += len(tlv)
|
||||
|
||||
|
||||
def main():
|
||||
args = get_arguments()
|
||||
|
||||
logging.debug("The script was started")
|
||||
|
||||
copyfile(args.in_file, args.out_file)
|
||||
in_file = open(args.in_file, 'rb')
|
||||
|
||||
out_file_content = bytearray(in_file.read())
|
||||
|
||||
damage_image(args, in_file, out_file_content, 0)
|
||||
|
||||
in_file.close()
|
||||
|
||||
file_to_damage = open(args.out_file, 'wb')
|
||||
file_to_damage.write(out_file_content)
|
||||
file_to_damage.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(format='%(levelname)5s: %(message)s',
|
||||
level=logging.DEBUG, stream=sys.stdout)
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,45 @@
|
||||
# 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.
|
||||
|
||||
FROM ubuntu:jammy
|
||||
|
||||
# Get dependencies for retrieving and building TF-M with MCUBoot, and QEMU.
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get install -y \
|
||||
cmake \
|
||||
curl \
|
||||
gcc-arm-none-eabi \
|
||||
gdb-multiarch \
|
||||
git \
|
||||
libncurses5 \
|
||||
python3 \
|
||||
python3-pip \
|
||||
qemu-system-arm \
|
||||
file &&\
|
||||
apt-get clean all
|
||||
|
||||
# Installing python packages
|
||||
RUN python3 -m pip install \
|
||||
imgtool>=1.9.0 \
|
||||
Jinja2>=2.10.3 \
|
||||
PyYAML \
|
||||
pyasn1
|
||||
|
||||
# Add tfm work directory && get rid of spurious git ownership errors
|
||||
RUN mkdir -p /root/work/tfm &&\
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
# run the command
|
||||
CMD ["bash"]
|
||||
29
bootloader/mcuboot/ci/fih_test_docker/docker-build/build.sh
Normal file
29
bootloader/mcuboot/ci/fih_test_docker/docker-build/build.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 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
|
||||
|
||||
trap cleanup_exit INT TERM EXIT
|
||||
|
||||
cleanup_exit()
|
||||
{
|
||||
rm -f *.list *.key
|
||||
}
|
||||
|
||||
export LANG=C
|
||||
|
||||
image=mcuboot/fih-test
|
||||
docker build --pull --tag=$image .
|
||||
68
bootloader/mcuboot/ci/fih_test_docker/execute_test.sh
Normal file
68
bootloader/mcuboot/ci/fih_test_docker/execute_test.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
# Copyright (c) 2020-2023 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
|
||||
|
||||
source $(dirname "$0")/paths.sh
|
||||
|
||||
SKIP_SIZE=$1
|
||||
BUILD_TYPE=$2
|
||||
DAMAGE_TYPE=$3
|
||||
FIH_LEVEL=$4
|
||||
|
||||
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_PATH $TFM_SPE_BUILD_PATH
|
||||
|
||||
cmake -S $TFM_TESTS_PATH/tests_reg/spe \
|
||||
-B $TFM_SPE_BUILD_PATH \
|
||||
-DTFM_PLATFORM=arm/mps2/an521 \
|
||||
-DCONFIG_TFM_SOURCE_PATH=$TFM_PATH \
|
||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
||||
-DTFM_TOOLCHAIN_FILE=$TFM_PATH/toolchain_GNUARM.cmake \
|
||||
-DTEST_S=ON \
|
||||
-DTEST_NS=ON \
|
||||
-DTFM_PSA_API=ON \
|
||||
-DMCUBOOT_PATH=$MCUBOOT_PATH \
|
||||
-DMCUBOOT_LOG_LEVEL=INFO \
|
||||
$CMAKE_FIH_LEVEL
|
||||
cmake --build $TFM_SPE_BUILD_PATH -- install
|
||||
|
||||
cmake -S $TFM_TESTS_PATH/tests_reg \
|
||||
-B $TFM_BUILD_PATH \
|
||||
-DCONFIG_SPE_PATH=$TFM_SPE_BUILD_PATH/api_ns \
|
||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
||||
-DTFM_TOOLCHAIN_FILE=$TFM_SPE_BUILD_PATH/api_ns/cmake/toolchain_ns_GNUARM.cmake
|
||||
cmake --build $TFM_BUILD_PATH
|
||||
|
||||
cd $TFM_BUILD_PATH
|
||||
$MCUBOOT_PATH/ci/fih_test_docker/run_fi_test.sh $BOOTLOADER_AXF_PATH $SKIP_SIZE $DAMAGE_TYPE> fih_test_output.yaml
|
||||
|
||||
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"
|
||||
|
||||
python3 $MCUBOOT_PATH/ci/fih_test_docker/generate_test_report.py fih_test_output.yaml
|
||||
python3 $MCUBOOT_PATH/ci/fih_test_docker/validate_output.py fih_test_output.yaml $SKIP_SIZE $FIH_LEVEL
|
||||
41
bootloader/mcuboot/ci/fih_test_docker/fi_make_manifest.sh
Normal file
41
bootloader/mcuboot/ci/fih_test_docker/fi_make_manifest.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/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"
|
||||
204
bootloader/mcuboot/ci/fih_test_docker/fi_tester_gdb.sh
Normal file
204
bootloader/mcuboot/ci/fih_test_docker/fi_tester_gdb.sh
Normal file
@@ -0,0 +1,204 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) 2020-2022 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.
|
||||
|
||||
source $(dirname "$0")/paths.sh
|
||||
|
||||
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
|
||||
file $AXF_FILE
|
||||
b boot_go_for_image_id if image_id == 0
|
||||
continue
|
||||
delete breakpoints 1
|
||||
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 $AXF_FILE \
|
||||
-device loader,file=$TFM_IMAGE_PATH,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 $AXF_FILE --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
|
||||
AXF_FILE=${BOOTLOADER_AXF_PATH}
|
||||
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
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright (c) 2020-2023 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.
|
||||
|
||||
import argparse
|
||||
from utils import CATEGORIES, parse_yaml_file
|
||||
|
||||
|
||||
def print_results(results):
|
||||
|
||||
test_stats, failed_boot_last_lines, exec_fail_reasons = results
|
||||
|
||||
print("{:s}: {:d}.".format(CATEGORIES['TOTAL'], test_stats[CATEGORIES['TOTAL']]))
|
||||
print("{:s} ({:d}):".format(CATEGORIES['SUCCESS'], test_stats[CATEGORIES['SUCCESS']]))
|
||||
print(" {:s}: ({:d}):".format(CATEGORIES['ADDRES_NOEXEC'], test_stats[CATEGORIES['ADDRES_NOEXEC']]))
|
||||
test_with_skip = test_stats[CATEGORIES['SUCCESS']] - test_stats[CATEGORIES['ADDRES_NOEXEC']]
|
||||
print(" {:s}: ({:d}):".format(CATEGORIES['SKIPPED'], test_with_skip))
|
||||
print(" {:s} ({:d}):".format(CATEGORIES['NO_BOOT'], test_with_skip - test_stats[CATEGORIES['BOOT']]))
|
||||
for last_line in failed_boot_last_lines.keys():
|
||||
print(" last line: {:s} ({:d})".format(last_line, failed_boot_last_lines[last_line]))
|
||||
print(" {:s} ({:d})".format(CATEGORIES['BOOT'], test_stats[CATEGORIES['BOOT']]))
|
||||
print("{:s} ({:d}):".format(CATEGORIES['FAILED'], test_stats[CATEGORIES['TOTAL']] - test_stats[CATEGORIES['SUCCESS']]))
|
||||
for reason in exec_fail_reasons.keys():
|
||||
print(" {:s} ({:d})".format(reason, exec_fail_reasons[reason]))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='''Process a FIH test output yaml file, and output a human readable report''')
|
||||
parser.add_argument('filename', help='yaml file to process')
|
||||
|
||||
args = parser.parse_args()
|
||||
results = parse_yaml_file(args.filename)
|
||||
print_results(results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
bootloader/mcuboot/ci/fih_test_docker/paths.sh
Normal file
10
bootloader/mcuboot/ci/fih_test_docker/paths.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
WORK_PATH=/root/work/tfm
|
||||
MCUBOOT_PATH=$WORK_PATH/mcuboot
|
||||
TFM_PATH=$WORK_PATH/trusted-firmware-m
|
||||
TFM_TESTS_PATH=$WORK_PATH/tf-m-tests
|
||||
TFM_SPE_BUILD_PATH=$TFM_PATH/build_spe
|
||||
TFM_BUILD_PATH=$TFM_PATH/build
|
||||
BOOTLOADER_AXF_PATH=$TFM_SPE_BUILD_PATH/bin/bl2.axf
|
||||
TFM_IMAGE_NAME=tfm_s_ns_signed.bin
|
||||
TFM_IMAGE_OUTPUT_PATH=$TFM_BUILD_PATH
|
||||
TFM_IMAGE_PATH=$TFM_IMAGE_OUTPUT_PATH/$TFM_IMAGE_NAME
|
||||
88
bootloader/mcuboot/ci/fih_test_docker/run_fi_test.sh
Normal file
88
bootloader/mcuboot/ci/fih_test_docker/run_fi_test.sh
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/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
|
||||
|
||||
source $(dirname "$0")/paths.sh
|
||||
|
||||
# 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
|
||||
{
|
||||
local IMAGE_NAME=${TFM_IMAGE_NAME}
|
||||
local BACKUP_IMAGE_NAME=${TFM_IMAGE_NAME}.orig
|
||||
local IMAGE=$TFM_IMAGE_OUTPUT_PATH/$IMAGE_NAME
|
||||
mv $IMAGE $TFM_IMAGE_OUTPUT_PATH/$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
|
||||
|
||||
python3 $DIR/damage_image.py -i $TFM_IMAGE_OUTPUT_PATH/$BACKUP_IMAGE_NAME -o $IMAGE $DAMAGE_PARAM 1>&2
|
||||
}
|
||||
|
||||
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 $TFM_IMAGE_OUTPUT_PATH $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
|
||||
63
bootloader/mcuboot/ci/fih_test_docker/utils.py
Normal file
63
bootloader/mcuboot/ci/fih_test_docker/utils.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2023 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.
|
||||
|
||||
import collections
|
||||
import yaml
|
||||
|
||||
CATEGORIES = {
|
||||
'TOTAL': 'Total tests run',
|
||||
'SUCCESS': 'Tests executed successfully',
|
||||
'FAILED': 'Tests failed to execute successfully',
|
||||
# the execution never reached the address
|
||||
'ADDRES_NOEXEC': 'Address was not executed',
|
||||
# The address was successfully skipped by the debugger
|
||||
'SKIPPED': 'Address was skipped',
|
||||
'NO_BOOT': 'System not booted (desired behaviour)',
|
||||
'BOOT': 'System booted (undesired behaviour)'
|
||||
}
|
||||
|
||||
|
||||
def parse_yaml_file(filepath):
|
||||
with open(filepath) as f:
|
||||
results = yaml.safe_load(f)
|
||||
|
||||
if not results:
|
||||
raise ValueError("Failed to parse output yaml file.")
|
||||
|
||||
test_stats = collections.Counter()
|
||||
failed_boot_last_lines = collections.Counter()
|
||||
exec_fail_reasons = collections.Counter()
|
||||
|
||||
for test in results:
|
||||
test = test["skip_test"]
|
||||
|
||||
test_stats.update([CATEGORIES['TOTAL']])
|
||||
|
||||
if test["test_exec_ok"]:
|
||||
test_stats.update([CATEGORIES['SUCCESS']])
|
||||
|
||||
if "skipped" in test.keys() and not test["skipped"]:
|
||||
# The debugger didn't stop at this address
|
||||
test_stats.update([CATEGORIES['ADDRES_NOEXEC']])
|
||||
continue
|
||||
|
||||
if test["boot"]:
|
||||
test_stats.update([CATEGORIES['BOOT']])
|
||||
continue
|
||||
|
||||
failed_boot_last_lines.update([test["last_line"]])
|
||||
else:
|
||||
exec_fail_reasons.update([test["test_exec_fail_reason"]])
|
||||
|
||||
return test_stats, failed_boot_last_lines, exec_fail_reasons
|
||||
39
bootloader/mcuboot/ci/fih_test_docker/validate_output.py
Normal file
39
bootloader/mcuboot/ci/fih_test_docker/validate_output.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) 2023 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.
|
||||
|
||||
import argparse
|
||||
from utils import CATEGORIES, parse_yaml_file
|
||||
|
||||
|
||||
def validate_output(test_stats, skip_size, fih_level):
|
||||
if (test_stats[CATEGORIES['BOOT']] > 0
|
||||
and skip_size == "2,4,6" and fih_level == "MEDIUM"):
|
||||
raise ValueError("The number of sucessful boots was more than zero")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='''Process a FIH test output yaml file,
|
||||
and validate no sucessfull boots have happened''')
|
||||
parser.add_argument('filename', help='yaml file to process')
|
||||
parser.add_argument('skip_size', help='instruction skip size')
|
||||
parser.add_argument('fih_level', nargs="?",
|
||||
help='fault injection hardening level')
|
||||
|
||||
args = parser.parse_args()
|
||||
test_stats = parse_yaml_file(args.filename)[0]
|
||||
validate_output(test_stats, args.skip_size, args.fih_level)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user