diff --git a/.github/actions/medusa-action/Dockerfile b/.github/actions/medusa-action/Dockerfile new file mode 100644 index 0000000..1440343 --- /dev/null +++ b/.github/actions/medusa-action/Dockerfile @@ -0,0 +1,9 @@ +FROM trailofbits/eth-security-toolbox:latest + +COPY entrypoint.sh /entrypoint.sh +RUN foundryup +USER root +RUN cp -rT /home/ethsec/ /root +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin:/root/.vyper/bin:/root/.foundry/bin:/root/.vyper/bin + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/medusa-action/action.yml b/.github/actions/medusa-action/action.yml new file mode 100644 index 0000000..a9f8c26 --- /dev/null +++ b/.github/actions/medusa-action/action.yml @@ -0,0 +1,49 @@ +name: "medusa-action" + +description: "Run Medusa, the smart contract fuzzer" + +branding: + icon: 'shield' + color: 'red' + +inputs: + timeout: + description: "Time to run the campaign" + required: false + output-file: + description: "Capture medusa's output into this file. The path must be relative to the repository root." + required: false + negate-exit-status: + description: "Apply logical NOT to medusa-test's exit status (for testing the action)" + required: false + medusa-workdir: + description: "Path to run medusa-test from." + required: false + internal-github-workspace: + # Do not set manually. This is a hacky way to pass the host workspace to inside the action + # This is used to improve compatibility when using ignore-compile. + # GitHub rewrites the argument if it is passed directly, to we use toJSON to "transform" + # it and avoid the remapping done by GitHub Actions. + default: ${{ toJSON(github.workspace) }} + +outputs: + output-file: + description: "If produced, the file containing medusa-test's output, relative to the repository root." + value: ${{ steps.medusa.outputs.output-file }} + +runs: + using: "composite" + steps: + - run: | + docker build -t eth-security-toolbox:latest ${{ github.action_path }} + shell: bash + - id: medusa + run: | + # medusa campaign + ${{ github.action_path }}/launch.sh eth-security-toolbox:latest + shell: bash + env: + INPUT_NEGATE-EXIT-STATUS: ${{ inputs.negate-exit-status }} + INPUT_MEDUSA-WORKDIR: ${{ inputs.medusa-workdir }} + INPUT_OUTPUT-FILE: ${{ inputs.output-file }} + INPUT_INTERNAL-GITHUB-WORKSPACE: ${{ inputs.internal-github-workspace }} diff --git a/.github/actions/medusa-action/entrypoint.sh b/.github/actions/medusa-action/entrypoint.sh new file mode 100755 index 0000000..96d667a --- /dev/null +++ b/.github/actions/medusa-action/entrypoint.sh @@ -0,0 +1,67 @@ +#! /bin/bash + +set -eu + +OPTIONS="contract config format corpus-dir test-limit test-mode shrink-limit \ +seq-len contract-addr deployer sender seed crytic-args solc-args" + +SWITCHES="multi-abi" + +# smoelius: `get` works for non-standard variable names like `INPUT_CORPUS-DIR`. +get() { + env | sed -n "s/^$1=\(.*\)/\1/;T;p" +} + +compatibility_link() +{ + HOST_GITHUB_WORKSPACE="$(get INPUT_INTERNAL-GITHUB-WORKSPACE | tr -d \")" + if [[ -d "$GITHUB_WORKSPACE" ]]; then + mkdir -p "$(dirname "$HOST_GITHUB_WORKSPACE")" + ln -s "$GITHUB_WORKSPACE" "$HOST_GITHUB_WORKSPACE" + echo "[-] Applied compatibility link: $HOST_GITHUB_WORKSPACE -> $GITHUB_WORKSPACE" + fi +} + +compatibility_link + +CMD=(medusa fuzz) + +for OPTION in $OPTIONS; do + NAME=INPUT_"${OPTION^^}" + VALUE="$(get "$NAME")" + if [[ -n "$VALUE" ]]; then + CMD+=(--"$OPTION" "$VALUE") + fi +done + +for SWITCH in $SWITCHES; do + NAME=INPUT_"${SWITCH^^}" + VALUE="$(get "$NAME")" + if [[ -n "$VALUE" ]]; then + CMD+=(--"$SWITCH") + fi +done + +echo "medusa version: $(medusa --version)" >&2 +echo "medusa command line: ${CMD[@]}" >&2 +echo 'PATH: ' "$PATH" >&2 +echo 'foundry version: ' "$(forge --version)" >&2 +echo >&2 + +OUTPUT_FILE="$(get 'INPUT_OUTPUT-FILE')" +if [[ -n "$OUTPUT_FILE" ]]; then + echo "::set-output name=output-file::$OUTPUT_FILE" + # tee stdout to $OUTPUT_FILE to capture medusa's output + exec > >(tee "$OUTPUT_FILE") +fi + +WORKDIR="$(get 'INPUT_MEDUSA-WORKDIR')" +if [[ -n "$WORKDIR" ]]; then + cd "$WORKDIR" +fi + +if [[ -n "$(get 'INPUT_NEGATE-EXIT-STATUS')" ]]; then + ! "${CMD[@]}" +else + "${CMD[@]}" +fi diff --git a/.github/actions/medusa-action/launch.sh b/.github/actions/medusa-action/launch.sh new file mode 100755 index 0000000..bb7d9c6 --- /dev/null +++ b/.github/actions/medusa-action/launch.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +IMAGE=$1 +INPUTS=$(env | cut -f1 -d= | grep '^INPUT_') + +CMD=(docker run --rm -v "$PWD:/github/workspace" --workdir /github/workspace -e GITHUB_WORKSPACE=/github/workspace) + +for VARNAME in $INPUTS; do + CMD+=(-e "$VARNAME") +done + +CMD+=("$IMAGE") + +"${CMD[@]}" diff --git a/.github/workflows/medusa.yml b/.github/workflows/medusa.yml new file mode 100644 index 0000000..730072d --- /dev/null +++ b/.github/workflows/medusa.yml @@ -0,0 +1,29 @@ +name: Run medusa +on: + push: + branches: [main, dev] + pull_request: + branches: + - "**" +jobs: + medusa-tests: + name: Medusa Test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: yarn + + - name: Install dependencies + run: yarn --frozen-lockfile --network-concurrency 1 + + - name: Run Medusa + uses: ./.github/actions/medusa-action