diff --git a/.gitignore b/.gitignore index 8851850..50f5d77 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ *.a *.lib +# IR files +*.ll + # Executables *.exe *.out @@ -62,4 +65,7 @@ examples/sha/*.csv examples/sha/sha examples/cuda/*.txt -examples/cuda/add \ No newline at end of file +examples/cuda/add + +# IDE +.idea/ diff --git a/README.md b/README.md index 0c20a2d..acaa837 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ The toolchain has been tested with the following versions: - CMake 3.22.1 - LLVM 16.0.0 +> RASM, RACFED and EDDI algorithm are already patched to LLVM 21. +> Patching of the other algorithms is being done. + During the development of ASPIS, done mostly on LLVM 15, we discovered a bug in the [`splitBasicBlock()`](https://llvm.org/doxygen/classllvm_1_1BasicBlock.html#a2bc5caaabd6841e4ab97237ebcaeb86d) procedure. The bug has been fixed in LLVM 16, so we recommend using it rather than applying the patch to the previous versions. ## Building @@ -80,6 +83,9 @@ ASPIS does not compile the annotated function or does not duplicate the annotate ## Built-in compilation pipeline `aspis.sh` is a simple command-line interface that allows users to run the entire compilation pipeline specifying a few command-line arguments. The arguments that are not recognised are passed directly to the front-end, hence all the `clang` arguments are admissible. +> RASM, RACFED and EDDI algorithm are already patched to LLVM 21. +> Patching of the other algorithms is being done. + ### Options - `-h`, `--help`: Display available options. - `-o `: Write the compilation output to ``. @@ -95,6 +101,7 @@ ASPIS does not compile the annotated function or does not duplicate the annotate - `--cfcss`: **(Default)** Enable CFCSS. - `--rasm`: Enable RASM. - `--inter-rasm`: Enable inter-RASM with the default signature `-0xDEAD`. + - `--racfed`: Enable RACFED. ### Example @@ -184,6 +191,11 @@ opt --enable-new-pm=0 -S -load build/passes/libEDDI.so -duplica clang out.ll -o out.elf ``` +> With newer versions of LLVM: +> `--enable-new-pm` is no longer valid. +> `-load` has been replaced by `-load-pass-plugin=build/passes/lib*.so -passes="passname"` + + ## References If you are using this tool in scientific works, please cite the following article: - Davide Baroffio, Federico Reghenzani, and William Fornaciari. 2024. Enhanced Compiler Technology for Software-based Hardware Fault Detection. ACM Trans. Des. Autom. Electron. Syst. 29, 5, Article 91 (September 2024), 23 pages. https://doi.org/10.1145/3660524 diff --git a/aspis.sh b/aspis.sh index c0c92c3..6865298 100755 --- a/aspis.sh +++ b/aspis.sh @@ -19,7 +19,8 @@ input_files="" clang_options= eddi_options="-S" cfc_options="-S" -llvm_bin=$(dirname $(which clang &> /dev/null) &> /dev/null) +llvm_bin=$(dirname "$(which clang)") +suffix="" build_dir="." dup=0 # 0 = eddi, 1 = seddi, 2 = fdsc cfc=0 # 0 = cfcss, 1 = rasm, 2 = inter-rasm @@ -65,15 +66,15 @@ title_msg () { perform_platform_checks() { if [ ! -f $1 ]; then - error_msg "\nCommand clang not found. Expected path: ${1}. Please check --llvm_bin parameter." + error_msg "\nCommand clang not found. Expected path: ${1}. Please check --llvm-bin parameter." fi if [ ! -f $2 ]; then - error_msg "\nCommand opt not found. Expected path: ${2}. Please check --llvm_bin parameter." + error_msg "\nCommand opt not found. Expected path: ${2}. Please check --llvm-bin parameter." fi if [ ! -f $3 ]; then - error_msg "\nCommand llvm-link not found. Expected path: ${3}. Please check --llvm_bin parameter." + error_msg "\nCommand llvm-link not found. Expected path: ${3}. Please check --llvm-bin parameter." fi } @@ -112,12 +113,14 @@ parse_commands() { -o Write the compilation output to . --build-dir Specify the directory where to place all the build files. - --llvm-bin Set the path to the llvm binaries (clang, opt, + --llvm-bin Set the path to the llvm binaries (clang, opt, llvm-link) to . - --exclude Set the files to exclude from the compilation. The + --suffix Set the suffix of the binary to use (clang, opt, + llvm-link) to . + --exclude Set the files to exclude from the compilation. The content of is the list of files to exclude, one for each line (wildcard * allowed). - --asmfiles Defines the set of assembly files required for the + --asmfiles Defines the set of assembly files required for the compilation. The content of is the list of assembly files to pass to the linker at compilation termination, one for each line (wildcard * allowed). @@ -128,12 +131,13 @@ parse_commands() { --eddi (Default) Enable EDDI. --seddi Enable Selective-EDDI. --fdsc Enable Full Duplication with Selective Checking. - --no-dup Completely disable data duplication + --no-dup Completely disable data duplication. --cfcss (Default) Enable CFCSS. --rasm Enable RASM. --inter-rasm Enable inter-RASM with the default signature -0xDEAD. - --no-cfc Completely disable control-flow checking + --racfed Enable RACFED. + --no-cfc Completely disable control-flow checking. Hardening options: --alternate-memmap When set, alternates the definition of original and @@ -155,16 +159,25 @@ EOF if [[ ${#opt} -eq 2 ]]; then parse_state=1; else - output_file=`echo "$opt" | cut -b 2`; + output_file=${opt##"-o"}; fi; ;; --llvm-bin*) + echo ${#opt} if [[ ${#opt} -eq 10 ]]; then parse_state=3; else - llvm_bin=`echo "$opt" | cut -b 10`; + llvm_bin=${opt##"--llvm-bin="}; fi; ;; + --suffix*) + if [[ ${#opt} -eq 8 ]]; then + parse_state=7; + else + suffix='-'; + suffix+=${opt##"--suffix="}; + fi; + ;; --exclude*) if [[ ${#opt} -eq 9 ]]; then parse_state=4; @@ -207,6 +220,9 @@ EOF --inter-rasm) cfc=2 ;; + --racfed) + cfc=3 + ;; --no-cfc) cfc=-1 ;; @@ -267,6 +283,10 @@ EOF build_dir="$opt"; parse_state=0; ;; + 7) + suffix="-$opt"; + parse_state=0; + ;; esac done @@ -291,9 +311,9 @@ EOF } fi - CLANG="${llvm_bin}/clang" - OPT="${llvm_bin}/opt" - LLVM_LINK="${llvm_bin}/llvm-link" + CLANG="${llvm_bin}/clang${suffix}" + OPT="${llvm_bin}/opt${suffix}" + LLVM_LINK="${llvm_bin}/llvm-link${suffix}" if [[ -n "$config_file" ]]; then CLANG="${CLANG} --config ${config_file}" @@ -319,27 +339,27 @@ run_aspis() { done ## LINK & PREPROCESS - exe $LLVM_LINK $build_dir/*.ll -o $build_dir/out.ll -opaque-pointers + exe $LLVM_LINK $build_dir/*.ll -o $build_dir/out.ll success_msg "Emitted and linked IR." if [[ $debug_enabled == false ]]; then - exe $OPT --enable-new-pm=1 --passes="strip" $build_dir/out.ll -o $build_dir/out.ll + exe $OPT --passes="strip" $build_dir/out.ll -o $build_dir/out.ll echo " Debug mode disabled, stripped debug symbols." fi - - exe $OPT --enable-new-pm=1 --passes="lowerswitch" $build_dir/out.ll -o $build_dir/out.ll + + exe $OPT --passes="lower-switch" $build_dir/out.ll -o $build_dir/out.ll ## FuncRetToRef - if [[ dup != -1 ]]; then - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libEDDI.so --passes="func-ret-to-ref" $build_dir/out.ll -o $build_dir/out.ll + if [[ dup -ne -1 ]]; then + exe $OPT -load-pass-plugin=$DIR/build/passes/libEDDI.so --passes="func-ret-to-ref" $build_dir/out.ll -o $build_dir/out.ll fi; title_msg "ASPIS transformations" ## DATA PROTECTION case $dup in 0) - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libEDDI.so --passes="eddi-verify" $build_dir/out.ll -o $build_dir/out.ll $eddi_options + exe $OPT -load-pass-plugin=$DIR/build/passes/libEDDI.so --passes="eddi-verify" $build_dir/out.ll -o $build_dir/out.ll $eddi_options ;; 1) exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libSEDDI.so --passes="eddi-verify" $build_dir/out.ll -o $build_dir/out.ll $eddi_options @@ -352,19 +372,21 @@ run_aspis() { esac success_msg "Applied data protection passes." - exe $OPT --enable-new-pm=1 --passes="simplifycfg" $build_dir/out.ll -o $build_dir/out.ll - + exe $OPT --passes="simplifycfg" $build_dir/out.ll -o $build_dir/out.ll ## CONTROL-FLOW CHECKING case $cfc in 0) - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libCFCSS.so --passes="cfcss-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options + exe $OPT -load-pass-plugin=$DIR/build/passes/libCFCSS.so --passes="cfcss-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options ;; 1) - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libRASM.so --passes="rasm-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options + exe $OPT -load-pass-plugin=$DIR/build/passes/libRASM.so --passes="rasm-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options ;; 2) - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libINTER_RASM.so --passes="rasm-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options + exe $OPT -load-pass-plugin=$DIR/build/passes/libINTER_RASM.so --passes="rasm-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options + ;; + 3) + exe $OPT -load-pass-plugin=$DIR/build/passes/libRACFED.so --passes="racfed-verify" $build_dir/out.ll -o $build_dir/out.ll $cfc_options ;; *) echo -e "\t--no-cfc specified!" @@ -393,8 +415,8 @@ run_aspis() { success_msg "Linked excluded files to the compilation." ## DuplicateGlobals - if [[ dup != -1 ]]; then - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libEDDI.so --passes="duplicate-globals" $build_dir/out.ll -o $build_dir/out.ll -S $eddi_options + if [[ dup -ne -1 ]]; then + exe $OPT -load-pass-plugin=$DIR/build/passes/libEDDI.so --passes="duplicate-globals" $build_dir/out.ll -o $build_dir/out.ll -S $eddi_options success_msg "Duplicated globals." fi; @@ -411,7 +433,7 @@ run_aspis() { exe $OPT $build_dir/out.ll -o $build_dir/out.ll -S $opt_flags if [[ "$enable_profiling" == "true" ]]; then title_msg "ASPIS Profiling" - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libPROFILER.so --passes="aspis-insert-check-profile" $build_dir/out.ll -o $build_dir/out.ll -S + exe $OPT -load-pass-plugin=$DIR/build/passes/libPROFILER.so --passes="aspis-insert-check-profile" $build_dir/out.ll -o $build_dir/out.ll -S success_msg "Code instrumented." exe $CLANG $clang_options $build_dir/out.ll $asm_files -o $build_dir/$output_file @@ -421,7 +443,7 @@ run_aspis() { success_msg "Profiled code executed." echo -e "Analyzing..." - exe $OPT --enable-new-pm=1 -load-pass-plugin=$DIR/build/passes/libPROFILER.so --passes="aspis-check-profile" $build_dir/out.ll -o $build_dir/out.ll -S + exe $OPT -load-pass-plugin=$DIR/build/passes/libPROFILER.so --passes="aspis-check-profile" $build_dir/out.ll -o $build_dir/out.ll -S exit fi; @@ -437,6 +459,6 @@ run_aspis() { success_msg "Done!" } -parse_commands $@ +parse_commands "$@" perform_platform_checks $CLANG $OPT $LLVM_LINK -run_aspis \ No newline at end of file +run_aspis diff --git a/passes/ASPIS.h b/passes/ASPIS.h index 4117034..caa50b6 100644 --- a/passes/ASPIS.h +++ b/passes/ASPIS.h @@ -19,7 +19,7 @@ using namespace llvm; // DATA PROTECTION class FuncRetToRef : public PassInfoMixin { private: - Function* updateFnSignature(Function &Fn, Module &Md); + void updateFnSignature(Function &Fn, Module &Md); void updateRetInstructions(Function &Fn); void updateFunctionCalls(Function &Fn, Function &NewFn); @@ -156,4 +156,89 @@ class RASM : public PassInfoMixin { }; -#endif \ No newline at end of file +/** + * @brief Pass implementing RACFED algorithm. + */ +class RACFED : public PassInfoMixin { +private: + std::map FuncAnnotations; + + /** + * Compile time signature map. + * + * Compile time signatures are unique identifiers for the single basic block. + */ + std::unordered_map compileTimeSig; + + /** + * SubRanPrevVals map. + * + * These values are added to compile time signature to identify unique + * branch jumps. + */ + std::unordered_map subRanPrevVals; + + /** + * Instra instruction sum map. + * + * This map contains the sum of all the random values put after instructions. + */ + std::unordered_map sumIntraInstruction; + + + #if (LOG_COMPILED_FUNCS == 1) + std::set CompiledFuncs; + #endif + + /** + * Initializes compile time signature and subRanPrevVal, for + * all of the basic blocks, with unique values. + */ + void initializeBlocksSignatures(Function &Fn); + + /** + * Adds updates to the runtime signature with random values + * after each instruction. + */ + void insertIntraInstructionUpdates(Function &Fn, + GlobalVariable *RuntimeSigGV, Type *IntType); + + /** + * Adds a check on runtime signature at the entrance of non entry blocks. + * + * If the runtime signature, after some proper modifications, does not match + * the compile time signature a jump to an error handling block is inserted. + */ + void checkJumpSignature(BasicBlock &BB, + GlobalVariable *RuntimeSigGV, Type *IntType, + BasicBlock &ErrBB); + + // TODO: Add documentation + Value *getCondition(Instruction &I); + + /** + * Adds an update of the runtime signature before a branch instruction. + * + * This function works in conjunction with checkJumpSignature(...). + */ + void updateBeforeJump(Module &Md, BasicBlock &BB, GlobalVariable *RuntimeSigGV, + Type *IntType); + + /** + * Adds a check on runtime signature before a return instruction. + * + * If the runtime signature, after some proper modifications, does not match + * the compile time signature a jump to an error handling block is inserted. + */ + Instruction *checkOnReturn(BasicBlock &BB, + GlobalVariable *RuntimeSigGV, + Type *IntType, BasicBlock &ErrBB, + Value *BckupRunSig); + +public: + PreservedAnalyses run(Module &Md, ModuleAnalysisManager &); + + static bool isRequired() { return true; } +}; + +#endif diff --git a/passes/CMakeLists.txt b/passes/CMakeLists.txt index b522814..691f0ac 100644 --- a/passes/CMakeLists.txt +++ b/passes/CMakeLists.txt @@ -49,6 +49,12 @@ add_library(INTER_RASM SHARED ) target_compile_definitions(INTER_RASM PRIVATE INTRA_FUNCTION_CFC=1) +# RACFED +add_library(RACFED SHARED + RACFED.cpp + Utils/Utils.cpp +) + add_library(PROFILER SHARED Profiling/ASPISCheckProfiler.cpp Utils/Utils.cpp diff --git a/passes/DuplicateGlobals.cpp b/passes/DuplicateGlobals.cpp index 247a79a..a66c89e 100755 --- a/passes/DuplicateGlobals.cpp +++ b/passes/DuplicateGlobals.cpp @@ -54,7 +54,7 @@ GlobalVariable* DuplicateGlobals::getDuplicatedGlobal(Module &Md, GlobalVariable if (DuplicatedGlobals.find(&GV) != DuplicatedGlobals.end()) { return DuplicatedGlobals.find(&GV)->second; } - else if (GV.getName().endswith("_dup")) { + else if (GV.getName().ends_with("_dup")) { return NULL; } else { @@ -71,7 +71,7 @@ void DuplicateGlobals::duplicateCall(Module &Md, CallBase* UCall, Value* Origina return; } // if the function is already a _dup function, we just duplicate the operand corresponding to our global - if (UCall->getCalledFunction()->getName().endswith("_dup")) { + if (UCall->getCalledFunction()->getName().ends_with("_dup")) { int i = 0; for (auto &Op : UCall->args()) { if (Op == Original) { @@ -167,9 +167,9 @@ PreservedAnalyses DuplicateGlobals::run(Module &Md, ModuleAnalysisManager &AM) { // if the global is a struct or an array we cannot just duplicate the stores bool toDuplicate = !isa(GV) && FuncAnnotations.find(GV) != FuncAnnotations.end() && - (FuncAnnotations.find(GV))->second.startswith("to_duplicate"); - if (! (GV->getType()->isFunctionTy() || GV->isConstant() || GV->getValueType()->isStructTy() || GV->getValueType()->isArrayTy() || GV->getValueType()->isOpaquePointerTy()) - || toDuplicate/* && ! GV.getName().endswith("_dup") */) { + (FuncAnnotations.find(GV))->second.starts_with("to_duplicate"); + if (! (GV->getType()->isFunctionTy() || GV->isConstant() || GV->getValueType()->isStructTy() || GV->getValueType()->isArrayTy() || GV->getValueType()->isPointerTy()) + || toDuplicate/* && ! GV.getName().ends_with("_dup") */) { // see if the global variable has already been cloned GlobalVariable *GVCopy = Md.getGlobalVariable((GV->getName() + "_dup").str(), true); Constant *Initializer = NULL; @@ -182,7 +182,7 @@ PreservedAnalyses DuplicateGlobals::run(Module &Md, ModuleAnalysisManager &AM) { GVCopy->setExternallyInitialized(GV->isExternallyInitialized()); } } - if (GVCopy == NULL && !GV->getName().endswith_insensitive("_dup")) { + if (GVCopy == NULL && !GV->getName().ends_with_insensitive("_dup")) { // get a copy of the global variable GVCopy = new GlobalVariable( Md, diff --git a/passes/EDDI.cpp b/passes/EDDI.cpp index 7f110a9..d8e5a35 100755 --- a/passes/EDDI.cpp +++ b/passes/EDDI.cpp @@ -414,7 +414,7 @@ void EDDI::fixFuncValsPassedByReference( Function *EDDI::getFunctionDuplicate(Function *Fn) { // If Fn ends with "_dup" we have already the duplicated function. // If Fn is NULL, it means that we don't have a duplicate - if (Fn == NULL || Fn->getName().endswith("_dup")) { + if (Fn == NULL || Fn->getName().ends_with("_dup")) { return Fn; } @@ -432,7 +432,7 @@ Function *EDDI::getFunctionDuplicate(Function *Fn) { Function *EDDI::getFunctionFromDuplicate(Function *Fn) { // If Fn ends with "_dup" we have already the duplicated function. // If Fn is NULL, it means that we don't have a duplicate - if (Fn == NULL || !Fn->getName().endswith("_dup")) { + if (Fn == NULL || !Fn->getName().ends_with("_dup")) { return Fn; } @@ -486,8 +486,8 @@ void EDDI::duplicateGlobals( for (auto GV : GVars) { if (!isa(GV) && FuncAnnotations.find(GV) != FuncAnnotations.end()) { - if ((FuncAnnotations.find(GV))->second.startswith("runtime_sig") || - (FuncAnnotations.find(GV))->second.startswith("run_adj_sig")) { + if ((FuncAnnotations.find(GV))->second.starts_with("runtime_sig") || + (FuncAnnotations.find(GV))->second.starts_with("run_adj_sig")) { continue; } } @@ -505,13 +505,13 @@ void EDDI::duplicateGlobals( bool isConstant = GV->isConstant(); bool isStruct = GV->getValueType()->isStructTy(); bool isArray = GV->getValueType()->isArrayTy(); - bool isPointer = GV->getValueType()->isOpaquePointerTy(); - bool endsWithDup = GV->getName().endswith("_dup"); + bool isPointer = GV->getValueType()->isPointerTy(); + bool endsWithDup = GV->getName().ends_with("_dup"); bool hasExternalLinkage = GV->isExternallyInitialized() || GV->hasExternalLinkage(); bool isMetadataInfo = GV->getSection() == "llvm.metadata"; bool toExclude = !isa(GV) && FuncAnnotations.find(GV) != FuncAnnotations.end() && - (FuncAnnotations.find(GV))->second.startswith("exclude"); + (FuncAnnotations.find(GV))->second.starts_with("exclude"); bool isConstStruct = GV->getSection() != "llvm.metadata" && GV->hasInitializer() && isa(GV->getInitializer()); bool isStructOfGlobals = false; // is true if and only if the global variable that we are duplicating contains at least a global pointer bool isStructOfFunctions = false; // is true if the global variable that we are duplicating contains at least a global pointer, and such global pointer is a function pointer @@ -579,7 +579,7 @@ void EDDI::duplicateGlobals( auto *valueOperand =storeInst->getValueOperand(); if(isa(valueOperand)){ CallBase *callInst = cast(valueOperand); - if (callInst->getCalledFunction() && callInst->getCalledFunction()->getName().equals("__cxa_begin_catch")) + if (callInst->getCalledFunction() && callInst->getCalledFunction()->getName() == "__cxa_begin_catch") {return true;} } @@ -822,7 +822,7 @@ int EDDI::duplicateInstruction( Callee = getFunctionFromDuplicate(Callee); // check if the function call has to be duplicated if ((FuncAnnotations.find(Callee) != FuncAnnotations.end() && - (*FuncAnnotations.find(Callee)).second.startswith("to_duplicate")) || + (*FuncAnnotations.find(Callee)).second.starts_with("to_duplicate")) || isIntrinsicToDuplicate(CInstr)) { // duplicate the instruction cloneInstr(*CInstr, DuplicatedInstructionMap); @@ -1247,4 +1247,4 @@ llvm::PassPluginLibraryInfo getEDDIPluginInfo() { extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return getEDDIPluginInfo(); -} \ No newline at end of file +} diff --git a/passes/RACFED.cpp b/passes/RACFED.cpp new file mode 100644 index 0000000..3eca8af --- /dev/null +++ b/passes/RACFED.cpp @@ -0,0 +1,603 @@ +/**************************************************************************************** + * @brief LLVM pass implementing Random Additive Control Flow Error Detection (RACFED). + * Original algorithm by Vankeirsbilck et Al. (DOI: 10.1007/978-3-319-99130-6_15) + * + * @author Martina Starone, Gabriele Santandrea, Politecnico di Milano, Italy + * (martina.starone@mail.polimi.it, gabriele.santandrea@mail.polimi.it) + * + * The RACFED algorithm is reported here. + * It was splitted into sections in order to develop a more readeable code. + * + * initializeBlocksSignatures + * 1:for all Basic Block (BB) in CFG do + * 2: repeat compileTimeSig ← random number + * 3: until compileTimeSig is unique + * 4: repeat subRanPrevVal ← random number + * 5: until (compileTimeSig + subRanPrevVal) is unique + * insertIntraInstructionUpdates + * 6:for all BB in CFG do + * 7: if NrInstrBB > 2 then + * 8: for all original instructions insert after + * 9: signature ← signature + random number + * checkJumpSignature + * 10:for all BB in CFG insert at beginning + * 11: signature ← signature − subRanPrevVal + * 12: if signature != compileTimeSig error() + * 13:for all BB in CFG do + * checkOnReturn + * 14: if Last Instr. is return instr. and NrIntrBB > 1 then + * 15: Calculate needed variables + * 16: return Val ← random number + * 17: adjust Value ← (compileTimeSigBB + SumIntraInstructions) - + * 18: return Val + * 19: Insert signature update before return instr. + * 20: signature ← signature + adjustValue + * 21: if signature != returnVal error() + * 22: else + * updateBeforeJump + * 23: for all Successor of BB do + * 24: adjustValue ← (compileTimeSigBB + SumIntraInstructions) - + * 25: (compileTimeSigSuccs + subRanPrevValSuccs) + * 26: Insert signature update at BB end + * 27: signature ← signature + adjustValue + *************************************************************************************/ + +#include "ASPIS.h" +#include "Utils/Utils.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" + +#include + +#define MARTI_DEBUG false + +using namespace llvm; + +// TODO: Seeds could be randomised + +// TODO: Check TODOs in updateBeforeJump + +/// Uniform distribution for 32 bits numbers. +/// +/// In each function using this distribution a different seed will be used. +/// Seeds are chosen to be constant for reproducibility. +/// +/// The range doesn't encapsulate the complete representation of 32 bits unsigned, +/// this was a design choice since multiple 32 bits are sum between each +/// other throughout the algorithm thus no overflow should occur when summing. +std::uniform_int_distribution dist32(1, 0x7fffffff); + +// -------- INITIALIZE BLOCKS SIGNATURES -------- + +/** + * Checks whether the compile time signature is unique. + * + * @param bb_num compile time signature of the current analysed basic block. + * @param compileTimeSig already assigned compile time signatures + */ +bool isNotUniqueCompileTimeSig( + const uint32_t bb_num, + const std::unordered_map &compileTimeSig +) { + for (const auto &[_, other_bb_id] : compileTimeSig) { + if ( other_bb_id == bb_num ) return true; + } + return false; +} + +/** + * Checks whether the compile time signature (already unique) + * + second unique identifier (subRanPrevVal). + * + * @param current_id compileTimeSignature + subRanPrevVal for the + * current basic block. + * @param compileTimeSig already assigned compile time signatures. + * @param subRanPrevVals already assigned subRanPrevVals. + */ +bool isNotUnique( + const uint32_t current_id, + const std::unordered_map &compileTimeSig, + const std::unordered_map &subRanPrevVals +) { + for (const auto &[other_bb, other_bb_num] : compileTimeSig) { + uint32_t other_id = static_cast(other_bb_num) + subRanPrevVals.at(other_bb); + if ( other_id == current_id ) { + return true; + } + } + + return false; +} + +/* + * initializeBlocksSignatures + * 1:for all Basic Block (BB) in CFG do + * 2: repeat compileTimeSig ← random number + * 3: until compileTimeSig is unique + * 4: repeat subRanPrevVal ← random number + * 5: until (compileTimeSig + subRanPrevVal) is unique + */ +void RACFED::initializeBlocksSignatures(Function &Fn) { + std::mt19937 rng(0xB00BA5); // constant seed for reproducibility + uint32_t randomBB; + uint32_t randomSub; + + for (BasicBlock &BB : Fn) { + do { + randomBB = dist32(rng); + } while ( isNotUniqueCompileTimeSig(randomBB, compileTimeSig) ); + + do { + randomSub = dist32(rng); + } while ( isNotUnique( + randomBB + randomSub, + compileTimeSig, + subRanPrevVals) ); + + compileTimeSig.insert(std::pair(&BB, randomBB)); + subRanPrevVals.insert(std::pair(&BB, randomSub)); + } +} + +// --------- INSERT UPDATES AFTER INSTRUCTIONS ----------- + +/** + * Computes the number of original instructions of the intermediate representation. + */ +void originalInstruction(BasicBlock &BB, std::vector &OrigInstructions) { + for (Instruction &I : BB) { + if ( isa(&I) ) continue; // NOT ORIGINAL + if ( I.isTerminator() ) continue; // NOT ORIGINAL + if ( isa(&I) ) continue; + OrigInstructions.push_back(&I); + } +} + +/* + * insertIntraInstructionUpdates + * 6:for all BB in CFG do + * 7: if NrInstrBB > 2 then + * 8: for all original instructions insert after + * 9: signature ← signature + random number + */ +void RACFED::insertIntraInstructionUpdates(Function &Fn, + GlobalVariable *RuntimeSigGV, + Type *IntType) { + std::mt19937 rng(0xC0FFEE); // fixed seed for reproducibility + + // 6: for all BB in CFG do + for (auto &BB: Fn){ + std::vector OrigInstructions; + originalInstruction(BB, OrigInstructions); + + // 7: if NrInstrBB > 2 then + if ( OrigInstructions.size() <= 2 ) continue; + + uint64_t partial_sum = 0; + + // 8: for all original instructions insert after + for (Instruction *I : OrigInstructions) { + Instruction *InsertPt = nullptr; + + if ( I->isTerminator() ) { + InsertPt = I; // insert BEFORE terminator + } else { + InsertPt = I->getNextNode(); // insert BEFORE next instruction (equivalent to "after I") + } + + IRBuilder<> InstrIR(InsertPt); + + // 9: signature ← signature + random number + uint64_t K = dist32(rng); + partial_sum += K; + + Value *Sig = InstrIR.CreateLoad(IntType, RuntimeSigGV); + Value *NewSig = InstrIR.CreateAdd(Sig, ConstantInt::get(IntType, K), "sig_add"); + InstrIR.CreateStore(NewSig, RuntimeSigGV); + } + // Track total sum + sumIntraInstruction[&BB] = partial_sum; + } +} + +// --------- CHECK BLOCKS AT JUMP END --------- + +/* + * checkJumpSignature + * 10:for all BB in CFG insert at beginning + * 11: signature ← signature − subRanPrevVal + * 12: if signature != compileTimeSig error() + * 13:for all BB in CFG do + */ +void RACFED::checkJumpSignature(BasicBlock &BB, + GlobalVariable *RuntimeSigGV, Type *IntType, + BasicBlock &ErrBB) { + if ( BB.isEntryBlock() ) return; + + // In this case BB is not the first Basic Block of the function, + // so it has to update RuntimeSig and check it + auto FirstNonPHI = BB.getFirstNonPHIIt(); + if ( (FirstNonPHI != BB.end() && isa(FirstNonPHI)) || + BB.getName().contains_insensitive("verification") ) { + + if ( BB.getFirstInsertionPt() == BB.end() ) return; // Skip empty/invalid blocks + + int randomNumberBB = compileTimeSig.find(&BB)->second; + IRBuilder<> BChecker(&*BB.getFirstInsertionPt()); + BChecker.CreateStore(llvm::ConstantInt::get(IntType, randomNumberBB), RuntimeSigGV, true); + } else if ( !BB.getName().contains_insensitive("errbb") ) { + // Get compile signatures + int compileTimeSigCurrBB = compileTimeSig.find(&BB)->second; + int subRanPrevValCurrBB = subRanPrevVals.find(&BB)->second; + // Create verification basic block + BasicBlock *VerificationBB = BasicBlock::Create( + BB.getContext(), "RACFED_Verification_BB", BB.getParent(), &BB + ); + IRBuilder<> BChecker(VerificationBB); + + // Add instructions for the first runtime signature update + Value *InstrRuntimeSig = + BChecker.CreateLoad(IntType, RuntimeSigGV, true); + + // 11: signature ← signature − subRanPrevVal + Value *RuntimeSignatureVal = BChecker.CreateSub( + InstrRuntimeSig, llvm::ConstantInt::get(IntType, subRanPrevValCurrBB)); + BChecker.CreateStore(RuntimeSignatureVal, RuntimeSigGV, true); + + // update phi placing them in the new block + while (isa(&BB.front())) { + Instruction *PhiInst = &BB.front(); + PhiInst->removeFromParent(); + PhiInst->insertInto(VerificationBB, VerificationBB->getFirstInsertionPt()); + } + + // replace the uses of BB with VerificationBB + BB.replaceAllUsesWith(VerificationBB); + + // Fix PHI nodes in successors + // replaceAllUsesWith updates PHI nodes in successors to point to VerificationBB, + // but the actual control flow is VerificationBB -> BB -> Succ, so Succ still sees BB + // as predecessor. + for (BasicBlock *Succ : successors(&BB)) { + for (PHINode &Phi : Succ->phis()) { + for (unsigned i = 0; i < Phi.getNumIncomingValues(); ++i) { + if ( Phi.getIncomingBlock(i) == VerificationBB ) { + Phi.setIncomingBlock(i, &BB); + } + } + } + } + + // 12: if signature != compileTimeSig error() + // add instructions for checking the runtime signature + Value *CmpVal = BChecker.CreateCmp( + llvm::CmpInst::ICMP_EQ, RuntimeSignatureVal, + llvm::ConstantInt::get(IntType, compileTimeSigCurrBB) + ); + BChecker.CreateCondBr(CmpVal, &BB, &ErrBB); + + // Map NewBB to the same signature requirements as BB so predecessors can + // target it correctly + compileTimeSig[VerificationBB] = compileTimeSigCurrBB; + subRanPrevVals[VerificationBB] = subRanPrevValCurrBB; + } +} + +// --- UPDATE BRANCH SIGNATURE BEFORE JUMP --- + +// TODO: Add documentation in ASPIS.h +Value *RACFED::getCondition(Instruction &I) { + if ( isa(I) && cast(I).isConditional() ) { + if ( !cast(I).isConditional() ) { + return nullptr; + } else { + return cast(I).getCondition(); + } + } else if ( isa(I) ) { + errs() << "There is a switch!\n"; + abort(); + return cast(I).getCondition(); + } else { + assert(false && "Tried to get a condition on a function that is not a " + "branch or a switch"); + } +} + +#if MARTI_DEBUG +/** + * Adds a call to printf to check the signature + */ +static void printSig(Module &Md, IRBuilder<> &B, Value *SigVal, const char *Msg) { + LLVMContext &Ctx = Md.getContext(); + + // int printf(const char*, ...) + FunctionCallee Printf = Md.getOrInsertFunction( + "printf", + FunctionType::get(IntegerType::getInt32Ty(Ctx), + PointerType::getUnqual(Ctx), + true)); + + // Crea stringa globale "Msg: %ld\n" + std::string Fmt = std::string(Msg) + ": %ld\n"; + Value *FmtStr = B.CreateGlobalString(Fmt); + + + if ( SigVal->getType()->isIntegerTy(32) ) { + SigVal = B.CreateZExt(SigVal, Type::getInt64Ty(Ctx)); + } + + B.CreateCall(Printf, {FmtStr, SigVal}); +} +#endif + +/* + * updateBeforeJump + * 23: for all Successor of BB do + * 24: adjustValue ← (compileTimeSigBB + SumIntraInstructions) - + * 25: (compileTimeSigSuccs + subRanPrevValSuccs) + * 26: Insert signature update at BB end + * 27: signature ← signature + adjustValue + */ +void RACFED::updateBeforeJump(Module &Md, BasicBlock &BB, GlobalVariable *RuntimeSigGV, + Type *IntType) { + Instruction *Term = BB.getTerminator(); + IRBuilder<> B(&BB); + B.SetInsertPoint(Term); + auto *BI = dyn_cast(Term); + if ( !BI ) return; + + //TODO: check this + + // Calculate Source Static Signature: CT_BB + SumIntra + uint64_t SourceStatic = + static_cast(compileTimeSig[&BB]) + sumIntraInstruction[&BB]; + + Value *Current = B.CreateLoad(IntType, RuntimeSigGV, "current"); + #if MARTI_DEBUG + printSig(Md, B, Current, "current"); + #endif + + //TODO: until here + + //define if conditional or unconditional branch + //Conditional: expected= CT_succ+subRan_succ + //adj = CTB-exp--> new signature = RT -adj + if ( BI->isUnconditional() ) { // only one successor + BasicBlock *Succ = BI->getSuccessor(0); + uint64_t SuccExpected = + static_cast(compileTimeSig[Succ] + subRanPrevVals[Succ]); + // adj = expected - current + uint64_t AdjValue = SuccExpected - SourceStatic; + Value *Adj = ConstantInt::get(IntType, AdjValue); + Value *NewSig = B.CreateAdd(Current, Adj, "racfed_newsig"); + B.CreateStore(NewSig, RuntimeSigGV); + #if MARTI_DEBUG + printSig(Md,B, NewSig, "newsig"); + #endif + + return; + } + + if ( BI-> isConditional()) { + BasicBlock *SuccT = BI->getSuccessor(0); + BasicBlock *SuccF = BI->getSuccessor(1); + Instruction *Terminator = BB.getTerminator(); + Value *BrCondition = getCondition(*Terminator); + + // Target T + uint64_t expectedT = + static_cast(compileTimeSig[SuccT] + subRanPrevVals[SuccT]); + uint64_t adj1 = expectedT - SourceStatic; + + // Target F + uint64_t expectedF = + static_cast(compileTimeSig[SuccF] + subRanPrevVals[SuccF]); + uint64_t adj2 = expectedF - SourceStatic; + + Value *Adj = B.CreateSelect(BrCondition, ConstantInt::get(IntType, adj1), ConstantInt::get(IntType, adj2)); + Value *NewSig = B.CreateAdd(Current, Adj, "racfed_newsig"); + B.CreateStore(NewSig, RuntimeSigGV); + + #if MARTI_DEBUG + printSig(Md, B, NewSig, "SIG after cond"); + #endif + } +} + +// --- CHECK ON RETURN --- + +/* + * checkOnReturn + * 14: if Last Instr. is return instr. and NrIntrBB > 1 then + * 15: Calculate needed variables + * 16: return Val ← random number + * 17: adjust Value ← (compileTimeSigBB + SumIntraInstructions) - + * 18: return Val + * 19: Insert signature update before return instr. + * 20: signature ← signature + adjustValue + * 21: if signature != returnVal error() + */ +Instruction *RACFED::checkOnReturn(BasicBlock &BB, + GlobalVariable *RuntimeSigGV, + Type* IntType, BasicBlock &ErrBB, + Value *BckupRunSig) { + + // Uniform distribution for 64 bits numbers. + std::uniform_int_distribution dist64(1, 0xffffffff); + // Constant seed for 64 bits. + // Fixed for reproducibility. + std::mt19937 rng64(0x5EED00); + + Instruction *Term = BB.getTerminator(); + + if ( !isa(Term) ) return nullptr; + + std::vector org_instr; + originalInstruction(BB, org_instr); + // 14: if Last Instr. is return instr. and NrIntrBB > 1 then + if ( org_instr.size() > 1 ) { + + // Splits the BB that contains the return instruction into + // two basic blocks: + // BB will contain the return instruction + // BeforeRetBB will contain all of the instructions before the return one + // + // These two BBs will be linked meaning that BeforeRetBB->successor == BB + BasicBlock *BeforeRetBB = BB.splitBasicBlockBefore(Term); + + // Creating control basic block to insert before + // the return instruction + BasicBlock *ControlBB = BasicBlock::Create( + BB.getContext(), + "RAFCED_ret_verification_BB", + BB.getParent(), + &BB + ); + + // Relinking the basic blocks so that the structure + // results in: BeforeRetBB->ControlBB->BB + BeforeRetBB->getTerminator()->replaceSuccessorWith(&BB, ControlBB); + + // Inserting instructions into ControlBB + IRBuilder<> ControlIR(ControlBB); + // 16: returnVal ← random number + uint64_t random_ret_value = dist64(rng64); + // 17: adjustValue ← (compileTimeSigBB + Sum) - + // 18: returnVal + // + // This is a 64 bit SIGNED integer (cause a subtraction happens + // and it cannot previously be established that it will be positive) + long int adj_value = static_cast(compileTimeSig[&BB]) + + sumIntraInstruction[&BB] - random_ret_value; + + // 19: Insert signature update before return instr. + // 20: signature ← signature + adjustValue // wrong must be subtracted + // 21: if signature != returnVal error() + Value *Sig = ControlIR.CreateLoad(IntType, RuntimeSigGV, true, "checking_sign"); + Value *CmpVal = ControlIR.CreateSub(Sig, llvm::ConstantInt::get(IntType, adj_value), "checking_value"); + Value *CmpSig = ControlIR.CreateCmp(llvm::CmpInst::ICMP_EQ, CmpVal, + llvm::ConstantInt::get(IntType, random_ret_value)); + + ControlIR.CreateCondBr(CmpSig, &BB, &ErrBB); + } + + return Term; +} + +PreservedAnalyses RACFED::run(Module &Md, ModuleAnalysisManager &AM) { + auto *I64 = llvm::Type::getInt64Ty(Md.getContext()); + + // Runtime signature defined as a global variable + GlobalVariable *RuntimeSig = new GlobalVariable( + Md, I64, + /*isConstant=*/false, + GlobalValue::ExternalLinkage, + ConstantInt::get(I64, 0), + "signature" + ); + + createFtFuncs(Md); + getFuncAnnotations(Md, FuncAnnotations); + LinkageMap linkageMap = mapFunctionLinkageNames((Md)); + + for (Function &Fn : Md) { + if (shouldCompile(Fn, FuncAnnotations)) + initializeBlocksSignatures(Fn); + } + + for (Function &Fn: Md) { + if (Fn.isDeclaration() || Fn.empty()) continue; + insertIntraInstructionUpdates(Fn, RuntimeSig, I64); + } + + for(Function &Fn: Md) { + if (!shouldCompile(Fn, FuncAnnotations)) continue; + + #if MARTI_DEBUG + errs() << "Analysing func " << Fn.getName() << "\n"; + #endif + + // Search debug location point + DebugLoc debugLoc; + for (auto &I : Fn.front()) { + if (I.getDebugLoc()) { + debugLoc = I.getDebugLoc(); + break; + } + } + + #if (LOG_COMPILED_FUNCS == 1) + CompiledFuncs.insert(&Fn); + #endif + + assert(!getLinkageName(linkageMap,"SigMismatch_Handler").empty() + && "Function SigMismatch_Handler is missing!"); + + // Create error basic block + BasicBlock *ErrBB = BasicBlock::Create(Fn.getContext(), "ErrBB", &Fn); + // Define instruction call to SigMismatch_Handler + auto CalleeF = ErrBB->getModule()->getOrInsertFunction( + getLinkageName(linkageMap,"SigMismatch_Handler"), + FunctionType::getVoidTy(Md.getContext()) + ); + + IRBuilder<> ErrIR(ErrBB); + // Add call instruction to function SigMismatch_Handler + ErrIR.CreateCall(CalleeF)->setDebugLoc(debugLoc); + ErrIR.CreateUnreachable(); + + // Initialize runtime signature backup + Value *runtime_sign_bkup = nullptr; + // Initialize return instruction: used to reinstate the runtime signature of the callee + Instruction *ret_inst = nullptr; + + for (BasicBlock &BB : Fn) { + // Backup of compile time sign when entering a function + if ( BB.isEntryBlock() ) { + IRBuilder<> InstrIR(&*BB.getFirstInsertionPt()); + runtime_sign_bkup = + InstrIR.CreateLoad(I64, RuntimeSig, true, "backup_run_sig"); + // Set runtime signature to compile time signature + // of the function's entry block. + InstrIR.CreateStore(llvm::ConstantInt::get(I64, compileTimeSig[&BB]), + RuntimeSig); + } + + checkJumpSignature(BB, RuntimeSig, I64, *ErrBB); + ret_inst = checkOnReturn(BB, RuntimeSig, I64, *ErrBB, runtime_sign_bkup); + updateBeforeJump(Md, BB, RuntimeSig, I64); + + // Restore signature on return + if ( ret_inst != nullptr ) { + IRBuilder<> RetInstIR(ret_inst); + RetInstIR.CreateStore(runtime_sign_bkup, RuntimeSig); + } + } + } + + #if (LOG_COMPILED_FUNCS == 1) + persistCompiledFunctions(CompiledFuncs, "compiled_racfed_functions.csv"); + #endif + + // There is nothing that this pass preserved + return PreservedAnalyses::none(); +} + +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK + +llvmGetPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, "RACFED", "v0.1", [](PassBuilder &PB) { + PB.registerPipelineParsingCallback( + [](StringRef Name, ModulePassManager &MPM, + ArrayRef) { + if (Name == "racfed-verify") { + MPM.addPass(RACFED()); + return true; + } + return false; + }); + }}; +} + diff --git a/passes/RASM.cpp b/passes/RASM.cpp index 36e8714..10e9ca4 100644 --- a/passes/RASM.cpp +++ b/passes/RASM.cpp @@ -264,7 +264,7 @@ void RASM::createCFGVerificationBB ( BasicBlock &BB, * 3) more than three successors -> we have a switch: impossible since we lower switches in a previous pass */ int numSuccessors = Terminator->getNumSuccessors(); - if (numSuccessors>1 and Terminator->isExceptionalTerminator()){ + if (numSuccessors>1 and Terminator->isSpecialTerminator()){ numSuccessors=1; } switch (numSuccessors) @@ -348,10 +348,10 @@ PreservedAnalyses RASM::run(Module &Md, ModuleAnalysisManager &AM) { // find the global variables required for the runtime signatures for (GlobalVariable &GV : Md.globals()) { if (!isa(GV) && FuncAnnotations.find(&GV) != FuncAnnotations.end()) { - if ((FuncAnnotations.find(&GV))->second.startswith("runtime_sig")) { + if ((FuncAnnotations.find(&GV))->second.starts_with("runtime_sig")) { RuntimeSig = &GV; } - else if ((FuncAnnotations.find(&GV))->second.startswith("run_adj_sig")) { + else if ((FuncAnnotations.find(&GV))->second.starts_with("run_adj_sig")) { RetSig = &GV; } } diff --git a/passes/Utils/Utils.cpp b/passes/Utils/Utils.cpp index 9584f55..8fea5b8 100644 --- a/passes/Utils/Utils.cpp +++ b/passes/Utils/Utils.cpp @@ -5,10 +5,8 @@ #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instructions.h" -#include "llvm/IR/Metadata.h" #include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" -#include #include #include #include @@ -117,7 +115,7 @@ bool shouldCompile(Function &Fn, !Fn.getName().contains("aspis.syncpt") // Moreover, it does not have to be marked as excluded or to_duplicate && (FuncAnnotations.find(&Fn) == FuncAnnotations.end() || - (!FuncAnnotations.find(&Fn)->second.startswith("exclude") /* && + (!FuncAnnotations.find(&Fn)->second.starts_with("exclude") /* && !FuncAnnotations.find(&Fn)->second.startswith("to_duplicate") */)) // nor it is one of the original functions && OriginalFunctions.find(&Fn) == OriginalFunctions.end(); diff --git a/passes/Utils/Utils.h b/passes/Utils/Utils.h index c34824c..f0c5d37 100644 --- a/passes/Utils/Utils.h +++ b/passes/Utils/Utils.h @@ -1,16 +1,14 @@ #ifndef UTILS_H #define UTILS_H -#include "llvm/ADT/Statistic.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instructions.h" -#include "llvm/IR/Metadata.h" -#include "llvm/Pass.h" -#include "llvm/Support/raw_ostream.h" +#include "llvm/IR/Module.h" #include #include +#include using namespace llvm; using LinkageMap = std::unordered_map>; diff --git a/testing/README.md b/testing/README.md index 98051ab..7858536 100644 --- a/testing/README.md +++ b/testing/README.md @@ -4,12 +4,41 @@ This directory contains utilities and scripts for testing ASPIS. ## Local Testing -For local testing, use the `test.py` script. Configure your tests using `test_config.json`. - +For local testing, use the `test.py` script. Configure your tests using `tests.toml`. Configure the llvm_bin flag in `llvm_bin.toml`. +Then run: ```bash pytest test.py ``` +> To run pytest the modules listed in requirements.txt must be installed. +> To install the modules: +> - directly install them globally with `pip install -r requirements.txt` +> - use a tool like conda +> - setup a python environment `python -m venv env` + +### Writing a configuration file + +Test config files must be `.toml` files with the following structure for each test: + +```toml +[[tests]] +test_name = +source_file = +expected_output = +aspis_options = +``` + +> `` is a relative path from `./tests/` folder + +### Flags + +It is possible to write different configuration test files. + +Additional flags are: +`--suffix ` : Operates the same way as aspis, searching for binaries versions denoted by ``. +`--tests-file ...` : Use the configuration files specified. + + ## Docker Testing You can also test ASPIS using Docker with the `test_docker_pipeline.py` script. This Pytest script uses Docker Compose to manage the container and execute ASPIS. @@ -23,4 +52,4 @@ To set up and run Docker tests: 3. **Run Tests:** Execute the `test_docker_pipeline.py` script: ```bash pytest test_docker_pipeline.py - ``` \ No newline at end of file + ``` diff --git a/testing/config/racfed+eddi.toml b/testing/config/racfed+eddi.toml new file mode 100644 index 0000000..ce3d34e --- /dev/null +++ b/testing/config/racfed+eddi.toml @@ -0,0 +1,108 @@ +[[tests]] +test_name = "racfed_eddi_ml_add" +source_file = "c/multi_instruction/add.c" +expected_output = "30" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_ml_function_call" +source_file = "c/multi_instruction/function.c" +expected_output = "foo() 25" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_preserve_runtime_sig" +source_file = "c/multi_instruction/call_less_two.c" +expected_output = "0" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_ml_if_then_else" +source_file = "c/multi_instruction/if_then_else.c" +expected_output = "1001" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_ml_phi_instruction" +source_file = "c/multi_instruction/phi.c" +expected_output = "SUCCESS" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_function_pointer" +source_file = "c/control_flow/function_pointer.c" +expected_output = "42" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_loop_exit" +source_file = "c/control_flow/loop_exit.c" +expected_output = "2" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_n-branch_eddi" +source_file = "c/control_flow/nested-branch.c" +expected_output = "6" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_s-branch_eddi" +source_file = "c/control_flow/simple-branch.c" +expected_output = "OK" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_s-case_eddi" +source_file = "c/control_flow/switch-case.c" +expected_output = "300" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_data_dep_branches" +source_file = "c/data_duplication_integrity/data_dep_branches.c" +expected_output = "7" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_global_var_across_functions" +source_file = "c/data_duplication_integrity/global_var_across_functions.c" +expected_output = "2" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_misc_data_dup" +source_file = "c/data_duplication_integrity/misc_data_dup.c" +expected_output = "OK" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_volatile_io" +source_file = "c/data_duplication_integrity/volatile_io.c" +expected_output = "42" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_arit_pipeline" +source_file = "c/misc_math/arit_pipeline.c" +expected_output = "3" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_mixed_ops" +source_file = "c/misc_math/mixed_ops.c" +expected_output = "14.5" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_eddi_xor_cypher" +source_file = "c/misc_math/xor_cypher.c" +expected_output = "SUCCESS" +aspis_options = "--eddi --racfed" + +[[tests]] +test_name = "racfed_ml_multi_if_else" +source_file = "c/multi_instruction/multi_if_then_else.c" +expected_output = "r > 200" +aspis_options = "--eddi --racfed" + diff --git a/testing/config/racfed.toml b/testing/config/racfed.toml new file mode 100644 index 0000000..5129d1f --- /dev/null +++ b/testing/config/racfed.toml @@ -0,0 +1,108 @@ +[[tests]] +test_name = "racfed_ml_add" +source_file = "c/multi_instruction/add.c" +expected_output = "30" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_ml_function_call" +source_file = "c/multi_instruction/function.c" +expected_output = "foo() 25" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_preserve_runtime_sig" +source_file = "c/multi_instruction/call_less_two.c" +expected_output = "0" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_ml_if_then_else" +source_file = "c/multi_instruction/if_then_else.c" +expected_output = "1001" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_ml_phi_instruction" +source_file = "c/multi_instruction/phi.c" +expected_output = "SUCCESS" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_function_pointer" +source_file = "c/control_flow/function_pointer.c" +expected_output = "42" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_loop_exit" +source_file = "c/control_flow/loop_exit.c" +expected_output = "2" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_nested-branch" +source_file = "c/control_flow/nested-branch.c" +expected_output = "6" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_simple-branch" +source_file = "c/control_flow/simple-branch.c" +expected_output = "OK" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_switch-case" +source_file = "c/control_flow/switch-case.c" +expected_output = "300" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_data_dep_branches" +source_file = "c/data_duplication_integrity/data_dep_branches.c" +expected_output = "7" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_global_var_across_functions" +source_file = "c/data_duplication_integrity/global_var_across_functions.c" +expected_output = "2" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_misc_data_dup" +source_file = "c/data_duplication_integrity/misc_data_dup.c" +expected_output = "OK" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_volatile_io" +source_file = "c/data_duplication_integrity/volatile_io.c" +expected_output = "42" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_arit_pipeline" +source_file = "c/misc_math/arit_pipeline.c" +expected_output = "3" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_mixed_ops" +source_file = "c/misc_math/mixed_ops.c" +expected_output = "14.5" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_xor_cypher" +source_file = "c/misc_math/xor_cypher.c" +expected_output = "SUCCESS" +aspis_options = "--no-dup --racfed" + +[[tests]] +test_name = "racfed_ml_multi_if_else" +source_file = "c/multi_instruction/multi_if_then_else.c" +expected_output = "r > 200" +aspis_options = "--no-dup --racfed" + diff --git a/testing/conftest.py b/testing/conftest.py index 6efa9df..537be87 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,4 +1,5 @@ import pytest +import os def pytest_addoption(parser): parser.addoption( @@ -7,7 +8,37 @@ def pytest_addoption(parser): default=False, help="Run tests inside a container", ) + parser.addoption( + "--tests-file", + action="store", + nargs="*", + default="config/tests.toml", + help="Path to the configuration file", + ) + parser.addoption( + "--suffix", + action="store", + help="LLVM version suffix", + ) @pytest.fixture(scope="session") def use_container(pytestconfig): - return pytestconfig.getoption("use_container") \ No newline at end of file + return pytestconfig.getoption("use_container") + +# Optional: Add a check to ensure the file exists before any tests start +def pytest_configure(config): + tests_file_paths = config.getoption("--tests-file") + for file_path in tests_file_paths: + if not os.path.exists(file_path): + pytest.exit(f"Config file not found: {file_path}") + +@pytest.fixture(scope="session") +def aspis_addopt(pytestconfig): + addopt = {} + suffix_opt = pytestconfig.getoption("--suffix") + if suffix_opt is None: + addopt["suffix"] = "" + else: + addopt["suffix"] = "--suffix " + suffix_opt + + return addopt["suffix"] diff --git a/testing/test.py b/testing/test.py index 044b744..6c1f571 100644 --- a/testing/test.py +++ b/testing/test.py @@ -16,9 +16,6 @@ def load_config(): assert os.path.exists("../aspis.sh") and "Cannot find aspis.sh, please run the tests from the ASPIS testing directory as `pytest test.py`" with open("config/llvm.toml", "rb") as f: config = tomllib.load(f) - with open("config/tests.toml", "rb") as f: - tests = tomllib.load(f) - config.update(tests) # Merge the dictionaries return config # Utility functions @@ -31,32 +28,46 @@ def run_command(command, cwd=None): def compile_with_aspis(source_file, output_file, options, llvm_bin, build_dir): """Compile a file using ASPIS with specified options.""" - command = f"{ASPIS_SCRIPT} --llvm-bin {llvm_bin} {options} {source_file} -o {output_file} --build-dir ./{build_dir} --verbose" + command = f"{ASPIS_SCRIPT} --llvm-bin {llvm_bin} {options} {source_file} -o {output_file}.out --build-dir ./{build_dir} --verbose" print(command) stdout, stderr, exit_code = run_command(command) if exit_code != 0: - raise RuntimeError(f"[{test_name}] Compilation failed: {stderr}") + raise RuntimeError(f"[{output_file}] Compilation failed: {stderr}") return stdout -def execute_binary(binary_file): +def execute_binary(local_build_dir, test_name): """Execute the compiled binary and return its output.""" + binary_file = f"./{local_build_dir}/{test_name}.out"; stdout, stderr, exit_code = run_command(binary_file) if exit_code != 0: raise RuntimeError(f"[{test_name}] Execution failed: {stderr}") - return stdout + return stdout.strip() + +def pytest_generate_tests(metafunc): + """Custom hook to parametrize tests based on the CLI --tests-file flag.""" + if "test_data" in metafunc.fixturenames: + tests_file_paths = metafunc.config.getoption("--tests-file") + test_list = [] + for file_path in tests_file_paths: + with open(file_path, "rb") as f: + config_data = tomllib.load(f) + # Access the 'tests' list inside the TOML dictionary + test_list.extend(config_data.get("tests", [])) + # Use 'test_name' for better output in the terminal + ids = [t.get("test_name", str(i)) for i, t in enumerate(test_list)] + + metafunc.parametrize("test_data", test_list, ids=ids) # Tests -@pytest.mark.parametrize("test", load_config()["tests"]) -def test_aspis(test, use_container): +def test_aspis(test_data, use_container, aspis_addopt): """Run a single ASPIS test.""" - global test_name config = load_config() llvm_bin = config["llvm_bin"] - test_name = test["test_name"] - source_file = test["source_file"] - aspis_options = test["aspis_options"] - expected_output = test["expected_output"] + test_name = test_data["test_name"] + source_file = test_data["source_file"] + aspis_options = aspis_addopt + " " + test_data["aspis_options"] + expected_output = test_data["expected_output"] # use docker compose rather than ASPIS if --use-container is set if use_container: ASPIS_SCRIPT = f"docker compose -f {DOCKER_COMPOSE_FILE} run --rm aspis_runner" @@ -67,15 +78,13 @@ def test_aspis(test, use_container): docker_build_dir = "./build/test/"+test_name local_build_dir = "./build/test/"+test_name - - output_file = f"{test_name}.out" source_path = os.path.join(TEST_DIR, source_file) # Compile the source file - compile_with_aspis(source_path, output_file, aspis_options, llvm_bin, docker_build_dir) + compile_with_aspis(source_path, test_name, aspis_options, llvm_bin, docker_build_dir) # Execute the binary and check output - result = execute_binary(f"./{local_build_dir}/{output_file}") + result = execute_binary(local_build_dir, test_name) assert result == expected_output, f"Test {test_name} failed: {result}" if __name__ == "__main__": diff --git a/testing/tests/c/multi_instruction/add.c b/testing/tests/c/multi_instruction/add.c new file mode 100644 index 0000000..5e50264 --- /dev/null +++ b/testing/tests/c/multi_instruction/add.c @@ -0,0 +1,12 @@ +// +// Created by Gabriele Santandrea on 24/12/25. +// +#include + +int main() { + int a = 10; + int b = 20; + int c = a + b; + printf("%d\n", c); + return 0; +} diff --git a/testing/tests/c/multi_instruction/call_less_two.c b/testing/tests/c/multi_instruction/call_less_two.c new file mode 100644 index 0000000..f599d37 --- /dev/null +++ b/testing/tests/c/multi_instruction/call_less_two.c @@ -0,0 +1,14 @@ +#include + +int foo() { + return 0; +} + +int main() { + int sasso_carta = 1; + int filippo_congenito = 2; + for(int i = foo(); i < sasso_carta + filippo_congenito; i++) + filippo_congenito--; + printf("%d", filippo_congenito); + return 0; +} diff --git a/testing/tests/c/multi_instruction/function.c b/testing/tests/c/multi_instruction/function.c new file mode 100644 index 0000000..8cc2f9f --- /dev/null +++ b/testing/tests/c/multi_instruction/function.c @@ -0,0 +1,25 @@ +// +// Created by Gabriele Santandrea +// +#include + +int foo(); +void print(int c); + +int main() { + int a = 10; + int b = 20; + int c = foo(); + print(c); + return a > b ? 1 : 0; +} + +int foo() { + int c = 12; + int d = 13; + return c + d; +} + +void print(int c) { + printf("foo() %d\n", c); +} diff --git a/testing/tests/c/multi_instruction/if_then_else.c b/testing/tests/c/multi_instruction/if_then_else.c new file mode 100644 index 0000000..0a9ab1d --- /dev/null +++ b/testing/tests/c/multi_instruction/if_then_else.c @@ -0,0 +1,17 @@ +// +// Created by martina on 28/12/25. +// +#include + + +int main() { + int x = 1000; + x = x+1; + if (x<10) { + x = x*10; + printf("%d\n", x); + } + else + printf("%d\n", x); + return 0; +} diff --git a/testing/tests/c/multi_instruction/multi_if_then_else.c b/testing/tests/c/multi_instruction/multi_if_then_else.c new file mode 100644 index 0000000..038f068 --- /dev/null +++ b/testing/tests/c/multi_instruction/multi_if_then_else.c @@ -0,0 +1,16 @@ +#include +#include +#include + +#define MAX 1024 + +int main() { + srand(time(NULL)); + int r = rand() % MAX + 200; + if ( r > 200 ) printf("r > 200\n"); + else if ( r > 100 ) printf("100 < r < 200\n"); + else if ( r > 50 ) printf("50 < r < 100\n"); + else printf("0 < r < 50 \n"); + + return 0; +} diff --git a/testing/tests/c/multi_instruction/phi.c b/testing/tests/c/multi_instruction/phi.c new file mode 100644 index 0000000..5839bed --- /dev/null +++ b/testing/tests/c/multi_instruction/phi.c @@ -0,0 +1,15 @@ +// +// Created by martina on 07/01/26. +// +#include +#include +#include + +int main() { + srand(time(NULL)); + int y = rand() % 2; + int r = 0; + int a = y || r; + printf("SUCCESS\n"); + return 0; +}