diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f32e7b73fa..be72bf1fb32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,8 @@ permissions: on: push: branches: [ master ] - pull_request: - branches: [ master ] + # pull_request: + # branches: [ master ] # cancel running workflows when new commits are being pushed in pull requests # but not on the master branch diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index c07b8676f7e..64058f89b8b 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,5 +1,5 @@ name: CIFuzz -on: [pull_request] +on: [] jobs: Fuzzing: runs-on: ubuntu-22.04 diff --git a/.github/workflows/microwalk.yml b/.github/workflows/microwalk.yml new file mode 100644 index 00000000000..57c0088ee4c --- /dev/null +++ b/.github/workflows/microwalk.yml @@ -0,0 +1,42 @@ +name: Build & Analyze with Microwalk + +on: + push: + pull_request: + workflow_dispatch: + +env: + script_directory: src/microwalk + +jobs: + build-analyze: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - name: Setup Build Agent + uses: ./.github/actions/setup-build-agent + with: + target: shared + cache-key: linux-gcc-x86_64-microwalk + + - name: Build Botan + run: python3 ./src/scripts/ci_build.py --cc='gcc' microwalk + + - name: Run Microwalk analysis + id: run_microwalk + uses: microwalk-project/microwalk-pin-action@v1 + with: + script-directory: ${{ env.script_directory }} + + - name: Upload analysis result + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ github.workspace }}/${{ env.script_directory }}/results/report.sarif + checkout_path: ${{ github.workspace }} + + - name: Archive analysis artifacts + uses: actions/upload-artifact@v3 + with: + name: leakage-analysis-results + path: ${{ github.workspace }}/${{ env.script_directory }}/results diff --git a/src/microwalk/analyze.sh b/src/microwalk/analyze.sh new file mode 100644 index 00000000000..f9ecdfd16c4 --- /dev/null +++ b/src/microwalk/analyze.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -e + +thisDir=$(pwd) +repoRootDir=$(realpath $thisDir/../..) +resultsDir=$thisDir/results + +mkdir -p $resultsDir + +reports="" + +for target in $(find . -name "target-*.cpp" -print) +do + targetName=$(basename -- ${target%.*}) + + echo "Running target ${targetName}..." + + export TESTCASE_DIRECTORY=$thisDir/testcases/$targetName + export TARGET_NAME=$targetName + + mkdir -p $WORK_DIR/$targetName/work + mkdir -p $WORK_DIR/$targetName/persist + + cd $MICROWALK_PATH + time dotnet Microwalk.dll $thisDir/config.yml + + cd $CQR_GENERATOR_PATH + reportFile=$resultsDir/report-$targetName.sarif + dotnet CiReportGenerator.dll $WORK_DIR/$targetName/persist/results/call-stacks.json $targetName $reportFile sarif dwarf $thisDir $repoRootDir + + cd $thisDir + cp $WORK_DIR/$targetName/persist/results/call-stacks.txt $resultsDir/call-stacks-$targetName.txt + + reports="${reports} ${reportFile}" + + echo "Running target ${targetName} successful, generated report ${reportFile}" +done + +echo "Merging report files..." +cat $reports | jq -s '.[0].runs[0].results=([.[].runs[0].results]|flatten)|.[0]' > $resultsDir/report.sarif diff --git a/src/microwalk/build.sh b/src/microwalk/build.sh new file mode 100644 index 00000000000..253d62fb000 --- /dev/null +++ b/src/microwalk/build.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +thisDir=$(pwd) +mainDir=$(realpath $thisDir/../..) + +# Build library +pushd $mainDir +# Note: we already build botan before the Microwalk action +dwarfdump -l libbotan-3.so >$thisDir/libbotan-3.so.dwarf +popd + +# Generate MAP file for library +pushd $MAP_GENERATOR_PATH +dotnet MapFileGenerator.dll $mainDir/libbotan-3.so $thisDir/libbotan-3.map +popd + +# Build targets +for target in $(find . -name "target-*.cpp" -print) +do + targetName=$(basename -- ${target%.*}) + + g++ main.cpp $targetName.cpp -g -fno-inline -fno-split-stack -L "$mainDir" -lbotan-3 -I "$mainDir/build/include" -std=c++20 -o $targetName + + pushd $MAP_GENERATOR_PATH + dotnet MapFileGenerator.dll $thisDir/$targetName $thisDir/$targetName.map + popd + + dwarfdump -l $targetName >$targetName.dwarf +done diff --git a/src/microwalk/config.yml b/src/microwalk/config.yml new file mode 100644 index 00000000000..2df1f2615a0 --- /dev/null +++ b/src/microwalk/config.yml @@ -0,0 +1,58 @@ +constants: + TARGET_PATH: $$CONFIG_PATH$$/$$$TARGET_NAME$$$ + LIBRARY_PATH: $$CONFIG_PATH$$/../../ + WORK_DIR: $$$WORK_DIR$$$/$$$TARGET_NAME$$$ +--- + +general: + logger: + log-level: debug + file: $$WORK_DIR$$/work/log.txt + monitor: + enable: true + sample-rate: 50 + +testcase: + module: load + module-options: + input-directory: $$$TESTCASE_DIRECTORY$$$ + +trace: + module: pin + module-options: + output-directory: $$WORK_DIR$$/work/traces + pin-tool-path: $$$PINTOOL$$$ + pin-path: $$$PIN_PATH$$$/pin + wrapper-path: $$TARGET_PATH$$ + environment: + LD_LIBRARY_PATH: $$LIBRARY_PATH$$ + images: + - $$$TARGET_NAME$$$ + - libbotan-3.so + options: + input-buffer-size: 4 + +preprocess: + module: pin + module-options: + output-directory: $$WORK_DIR$$/work/traces + store-traces: true + keep-raw-traces: false + options: + input-buffer-size: 2 + max-parallel-threads: 4 + +analysis: + modules: + - module: control-flow-leakage + module-options: + output-directory: $$WORK_DIR$$/persist/results + map-files: + - $$TARGET_PATH$$.map + - libbotan-3.map + dump-call-tree: false + include-testcases-in-call-stacks: false + + options: + input-buffer-size: 1 + max-parallel-threads: 1 diff --git a/src/microwalk/main.cpp b/src/microwalk/main.cpp new file mode 100644 index 00000000000..6f5f7818204 --- /dev/null +++ b/src/microwalk/main.cpp @@ -0,0 +1,131 @@ +#include "main.h" + +#include +#include +#include +#include +#include +#include + +#include + +// Pin notification functions. +// These functions (and their names) must not be optimized away by the compiler, so Pin can find and instrument them. +// The return values reduce the probability that the compiler uses these function in other places as no-ops (Visual C++ did do this in some experiments). +#pragma optimize("", off) +extern "C" int PinNotifyTestcaseStart(int t) { return t + 42; } +extern "C" int PinNotifyTestcaseEnd() { return 42; } +extern "C" int PinNotifyStackPointer(uint64_t spMin, uint64_t spMax) { return static_cast(spMin + spMax + 42); } +extern "C" int PinNotifyAllocation(uint64_t address, uint64_t size) { return (int)(address + 23 * size); } +#pragma optimize("", on) + +void Microwalk_Test::critical_section_start(int testcaseId) +{ + PinNotifyTestcaseStart(testcaseId); +} + +void Microwalk_Test::critical_section_end() +{ + PinNotifyTestcaseEnd(); +} + +// Reads the stack pointer base value and transmits it to Pin. +void ReadAndSendStackPointer() +{ + // There does not seem to be a reliable way to get the stack size, so we use an estimation + // Compiling with -fno-split-stack may be desired, to avoid surprises during analysis + + // Take the current stack pointer as base value + uintptr_t stackBase; + asm("mov %%rsp, %0" : "=r"(stackBase)); + + // Get full stack size + struct rlimit stackLimit; + if(getrlimit(RLIMIT_STACK, &stackLimit) != 0) + { + char errBuffer[128]; + strerror_r(errno, errBuffer, sizeof(errBuffer)); + fprintf(stderr, "Error reading stack limit: [%d] %s\n", errno, errBuffer); + } + + uint64_t stackMin = reinterpret_cast(stackBase) - reinterpret_cast(stackLimit.rlim_cur); + uint64_t stackMax = (reinterpret_cast(stackBase) + 0x10000) & ~0xFFFFull; // Round to next higher multiple of 64 kB (should be safe on x86 systems) + PinNotifyStackPointer(stackMin, stackMax); +} + +static std::string read_testdata(const std::string& filename) + { + std::vector lines; + std::ifstream infile(filename); + if(infile.good() == false) + { + throw std::runtime_error("Error reading test data from '" + filename + "'"); + } + std::string line; + while(std::getline(infile, line)) + { + if(!line.empty() && line.at(0) != '#') + { + lines.push_back(line); + } + } + if(lines.size() != 1) + { + throw std::runtime_error("Error reading test data from '" + filename + "'. Expected exactly one line."); + } + return lines.at(0); + } + +// Main trace target function. The following actions are performed: +// The current action is read from stdin. +// A line with "t" followed by a numeric ID, and another line with a file path determining a new testcase, that is subsequently loaded and fed into the target function, while calling PinNotifyNextFile() beforehand. +// A line with "e 0" terminates the program. +void TraceFunc() +{ + // First transmit stack pointer information + ReadAndSendStackPointer(); + + PinNotifyAllocation((uint64_t)&errno, 8); + + // Initialize target library + // InitTarget(); + std::unique_ptr test = creat_test(); + + // Run until exit is requested + char inputBuffer[512]; + char errBuffer[128]; + while(true) + { + // Read command and testcase ID (0 for exit command) + char command; + int testcaseId; + fgets(inputBuffer, sizeof(inputBuffer), stdin); + sscanf(inputBuffer, "%c %d", &command, &testcaseId); + + // Exit or process given testcase + if(command == 'e') + break; + if(command == 't') + { + // Read testcase file name + fgets(inputBuffer, sizeof(inputBuffer), stdin); + int inputFileNameLength = strlen(inputBuffer); + if(inputFileNameLength > 0 && inputBuffer[inputFileNameLength - 1] == '\n') + inputBuffer[inputFileNameLength - 1] = '\0'; + + // Load testcase file and run target function + std::string raw_input = read_testdata(inputBuffer); + std::vector input = test->prepare_input(raw_input); + + test->critical_function(testcaseId, input); + } + } +} + +// Wrapper entry point. +int main(int argc, const char** argv) +{ + // Run target function + TraceFunc(); + return 0; +} diff --git a/src/microwalk/main.h b/src/microwalk/main.h new file mode 100644 index 00000000000..74971fab848 --- /dev/null +++ b/src/microwalk/main.h @@ -0,0 +1,44 @@ +#include +#include + +#include +#include +#include + +class Microwalk_Test + { + public: + Microwalk_Test() + { + m_rng = std::make_shared(); + } + + virtual ~Microwalk_Test() = default; + + Microwalk_Test(const Microwalk_Test& other) = delete; + Microwalk_Test(Microwalk_Test&& other) = delete; + Microwalk_Test& operator=(const Microwalk_Test& other) = delete; + Microwalk_Test& operator=(Microwalk_Test&& other) = delete; + + virtual std::vector prepare_input(const std::string& input) + { + return Botan::hex_decode(input); + } + + virtual void critical_function(int testcaseId, const std::vector& input) = 0; + + protected: + Botan::RandomNumberGenerator& timing_test_rng() + { + return (*m_rng); + } + + static void critical_section_start(int testcaseId); + + static void critical_section_end(); + + private: + std::shared_ptr m_rng; + }; + +std::unique_ptr creat_test(); diff --git a/src/microwalk/target-ecc_mul.cpp b/src/microwalk/target-ecc_mul.cpp new file mode 100644 index 00000000000..174892208b1 --- /dev/null +++ b/src/microwalk/target-ecc_mul.cpp @@ -0,0 +1,34 @@ +#include "main.h" + +#include +#include + +class ECC_Mul_Microwalk_Test final : public Microwalk_Test + { + public: + explicit ECC_Mul_Microwalk_Test(const std::string& ecgroup) : + m_group(ecgroup) + {} + + void critical_function(int testcaseId, const std::vector& input) override; + + private: + const Botan::EC_Group m_group; + std::vector m_ws; + }; + +void ECC_Mul_Microwalk_Test::critical_function(int testcaseId, const std::vector& input) + { + const Botan::BigInt k(input.data(), input.size()); + + critical_section_start(testcaseId); + + const Botan::EC_Point k_times_P = m_group.blinded_base_point_multiply(k, timing_test_rng(), m_ws); + + critical_section_end(); + } + +std::unique_ptr creat_test() + { + return std::make_unique("brainpool512r1"); + } diff --git a/src/microwalk/testcases/target-branch-leakage/t0.testcase b/src/microwalk/testcases/target-branch-leakage/t0.testcase new file mode 100644 index 00000000000..ff2f24957ea --- /dev/null +++ b/src/microwalk/testcases/target-branch-leakage/t0.testcase @@ -0,0 +1 @@ +Ÿè‘´ Rû"ň'³OŒ3­7`‚dí”