diff --git a/src/entrypoints/Main/ExperimentHIL.py b/src/entrypoints/Main/ExperimentHIL.py new file mode 100644 index 0000000000000000000000000000000000000000..adeb734810be57c0a942e9f0c96bae40646d3d36 --- /dev/null +++ b/src/entrypoints/Main/ExperimentHIL.py @@ -0,0 +1,344 @@ +from pwnlib import * +from pwn import * +from time import * +from enum import Enum +from intervaltree import IntervalTree +import random as rn +import pickle +import os +import subprocess as sp +import threading + +maxInjectionTime = 15 +maxTestTime = 90 +nExperiments = 500 + +cpu = [] # stores the cpu mean busy time and stdDev +criticalFailure = '' +apogee = 0 + +GDB_PID = 0 +GDB_PORT = 0 +elfPath = "" +experimentBase = "" + +injectable_addresses = IntervalTree() + +MatlabSimulatorFolder = "/home/streben/Documenti/SKYWARD/Repo/matlab-simulator-hil/simulator/" + +class FailureMode(Enum): + """ + > 0 = golden_run + > 1 = undetected but crc wrong + > 2 = silent data corruption (undetected and crc correct) + > 3 = data corruption handler + > 4 = signature mismatch handler + > 5 = hardware fault (reboot) + > 6 = infinite loop + > 7 = other reason + """ + GOLDEN = 0, + DATA_CORRUPTED = 1, + SILENT_CORRUPTION = 2, + DATA_HANDLER = 3, + SIGNATURE_HANDLER = 4, + HARDWARE_FAULT = 5, + INFINITE_LOOP = 6, + OTHER = 7 + +def fromFailurModeEnumToStr(FM): + if FM == FailureMode.GOLDEN: + return "GOLDEN" + elif FM == FailureMode.DATA_CORRUPTED: + return "DATA_CORRUPTED" + elif FM == FailureMode.SILENT_CORRUPTION: + return "SILENT_CORRUPTION" + elif FM == FailureMode.DATA_HANDLER: + return "DATA_HANDLER" + elif FM == FailureMode.SIGNATURE_HANDLER: + return "SIGNATURE_HANDLER" + elif FM == FailureMode.HARDWARE_FAULT: + return "HARDWARE_FAULT" + elif FM == FailureMode.INFINITE_LOOP: + return "INFINITE_LOOP" + elif FM == FailureMode.OTHER: + return "OTHER" + else: + return "Invalid FailurMode enum!" + + +def setup(s): + s.sendline(b"delete") # delete all breakpoints + s.sendline(b"b waitActuatorData") # breakpoint for injection + s.recvuntil(b"(gdb)") + s.sendline(b"b simulationEnd") # breakpoint for end of experiment (cutters actuated) + s.sendline(b"monitor reset halt") + s.recvuntil(b"(gdb)") + s.sendline(b"b DataCorruption_Handler") # breakpoint for fault handlers + s.clean() + s.sendline(b"b SigMismatch_Handler") # breakpoint for fault handlers + s.recvuntil(b"(gdb)") + sleep(0.5) + s.clean() + + +def setupAfterInjection(s): + s.sendline(b"delete") # delete all breakpoints + s.recvuntil(b"(gdb)") + s.sendline(b"b main") # breakpoint for end of experiment (cutters actuated) + s.sendline(b"b simulationEnd") # breakpoint for end of experiment (cutters actuated) + s.recvuntil(b"(gdb)") + s.sendline(b"b DataCorruption_Handler") # breakpoint for fault handlers + s.sendline(b"b SigMismatch_Handler") # breakpoint for fault handlers + s.recvuntil(b"(gdb)") + s.clean() + + +def save_run(name, result, injection, cpu): + """ + Saves in a file the run: + - seed: seed number + - name: file path where to save the structure + - result: result of the experiment + - run_<bench>: list of couples (time, crc) of each cycle + """ + data = { + "result": result, + "injection": injection, + "cpu" : cpu + } + with open(name+".pkl", 'wb') as f: + pickle.dump(data, f) + + +def recover(s): + os.system(f"kill -2 {GDB_PID}") + s.recvuntil(b"(gdb)") + s.sendline(b"monitor reset halt") + s.recvuntil(b"(gdb)") + s.clean() + + +def inject(s, ada_start, ada_size): + # os.system(f"kill -2 {GDB_PID}") + + # Extract SEU in ada + mem = hex(rn.randrange(start=ada_start, stop=(ada_start + ada_size), step=4)) + + # Extract value + value = 0b1<<rn.randint(0,31) + + s.recvuntil(b"(gdb)") + s.sendline(f"set *{mem}= *{mem}^{value}".encode()) + s.recvuntil(b"(gdb)") + setupAfterInjection(s) + s.sendline(b"continue") + s.recvuntil(b"Continuing.") + return mem + + +def simulatorThread(): + global liftoff + global criticalFailure + global apogee + global cpu + liftoff = False + criticalFailure = "" + apogee = 0 + cpu = [] + + try: + simu = process(["matlab", "-sd", MatlabSimulatorFolder, "-batch", "mainSimulator"]) + line = simu.recvline(timeout=maxTestTime) + while line: + # get cpu data only from liftoff to apogee + if line.find(b"cpu:") != -1 and liftoff and apogee == 0: + split = line.split() + cpu.append(tuple([split[1], split[3]])) + elif line.find(b"Liftoff:") != -1: + liftoff = True + elif line.find(b"CRITICAL FAILURE:") != -1: + criticalFailure = line + print(line) + break + elif line.find(b"apogee: ") != -1: + apogee = float(line.split()[1]) + break + # else: + # print(line) + line = simu.recvline(timeout=maxTestTime) + except Exception: + pass + + os.system(f"kill -2 {GDB_PID}") + + + +liftoff = False +criticalFailure = "" +apogee = 0 +cpu = [] + +def makeExperiments(nExperiments, folderName, needToFlash): + global liftoff + global criticalFailure + global apogee + global cpu + + # Extract paths for all needed + + elfPath = folderName + "/" + "main-entry.elf" + openOCDPath = folderName + "/" + "openocd.cfg" + experimentBase = folderName + "/" + folderName + + BOARD_SERIAL = "" + with open(openOCDPath, 'r') as f: + for line in f: + if line.find("gdb_port") != -1: + print(line) + GDB_PORT = int(line.split()[1]) + print("GDB_PORT: ", GDB_PORT) + elif line.find("hla_serial") != -1: + BOARD_SERIAL = line.split()[1] + print("BOARD_SERIAL: ", BOARD_SERIAL) + + if(GDB_PORT == 0): + print("Port of gdbserver not found!") + exit(-1) + + if(BOARD_SERIAL == ""): + print("Board serial not found!") + exit(-1) + + files = next(os.walk(folderName))[2] + numbers = [int(f[len(folderName):-4].split('_')[0]) for f in files if f.startswith(folderName)] + + starting = 0 + if(len(numbers) > 0): + starting = max(numbers)+1 + + # Reflash the boards if needed + if(needToFlash and starting < nExperiments): + print(f"Flashing {folderName} ({BOARD_SERIAL})") + os.system("pkill -SIGKILL openocd") + os.system("pkill -SIGKILL gdb") + if os.system(f"st-flash --serial {BOARD_SERIAL} write {folderName}/main-entry.bin 0x8000000 && st-flash --serial {BOARD_SERIAL} reset") != 0: + print("Couldn't flash!") + exit() + + # Symbols definition + elf = ELF(elfPath) + ada_start = elf.symbols['ada'] + ada_size = 272 # bytes + + for i in range(starting, nExperiments): + + result = FailureMode.OTHER + + while(result == FailureMode.OTHER): + + recover = False + os.system("pkill -SIGKILL openocd") + os.system("pkill -SIGKILL gdb") + + os.system(f"st-flash --serial {BOARD_SERIAL} reset") + os.system(f"openocd -f {openOCDPath} --debug=0 > /dev/null 2> /dev/null &") + + s = process(["gdb", elfPath]) + GDB_PID = s.proc.pid + print(f"gdb pid: {GDB_PID}") + s.sendline(f"target extended-remote :{GDB_PORT}".encode()) + s.sendline(b"set style enabled off") + s.recvuntil(b"(gdb)") + sleep(0.5) + s.clean() + + experimentFile = experimentBase + str(i) + + print(f"{i+1}/{nExperiments} {experimentFile}", end="\t") + setup(s) + + # Setup injection + s.sendline(b"continue") + if s.recvuntil(b"Continuing.", timeout=maxTestTime) == b'': + print("RECOVERING") + recover = True + continue + + simulator = threading.Thread(target=simulatorThread) + simulator.start() + + while not liftoff: + s.sendline(b"continue") + if s.recvuntil(b"Continuing.", timeout=maxTestTime) == b'': + print("RECOVERING") + recover = True + break + continue + + if(recover): + continue + + # Injecting after random amount of time (between 0 and 1.5 seconds) + injTime = rn.random() * maxInjectionTime + + # injection + injTs = time() + 5 + injTime + while time() < injTs: + s.sendline(b"continue") + if s.recvuntil(b"Continuing.", timeout=maxTestTime) == b'': + print("RECOVERING") + recover = True + break + + if(recover): + continue + + injection = inject(s, ada_start, ada_size) + + # Here we check where we end up (endExperiment, fault handlers, start of main due to reboot, stuck in an infinite loop) + msg = s.recvuntil(b"(gdb)", timeout=maxTestTime) + s.sendline(b"continue") + + simulator.join() + + if criticalFailure != "": + result = FailureMode.DATA_CORRUPTED + criticalFailure = "" + elif(msg == b""): + result = FailureMode.INFINITE_LOOP + # recover(s) + elif msg.find(b"DataCorruption_Handler") != -1: + result = FailureMode.DATA_HANDLER + elif msg.find(b"SigMismatch_Handler") != -1: + result = FailureMode.SIGNATURE_HANDLER + elif msg.find(b"simulationEnd") != -1: + # Check results since the test reached its end + result = FailureMode.SILENT_CORRUPTION + elif msg.find(b"main") != -1: + result = FailureMode.HARDWARE_FAULT + else: + print("Didn't recognized this result: ") + print(msg) + result = FailureMode.OTHER + + # print(check_results(), end="\t") + print(fromFailurModeEnumToStr(result), end="\t") + print(injection) + + if(result != FailureMode.OTHER): + save_run(experimentFile, result, injection, cpu) + + + # server.close() + +""" + argv: + - 0: program name + - 1: Folder for openocd.cfg, main-entry.elf and main-entry.bin +""" +if __name__ == "__main__": + context.terminal = ['bash', 'splitw', '-v'] + + makeExperiments(nExperiments, "reddi", True) + makeExperiments(nExperiments, "noProtection", True) diff --git a/src/entrypoints/Main/PostprocessHIL.py b/src/entrypoints/Main/PostprocessHIL.py new file mode 100644 index 0000000000000000000000000000000000000000..f689f51b5411037c403c0cf1be2fdaa77137f041 --- /dev/null +++ b/src/entrypoints/Main/PostprocessHIL.py @@ -0,0 +1,110 @@ +from pwnlib import * +from pwn import * +from time import * +from enum import Enum +from intervaltree import IntervalTree +from statistics import mean, stdev +import numpy as np +import random as rn +import pickle +import os +import subprocess as sp +import matplotlib.pyplot as plt + +baseFolder = "." + +# Log IP, address/register injected and value injected + +class FailureMode(Enum): + """ + > 0 = golden_run + > 1 = undetected but crc wrong + > 2 = silent data corruption (undetected and crc correct) + > 3 = data corruption handler + > 4 = signature mismatch handler + > 5 = hardware fault (reboot) + > 6 = infinite loop + > 7 = other reason + """ + GOLDEN = 0, + DATA_CORRUPTED = 1, + SILENT_CORRUPTION = 2, + DATA_HANDLER = 3, + SIGNATURE_HANDLER = 4, + HARDWARE_FAULT = 5, + INFINITE_LOOP = 6, + OTHER = 7 + +def fromFailurModeEnumToStr(FM): + if FM == FailureMode.GOLDEN: + return "GOLDEN" + elif FM == FailureMode.DATA_CORRUPTED: + return "DATA_CORRUPTED" + elif FM == FailureMode.SILENT_CORRUPTION: + return "SILENT_CORRUPTION" + elif FM == FailureMode.DATA_HANDLER: + return "DATA_HANDLER" + elif FM == FailureMode.SIGNATURE_HANDLER: + return "SIGNATURE_HANDLER" + elif FM == FailureMode.HARDWARE_FAULT: + return "HARDWARE_FAULT" + elif FM == FailureMode.INFINITE_LOOP: + return "INFINITE_LOOP" + elif FM == FailureMode.OTHER: + return "OTHER" + else: + return "Invalid FailurMode enum!" + + +def postprocess_runs(folder): + """ + Loads in a structure the previous run with given seed and name: + - name: the name of the file + - axs: axs[0, 0] acas; axs[0, 1] jpeg; axs[0, 2] latnav; axs[1, 0] ann; axs[1, 1] canny; axs[1, 2] scaling + + Returns + - list of the runs of different benchmarks (time, crc) + - result of the experiment as a FailureMode enum + """ + files = next(os.walk(folder))[2] + dataFile = [f for f in files if f.endswith(".pkl")] + + outcomes = { + "GOLDEN" : 0, + "DATA_CORRUPTED" : 0, + "SILENT_CORRUPTION" : 0, + "DATA_HANDLER" : 0, + "SIGNATURE_HANDLER" : 0, + "HARDWARE_FAULT" : 0, + "INFINITE_LOOP" : 0, + "OTHER" : 0, + } + + nFile = 0 + + cpuM = [] + cpuV = [] + for file in dataFile: + nFile += 1 + + print(f"{nFile}/{len(dataFile)}", end='\r') + with open(folder + "/" + file, 'rb') as f: + data = pickle.load(f) + + outcomes[fromFailurModeEnumToStr(data["result"])] += 1 + if(data["result"] == FailureMode.SILENT_CORRUPTION): + cpuM.append(mean([float(c[0]) for c in data["cpu"] ])) + cpuV.append(mean([float(c[1]) for c in data["cpu"] ])) + + print(folder) + print(outcomes) + print(mean(cpuM), mean(cpuV)) + plt.hist(cpuM, bins='auto', label=folder) + + +if __name__ == "__main__": + postprocess_runs(f"{baseFolder}/noProtection") + postprocess_runs(f"{baseFolder}/reddi") + plt.legend(loc='upper right') + plt.show() +