diff --git a/docker/ledger/Dockerfile b/docker/ledger/Dockerfile index 12f4894b..ef02e5b0 100644 --- a/docker/ledger/Dockerfile +++ b/docker/ledger/Dockerfile @@ -5,8 +5,7 @@ WORKDIR /opt RUN apt-get update && \ apt-get install -y apt-utils && \ apt-get install -y curl && \ - apt-get install -y build-essential=12.8ubuntu1 && \ - apt-get install -y clang-tools + apt-get install -y build-essential=12.8ubuntu1 RUN curl -L -o gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 \ https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 && \ diff --git a/docker/mware/Dockerfile b/docker/mware/Dockerfile index ca9125c5..d37bbfab 100644 --- a/docker/mware/Dockerfile +++ b/docker/mware/Dockerfile @@ -6,7 +6,8 @@ RUN apt-get update && \ apt-get install -y apt-utils vim && \ apt-get install -y build-essential=12.9 && \ apt-get install -y git && \ - apt-get install -y lcov + apt-get install -y lcov && \ + apt-get install -y cppcheck # Python package prerequisites RUN apt-get install -y \ diff --git a/ledger/static-analysis/.gitignore b/ledger/static-analysis/.gitignore new file mode 100644 index 00000000..6caf68af --- /dev/null +++ b/ledger/static-analysis/.gitignore @@ -0,0 +1 @@ +output \ No newline at end of file diff --git a/ledger/static-analysis/gen-report.py b/ledger/static-analysis/gen-report.py new file mode 100644 index 00000000..b5f499a4 --- /dev/null +++ b/ledger/static-analysis/gen-report.py @@ -0,0 +1,120 @@ +from argparse import ArgumentParser +import xml.etree.ElementTree as ET +import os + + +class ErrorRecord: + def __init__(self, id, severity, message, file, line, column): + self.id = id + self.severity = severity + self.message = message + self.file = file + self.line = line + self.column = column + + +def get_error_list(xml_file): + tree = ET.parse(xml_file) + root = tree.getroot() + + error_list = [] + + for error in root.iter('error'): + id = error.get('id') + severity = error.get('severity') + message = error.get('msg') + location = error.find('location') + file = location.get('file') + line = location.get('line') + column = location.get('col') + error_list.append(ErrorRecord(id, severity, message, file, line, column)) + + return error_list + + +def get_report_paths(input_dir): + common_report = os.path.join(input_dir, "common.xml") + signer_report = os.path.join(input_dir, "signer.xml") + ui_report = os.path.join(input_dir, "ui.xml") + tcpsigner_report = os.path.join(input_dir, "tcpsigner.xml") + + if not os.path.isfile(common_report): + raise Exception(f"File not found: {common_report}") + + if not os.path.isfile(signer_report): + raise Exception(f"File not found: {signer_report}") + + if not os.path.isfile(ui_report): + raise Exception(f"File not found: {ui_report}") + + if not os.path.isfile(tcpsigner_report): + raise Exception(f"File not found: {tcpsigner_report}") + + return [common_report, signer_report, ui_report, tcpsigner_report] + + +def generate_report(report, cppcheck_report): + error_list = get_error_list(cppcheck_report) + if len(error_list) == 0: + report.write("### No errors found!\n\n") + else: + report.write(f"### {len(error_list)} errors:\n\n") + report.write("| File | Message | Severity |\n") + report.write("|------|---------|----------|\n") + for error in error_list: + report.write(f"| {error.file}:{error.line} |" + f" {error.message} |" + f" {error.severity} |\n") + + +def main(): + parser = ArgumentParser(description="Generates a report from cppcheck XML outputs") + parser.add_argument( + "-d", + "--inputDir", + dest="input_dir", + help="Directory containing cppcheck XML output files.", + required=True, + ) + parser.add_argument( + "-o", + "--output", + dest="output_path", + help="Output file path (Markdown report).", + required=True, + ) + options = parser.parse_args() + + try: + [common_report, signer_report, + ui_report, tcpsigner_report] = get_report_paths(options.input_dir) + + # If an old report already exists, remove it + if os.path.exists(options.output_path): + os.remove(options.output_path) + + # Create and open report file + with open(options.output_path, "w") as report: + # Write report title + report.write("# rsk-powhsm Static Analysis Report\n\n") + # Write reports for each module + report.write("## Common\n\n") + generate_report(report, common_report) + report.write("****\n") + report.write("## Signer\n\n") + generate_report(report, signer_report) + report.write("****\n") + report.write("## UI\n\n") + generate_report(report, ui_report) + report.write("****\n") + report.write("## TCPSigner\n\n") + generate_report(report, tcpsigner_report) + + print(f"Report generated at {options.output_path}") + except Exception as e: + print(f"Error: {e}") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/ledger/static-analysis/gen-static-analysis b/ledger/static-analysis/gen-static-analysis new file mode 100755 index 00000000..a984b079 --- /dev/null +++ b/ledger/static-analysis/gen-static-analysis @@ -0,0 +1,61 @@ +#!/bin/bash + +if [[ $1 == "exec" ]]; then + BASEDIR=$(realpath $(dirname $0)) + SRCDIR=$(realpath $BASEDIR/../src) + REPOROOT=$(realpath $BASEDIR/../..) + + # Remove any existing static analysis data + rm -rf $BASEDIR/output && mkdir $BASEDIR/output + + CCCHECKER=cppcheck + CCCHECKER_FLAGS="--quiet --enable=warning --inline-suppr --error-exitcode=1 --xml --xml-version=2" + + found_errors=0 + # Run static analysis on ledger common + COMMON_SRC_DIR=$SRCDIR/common/src + find $COMMON_SRC_DIR -name "*.[ch]" | \ + xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/common.xml + if [[ $? -ne 0 ]]; then + found_errors=1 + fi + + # Run static analysis on ledger signer + SIGNER_SRC_DIR=$SRCDIR/signer/src + find $SIGNER_SRC_DIR -name "*.[ch]" | \ + egrep -v "(bigdigits|bigdtypes|keccak256)\.[ch]$" | \ + xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/signer.xml + if [[ $? -ne 0 ]]; then + found_errors=1 + fi + + # Run static analysis on ledger ui + UI_SRC_DIR=$SRCDIR/ui/src + find $UI_SRC_DIR -name "*.[ch]" | \ + xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/ui.xml + if [[ $? -ne 0 ]]; then + found_errors=1 + fi + + # Run static analysis on ledger tcpsigner + TCPSIGNER_SRC_DIR=$SRCDIR/tcpsigner + find $TCPSIGNER_SRC_DIR -name "*.[ch]" | \ + egrep -v "(bigdigits|bigdtypes|cJSON)\.[ch]$" | \ + xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/tcpsigner.xml + if [[ $? -ne 0 ]]; then + found_errors=1 + fi + + # If any of the checks above found an error, we want this script to fail to inform the CI. + err_code=$found_errors +else + # Script directory + REPOROOT=$(realpath $(dirname $0)/../..) + SCRIPT=$(realpath $0 --relative-to=$REPOROOT) + + # Generate coverage report + $REPOROOT/docker/mware/do-notty-nousb /hsm2 "./$SCRIPT exec" + err_code=$? +fi + +exit $err_code diff --git a/static-analysis-signer b/static-analysis-signer deleted file mode 100755 index 2112eab1..00000000 --- a/static-analysis-signer +++ /dev/null @@ -1,34 +0,0 @@ -#! /usr/bin/env bash - -# -# Run static analysis on ledger signer -# - -if [[ $1 == "exec" ]]; then - pushd $(dirname $0) > /dev/null - HSM_ROOT=$(pwd) - popd > /dev/null - - SRC_DIR=$HSM_ROOT/ledger/src/signer - echo Running static analysis on $SRC_DIR... - - CHECKPOINT=e108960a242ad7bd45c21aff9c7ed9c516789e9cffacdd895502727d8f460d2c - TARGET_DIFFICULTY=0x6e - NETWORK=regtest - - BUILD_CMD="make CHECKPOINT=$CHECKPOINT TARGET_DIFFICULTY=$TARGET_DIFFICULTY NETWORK=$NETWORK" - - cd $SRC_DIR - make clean - scan-build --use-cc=/usr/bin/clang --exclude /opt --show-description --status-bugs -o $SRC_DIR/static-analysis $BUILD_CMD -else - # Script directory - pushd $(dirname $0) > /dev/null - REPO_ROOT=$(pwd) - popd > /dev/null - - SCRIPT=$(basename $0) - - $REPO_ROOT/docker/ledger/do-notty /hsm2 "./$SCRIPT exec" -fi - diff --git a/static-analysis-ui b/static-analysis-ui deleted file mode 100755 index 3c272bb3..00000000 --- a/static-analysis-ui +++ /dev/null @@ -1,34 +0,0 @@ -#! /usr/bin/env bash - -# -# Run static analysis on ledger ui -# - -if [[ $1 == "exec" ]]; then - pushd $(dirname $0) > /dev/null - HSM_ROOT=$(pwd) - popd > /dev/null - - SRC_DIR=$HSM_ROOT/ledger/src/ui - echo Running static analysis on $SRC_DIR... - - - SIGNER_HASH=5f25813b2c5db0bb803bccb6b56707dd7c2ec60434e674c6dcc8da8e7121bad2 - SIGNER_ITERATION=1 - SIGNERS_FILE=testing - - BUILD_CMD="make SIGNER_HASH=$SIGNER_HASH SIGNER_ITERATION=$SIGNER_ITERATION SIGNERS_FILE=$SIGNERS_FILE" - - cd $SRC_DIR - make clean - scan-build --use-cc=/usr/bin/clang --exclude /opt --show-description --status-bugs -o $SRC_DIR/static-analysis $BUILD_CMD -else - # Script directory - pushd $(dirname $0) > /dev/null - REPO_ROOT=$(pwd) - popd > /dev/null - - SCRIPT=$(basename $0) - - $REPO_ROOT/docker/ledger/do-notty /hsm2 "./$SCRIPT exec" -fi