microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
.github/workflows/fuzz.yml
192lines · modecode
| 1 | name: fuzz |
| 2 | run-name: Fuzz |
| 3 | env: |
| 4 | OWNER_RDPATH: . # Rel path to the dir that contains the fuzzing infra (contains "fuzz" dir). |
| 5 | DURATION_SEC: 7200 # Fuzzing run duration in seconds. |
| 6 | STDERR_LOG_FNAME: fuzz.stderr.log # File name to redirect the fuzzing run's stderr to. |
| 7 | TMIN_LOG_FNAME: fuzz.tmin.log # File name to redirect the fuzzing input minimization log to. |
| 8 | GH_ISSUE_TEMPLATE_RFPATH: .github/ISSUE_TEMPLATE/fuzz_bug_report.md |
| 9 | # GitHub issue template rel file path. |
| 10 | TARGET_NAME: compile # Fuzzing target name. Fuzzes the `compile` func of the Q# compiler. |
| 11 | ARTIFACTS_RDPATH: fuzz/artifacts # Fuzzing artifacts rel dir path. |
| 12 | SEEDS_RDPATH: fuzz/seed_inputs # Fuzzing seed inputs rel dir path. |
| 13 | SEEDS_FNAME: list.txt # Fuzzing seed inputs list file name. |
| 14 | on: |
| 15 | schedule: # Production runs against default branch. |
| 16 | - cron: '24 11 * * 0' # Run after 11:24 UTC (4:24am PDT/3:24am PST) on Sun. |
| 17 | workflow_dispatch: # Manual runs. |
| 18 | |
| 19 | jobs: |
| 20 | fuzz: |
| 21 | name: Fuzzing |
| 22 | strategy: |
| 23 | matrix: |
| 24 | os: [ubuntu-latest] # Fuzzing is not supported on Win. The macos is temporarily removed |
| 25 | # because of low availability. |
| 26 | runs-on: ${{ matrix.os }} |
| 27 | |
| 28 | steps: |
| 29 | - name: Install and Configure Tools |
| 30 | run: | |
| 31 | rustup install nightly # Install nightly toolchain. |
| 32 | rustup default nightly # Make nightly toolchain default. |
| 33 | cargo install cargo-fuzz # Install cargo-fuzz (fuzzing tool). |
| 34 | |
| 35 | - name: Checkout the Repo |
| 36 | uses: actions/checkout@v3 |
| 37 | with: |
| 38 | submodules: "true" |
| 39 | |
| 40 | - name: Gather the Seed Inputs |
| 41 | run: | |
| 42 | cd $OWNER_RDPATH # Enter the dir containing the fuzzing infra. |
| 43 | |
| 44 | # Clone the submodules of QDK: |
| 45 | REPOS="Quantum Quantum-NC QuantumKatas QuantumLibraries iqsharp qdk-python qsharp-compiler qsharp-runtime" |
| 46 | for REPO in $REPOS ; do |
| 47 | git clone --depth 1 --single-branch --no-tags --recurse-submodules --shallow-submodules --jobs 4 \ |
| 48 | https://github.com/microsoft/$REPO.git $SEEDS_RDPATH/$TARGET_NAME/$REPO |
| 49 | done |
| 50 | |
| 51 | # Build a comma-separated list of all the .qs files in $SEEDS_FNAME file: |
| 52 | find $SEEDS_RDPATH/$TARGET_NAME -name "*.qs" | tr "\n" "," > \ |
| 53 | $SEEDS_RDPATH/$TARGET_NAME/$SEEDS_FNAME |
| 54 | |
| 55 | - name: Build and Run the Fuzz Target |
| 56 | run: | |
| 57 | cd $OWNER_RDPATH # Enter the dir containing the fuzzing infra. |
| 58 | cargo fuzz build --features do_fuzz $TARGET_NAME # Build the fuzz target. |
| 59 | |
| 60 | # Run fuzzing for specified number of seconds and redirect the `stderr` to a file |
| 61 | # whose name is specified by the STDERR_LOG_FNAME env var: |
| 62 | RUST_BACKTRACE=1 cargo fuzz run --features do_fuzz $TARGET_NAME -- \ |
| 63 | -seed_inputs=@$SEEDS_RDPATH/$TARGET_NAME/$SEEDS_FNAME \ |
| 64 | -max_total_time=$DURATION_SEC \ |
| 65 | -rss_limit_mb=4096 \ |
| 66 | -max_len=32767 \ |
| 67 | 2>$STDERR_LOG_FNAME |
| 68 | # The `-rss_limit_mb` and `-max_len` work around running out of memory. |
| 69 | |
| 70 | - name: "If Fuzzing Failed: Collect Failure Info" |
| 71 | if: failure() |
| 72 | run: | |
| 73 | cd $OWNER_RDPATH # Enter the dir containing the fuzzing infra. |
| 74 | |
| 75 | # Extract from stderr log the panic message: |
| 76 | PANIC_MESSAGE=`cat $STDERR_LOG_FNAME | |
| 77 | grep "panicked at" | sed "s|thread '<unnamed>' panicked at '\([^']*\).*|\1|"` |
| 78 | # Explanation: |
| 79 | # `cat $STDERR_LOG_FNAME |`: Display the contents of the stderr log file and pass the contents |
| 80 | # to the next command. |
| 81 | # `grep "panicked at" |`: Filter out (drop) all the lines except the ones containing "panicked at", |
| 82 | # the script expects that there is only one such line, pass that line to the next command. Line example: |
| 83 | # thread '<unnamed>' panicked at 'global item should have type', . . ./compiler/qsc_frontend/src/typeck/rules.rs:300:26 |
| 84 | # `sed "s|thread '<unnamed>' panicked at '\([^']*\).*|\1|"`: `sed` - stream editor. |
| 85 | # `s` after quote: search command. After `s` there are two sections, each between a pair of '|'. |
| 86 | # First section: |
| 87 | # In the incoming stream search for a sequence starting with "thread '<unnamed>' panicked at '" |
| 88 | # (sequence from the beginning of the line until after the apostrophe where the panic message starts), |
| 89 | # followed by zero or more ('*' after ']') non-apostrophe chars (`[^']`) |
| 90 | # and memorize ( `\(`, `\)` ) that sequence of non-apostrophe chars (between apostrophes - |
| 91 | # "global item should have type") as a memory item 1; |
| 92 | # followed by zero or more ('*' after '.') arbitrary chars ('.') till the end of the line. |
| 93 | # Second section (`\1`): |
| 94 | # If the sequence specified by the first section is found, then replace that sequence (the whole line) |
| 95 | # with the memory item 1 (`\1`), ending up in a panic message between the apostrophes. |
| 96 | # PANIC_MESSAGE=`. . .`: The output of the command(s) between the backticks ('`') is saved in the |
| 97 | # env var PANIC_MESSAGE. |
| 98 | # If the failure is not panic-based then extract any ERROR message(s): |
| 99 | if [ "$PANIC_MESSAGE" == "" ]; then |
| 100 | PANIC_MESSAGE=`cat $STDERR_LOG_FNAME | grep "ERROR"` |
| 101 | fi |
| 102 | echo "PANIC_MESSAGE: '$PANIC_MESSAGE'" # Output the PANIC_MESSAGE var value to the log |
| 103 | # (optional, for workflow failure analysis and sanity check). |
| 104 | echo "PANIC_MESSAGE=$PANIC_MESSAGE" >> "$GITHUB_ENV" # Save the PANIC_MESSAGE var in the env, will be used in |
| 105 | # the subsequent `run:` and `uses:` steps. |
| 106 | |
| 107 | # Determine the name of a file containing the input of interest (that triggers the panic/crash): |
| 108 | if [ -e $ARTIFACTS_RDPATH/$TARGET_NAME/crash-* ]; then # Panic and Stack Overflow Cases. |
| 109 | TO_MINIMIZE_FNAME=crash-*; |
| 110 | elif [ -e $ARTIFACTS_RDPATH/$TARGET_NAME/oom-* ]; then # Out-of-Memory Case. |
| 111 | TO_MINIMIZE_FNAME=oom-*; |
| 112 | else |
| 113 | echo -e "File to minimize not found.\nContents of artifacts dir \"$ARTIFACTS_RDPATH/$TARGET_NAME/\":" |
| 114 | ls $ARTIFACTS_RDPATH/$TARGET_NAME/ |
| 115 | fi |
| 116 | |
| 117 | if [ "$TO_MINIMIZE_FNAME" != "" ]; then |
| 118 | echo "TO_MINIMIZE_FNAME: $TO_MINIMIZE_FNAME" |
| 119 | |
| 120 | # Minimize the input: |
| 121 | ( cargo fuzz tmin --features do_fuzz -r 10000 $TARGET_NAME $ARTIFACTS_RDPATH/$TARGET_NAME/$TO_MINIMIZE_FNAME 2>&1 ) > \ |
| 122 | $TMIN_LOG_FNAME || MINIMIZATION_FAILED=1 |
| 123 | |
| 124 | # Get the minimized input relative faile path: |
| 125 | if [ "$MINIMIZATION_FAILED" == "1" ]; then |
| 126 | # Minimization failed, get the latest successful minimized input relative faile path: |
| 127 | MINIMIZED_INPUT_RFPATH=` |
| 128 | cat $TMIN_LOG_FNAME | grep "CRASH_MIN: minimizing crash input: " | tail -n 1 | |
| 129 | sed "s|^.*\($ARTIFACTS_RDPATH/$TARGET_NAME/[^\']*\).*|\1|"` |
| 130 | else |
| 131 | # Minimization Succeeded, get the reported minimized input relative faile path:: |
| 132 | MINIMIZED_INPUT_RFPATH=` |
| 133 | cat $TMIN_LOG_FNAME | grep "failed to minimize beyond" | |
| 134 | sed "s|.*\($ARTIFACTS_RDPATH/$TARGET_NAME/[^ ]*\).*|\1|" ` |
| 135 | fi |
| 136 | echo "MINIMIZED_INPUT_RFPATH: $MINIMIZED_INPUT_RFPATH" |
| 137 | echo "MINIMIZED_INPUT_RFPATH=$MINIMIZED_INPUT_RFPATH" >> "$GITHUB_ENV" |
| 138 | |
| 139 | # Extract the minimized input: |
| 140 | MINIMIZED_INPUT=`cat $MINIMIZED_INPUT_RFPATH | tr "\n" "\r"` |
| 141 | # Display the contents of the minimized input file and replace all the occurrences of '\n' with '\r' |
| 142 | # so that the potentially multiline sequence can be "serialized" into the env var, |
| 143 | # while preserving the information about the line breaks. |
| 144 | else |
| 145 | MINIMIZED_INPUT="(Input minimization failed, see the workflow logs and artifacts)" |
| 146 | fi |
| 147 | echo "MINIMIZED_INPUT: '$MINIMIZED_INPUT'" |
| 148 | echo "MINIMIZED_INPUT=$MINIMIZED_INPUT" >> "$GITHUB_ENV" |
| 149 | |
| 150 | # Get the workflow agent system info: |
| 151 | WF_AGENT_SYS_INFO="`uname -a`" |
| 152 | echo "WF_AGENT_SYS_INFO: $WF_AGENT_SYS_INFO" |
| 153 | echo "WF_AGENT_SYS_INFO=$WF_AGENT_SYS_INFO" >> "$GITHUB_ENV" |
| 154 | echo "WF_AGENT_OS=${{ matrix.os }}" >> "$GITHUB_ENV" |
| 155 | |
| 156 | # Get the commit info: |
| 157 | COMMIT_INFO=`git log -1 | tr "\n" "\r"` |
| 158 | echo "COMMIT_INFO: '$COMMIT_INFO'" |
| 159 | echo "COMMIT_INFO=$COMMIT_INFO" >> "$GITHUB_ENV" |
| 160 | |
| 161 | # Get the last N bytes of the fuzzing stderr log into the env var |
| 162 | # (N is such that the subsequent GitHub issue reporting does not overflow): |
| 163 | STDERR_LOG=`tail -c 63488 $STDERR_LOG_FNAME | tr "\n" "\r"` |
| 164 | echo "STDERR_LOG: '$STDERR_LOG'" |
| 165 | echo "STDERR_LOG=$STDERR_LOG" >> "$GITHUB_ENV" |
| 166 | |
| 167 | - name: "If Fuzzing Failed: Upload Failure Artifacts" |
| 168 | if: failure() |
| 169 | uses: actions/upload-artifact@v3 |
| 170 | with: |
| 171 | path: | |
| 172 | ${{ env.OWNER_RDPATH }}/${{ env.STDERR_LOG_FNAME }} |
| 173 | ${{ env.OWNER_RDPATH }}/${{ env.TMIN_LOG_FNAME }} |
| 174 | ${{ env.OWNER_RDPATH }}/${{ env.ARTIFACTS_RDPATH }}/${{ env.TARGET_NAME }}/* |
| 175 | ${{ env.OWNER_RDPATH }}/${{ env.SEEDS_RDPATH }}/${{ env.TARGET_NAME }}/${{ env.SEEDS_FNAME }} |
| 176 | if-no-files-found: error |
| 177 | |
| 178 | - name: "If Fuzzing Failed: Report GutHub Issue" |
| 179 | if: failure() |
| 180 | uses: JasonEtco/create-an-issue@v2 |
| 181 | env: |
| 182 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 183 | WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |
| 184 | with: |
| 185 | filename: ${{ env.GH_ISSUE_TEMPLATE_RFPATH }} |
| 186 | # This issue template file uses a number of env vars collected above. |
| 187 | id: create-issue |
| 188 | |
| 189 | - name: "If Fuzzing Failed: Log Issue Info" |
| 190 | if: failure() |
| 191 | run: | |
| 192 | echo "Created issue #${{ steps.create-issue.outputs.number }} ${{ steps.create-issue.outputs.url }}" |