microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
9300733f19858fa2444c731bcda2faafddbd097e

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/workflows/fuzz.yml

242lines · modecode

1name: fuzz
2run-name: Fuzz
3env:
4 OWNER_RDPATH: ./source # 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:
9 .github/ISSUE_TEMPLATE/fuzz_bug_report.md
10 # GitHub issue template rel file path.
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 PYTHON_VERSION: "3.11"
15
16on:
17 workflow_dispatch: # Manual runs.
18 push:
19 branches:
20 - main # Development runs against main branch.
21 paths:
22 - "source/compiler/**" # Run if the compiler was changed.
23 - "source/fuzz/**" # Run if the fuzzing infra was changed.
24 - ".github/ISSUE_TEMPLATE/fuzz_bug_report.md"
25 # Run if the GitHub issue template was changed.
26 - ".github/workflows/fuzz.yml" # Run if the workflow itself was changed.
27 - "!source/compiler/qsc_eval/**" # Exclude the qsc_eval dir.
28 - "!source/compiler/qsc_codegen/**" # Exclude the qsc_codegen dir.
29
30jobs:
31 fuzz:
32 name: Fuzzing
33 strategy:
34 fail-fast: false
35 matrix:
36 os:
37 [ubuntu-latest] # Fuzzing is not supported on Win. The macos is temporarily removed
38 # because of low availability.
39 target_name: [qsharp, qasm]
40
41 runs-on: ${{ matrix.os }}
42 permissions:
43 issues: write
44 steps:
45 - name: Install and Configure Tools
46 run: |
47 rustup install nightly # Install nightly toolchain.
48 rustup default nightly # Make nightly toolchain default.
49 cargo install cargo-fuzz # Install cargo-fuzz (fuzzing tool).
50
51 - name: Checkout the Repo
52 uses: actions/checkout@v6
53 with:
54 submodules: "true"
55 - uses: actions/setup-python@v6
56 with:
57 python-version: ${{ env.PYTHON_VERSION }}
58
59 - name: Gather the Seed Inputs
60 if: matrix.target_name == 'qsharp'
61 working-directory: ${{ env.OWNER_RDPATH }}
62 run: |
63 # Clone the submodules of QDK:
64 REPOS="Quantum Quantum-NC QuantumKatas QuantumLibraries iqsharp qdk-python qsharp-compiler qsharp-runtime"
65 for REPO in $REPOS ; do
66 git clone --depth 1 --single-branch --no-tags --recurse-submodules --shallow-submodules --jobs 4 \
67 https://github.com/microsoft/$REPO.git $SEEDS_RDPATH/${{ matrix.target_name }}/$REPO
68 done
69
70 # Build a comma-separated list of all the .qs files in $SEEDS_FNAME file:
71 find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.qs" | tr "\n" "," > \
72 $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME
73
74 - name: Gather the Seed Inputs (qasm)
75 if: matrix.target_name == 'qasm'
76 working-directory: ${{ env.OWNER_RDPATH }}
77 run: |
78 # Clone openqasm repo for samples:
79 git clone --depth 1 --single-branch --no-tags --recurse-submodules --shallow-submodules --jobs 4 \
80 https://github.com/openqasm/openqasm.git $SEEDS_RDPATH/${{ matrix.target_name }}/openqasm
81
82
83 # Build a comma-separated list of all the .qasm and .inc files in $SEEDS_FNAME file:
84 find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.qasm" | tr "\n" "," > \
85 $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME
86 find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.inc" | tr "\n" "," > \
87 $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME
88
89 - name: Build and Run the Fuzz Target
90 working-directory: ${{ env.OWNER_RDPATH }}
91 run: |
92 cargo fuzz build --fuzz-dir ./fuzz --release --sanitizer=none --features do_fuzz ${{ matrix.target_name }} # Build the fuzz target.
93
94 # Run fuzzing for specified number of seconds and redirect the `stderr` to a file
95 # whose name is specified by the STDERR_LOG_FNAME env var:
96 RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir ./fuzz --release --sanitizer=none --features do_fuzz ${{ matrix.target_name }} -- \
97 -seed_inputs=@$SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME \
98 -max_total_time=$DURATION_SEC \
99 -rss_limit_mb=4096 \
100 -max_len=20000 \
101 2>$STDERR_LOG_FNAME
102 # The `-rss_limit_mb` and `-max_len` work around running out of memory.
103
104 - name: "If Fuzzing Failed: Collect Failure Info"
105 if: failure()
106 working-directory: ${{ env.OWNER_RDPATH }}
107 run: |
108 # Extract from stderr log the panic message:
109 PANIC_MESSAGE=`cat $STDERR_LOG_FNAME |
110 grep "panicked at" | sed "s|thread '<unnamed>' panicked at '\([^']*\).*|\1|"`
111 # Explanation:
112 # `cat $STDERR_LOG_FNAME |`: Display the contents of the stderr log file and pass the contents
113 # to the next command.
114 # `grep "panicked at" |`: Filter out (drop) all the lines except the ones containing "panicked at",
115 # the script expects that there is only one such line, pass that line to the next command. Line example:
116 # thread '<unnamed>' panicked at 'global item should have type', . . ./compiler/qsc_frontend/src/typeck/rules.rs:300:26
117 # `sed "s|thread '<unnamed>' panicked at '\([^']*\).*|\1|"`: `sed` - stream editor.
118 # `s` after quote: search command. After `s` there are two sections, each between a pair of '|'.
119 # First section:
120 # In the incoming stream search for a sequence starting with "thread '<unnamed>' panicked at '"
121 # (sequence from the beginning of the line until after the apostrophe where the panic message starts),
122 # followed by zero or more ('*' after ']') non-apostrophe chars (`[^']`)
123 # and memorize ( `\(`, `\)` ) that sequence of non-apostrophe chars (between apostrophes -
124 # "global item should have type") as a memory item 1;
125 # followed by zero or more ('*' after '.') arbitrary chars ('.') till the end of the line.
126 # Second section (`\1`):
127 # If the sequence specified by the first section is found, then replace that sequence (the whole line)
128 # with the memory item 1 (`\1`), ending up in a panic message between the apostrophes.
129 # PANIC_MESSAGE=`. . .`: The output of the command(s) between the backticks ('`') is saved in the
130 # env var PANIC_MESSAGE.
131 # If the failure is not panic-based then extract any ERROR message(s):
132 if [ "$PANIC_MESSAGE" == "" ]; then
133 PANIC_MESSAGE=`cat $STDERR_LOG_FNAME | grep "ERROR"`
134 fi
135 echo "PANIC_MESSAGE: '$PANIC_MESSAGE'" # Output the PANIC_MESSAGE var value to the log
136 # (optional, for workflow failure analysis and sanity check).
137 echo "PANIC_MESSAGE=$PANIC_MESSAGE" >> "$GITHUB_ENV" # Save the PANIC_MESSAGE var in the env, will be used in
138 # the subsequent `run:` and `uses:` steps.
139
140 # Determine the name of a file containing the input of interest (that triggers the panic/crash):
141 if [ -e $ARTIFACTS_RDPATH/${{ matrix.target_name }}/crash-* ]; then # Panic and Stack Overflow Cases.
142 TO_MINIMIZE_FNAME=crash-*;
143 elif [ -e $ARTIFACTS_RDPATH/${{ matrix.target_name }}/oom-* ]; then # Out-of-Memory Case.
144 TO_MINIMIZE_FNAME=oom-*;
145 else
146 echo -e "File to minimize not found.\nContents of artifacts dir \"$ARTIFACTS_RDPATH/${{ matrix.target_name }}/\":"
147 ls $ARTIFACTS_RDPATH/${{ matrix.target_name }}/
148 fi
149
150 if [ "$TO_MINIMIZE_FNAME" != "" ]; then
151 echo "TO_MINIMIZE_FNAME: $TO_MINIMIZE_FNAME"
152
153 # Minimize the input:
154 ( cargo fuzz --fuzz-dir ./fuzz tmin --release --sanitizer=none --features do_fuzz -r 10000 ${{ matrix.target_name }} $ARTIFACTS_RDPATH/${{ matrix.target_name }}/$TO_MINIMIZE_FNAME 2>&1 ) > \
155 $TMIN_LOG_FNAME || MINIMIZATION_FAILED=1
156
157 # Get the minimized input relative faile path:
158 if [ "$MINIMIZATION_FAILED" == "1" ]; then
159 # Minimization failed, get the latest successful minimized input relative faile path:
160 MINIMIZED_INPUT_RFPATH=`
161 cat $TMIN_LOG_FNAME | grep "CRASH_MIN: minimizing crash input: " | tail -n 1 |
162 sed "s|^.*\($ARTIFACTS_RDPATH/${{ matrix.target_name }}/[^\']*\).*|\1|"`
163 else
164 # Minimization Succeeded, get the reported minimized input relative faile path::
165 MINIMIZED_INPUT_RFPATH=`
166 cat $TMIN_LOG_FNAME | grep "failed to minimize beyond" |
167 sed "s|.*\($ARTIFACTS_RDPATH/${{ matrix.target_name }}/[^ ]*\).*|\1|" `
168 fi
169 echo "MINIMIZED_INPUT_RFPATH: $MINIMIZED_INPUT_RFPATH"
170 echo "MINIMIZED_INPUT_RFPATH=$MINIMIZED_INPUT_RFPATH" >> "$GITHUB_ENV"
171
172 # Extract the minimized input:
173 MINIMIZED_INPUT=`cat $MINIMIZED_INPUT_RFPATH | tr "\n" "\r"`
174 # Display the contents of the minimized input file and replace all the occurrences of '\n' with '\r'
175 # so that the potentially multiline sequence can be "serialized" into the env var,
176 # while preserving the information about the line breaks.
177 else
178 MINIMIZED_INPUT="(Input minimization failed, see the workflow logs and artifacts)"
179 fi
180 echo "MINIMIZED_INPUT: '$MINIMIZED_INPUT'"
181 echo "MINIMIZED_INPUT=$MINIMIZED_INPUT" >> "$GITHUB_ENV"
182
183 # Get the workflow agent system info:
184 WF_AGENT_SYS_INFO="`uname -a`"
185 echo "WF_AGENT_SYS_INFO: $WF_AGENT_SYS_INFO"
186 echo "WF_AGENT_SYS_INFO=$WF_AGENT_SYS_INFO" >> "$GITHUB_ENV"
187 echo "WF_AGENT_OS=${{ matrix.os }}" >> "$GITHUB_ENV"
188
189 # Get the branch info:
190 BRANCH_INFO=`git branch | grep '*'`
191 echo "BRANCH_INFO: '$BRANCH_INFO'"
192 echo "BRANCH_INFO=$BRANCH_INFO" >> "$GITHUB_ENV"
193
194 # Get the commit info:
195 COMMIT_INFO=`git log -1 | tr "\n" "\r"`
196 echo "COMMIT_INFO: '$COMMIT_INFO'"
197 echo "COMMIT_INFO=$COMMIT_INFO" >> "$GITHUB_ENV"
198
199 # Get the last N bytes of the fuzzing stderr log into the env var
200 # (N is such that the subsequent GitHub issue reporting does not overflow):
201 STDERR_LOG=`tail -c 63488 $STDERR_LOG_FNAME | tr "\n" "\r"`
202 echo "STDERR_LOG: '$STDERR_LOG'"
203 echo "STDERR_LOG=$STDERR_LOG" >> "$GITHUB_ENV"
204
205 - name: "If Fuzzing Failed: Upload Failure Artifacts"
206 if: failure()
207 uses: actions/upload-artifact@v6
208 with:
209 name: ${{ matrix.target_name }}-fuzz-failure-artifacts
210 path: |
211 ${{ env.OWNER_RDPATH }}/${{ env.STDERR_LOG_FNAME }}
212 ${{ env.OWNER_RDPATH }}/${{ env.TMIN_LOG_FNAME }}
213 ${{ env.OWNER_RDPATH }}/${{ env.ARTIFACTS_RDPATH }}/${{ matrix.target_name }}/*
214 ${{ env.OWNER_RDPATH }}/${{ env.SEEDS_RDPATH }}/${{ matrix.target_name }}/${{ env.SEEDS_FNAME }}
215 if-no-files-found: error
216
217 - name: "If Fuzzing Failed: Report GutHub Issue"
218 if: failure()
219 id: create-issue
220 env:
221 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
222 WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
223 run: |
224 python - <<'PY'
225 import os, pathlib
226 template = pathlib.Path(os.environ["GH_ISSUE_TEMPLATE_RFPATH"]).read_text()
227 rendered = os.path.expandvars(template)
228 pathlib.Path("/tmp/fuzz_issue.md").write_text(rendered)
229 PY
230
231 url="$(gh issue create \
232 --title "Fuzz failure: ${{ matrix.target_name }} (${{ github.run_id }})" \
233 --body-file /tmp/fuzz_issue.md)"
234 number="$(gh issue view "$url" --json number --jq .number)"
235
236 echo "url=$url" >> "$GITHUB_OUTPUT"
237 echo "number=$number" >> "$GITHUB_OUTPUT"
238
239 - name: "If Fuzzing Failed: Log Issue Info"
240 if: failure()
241 run: |
242 echo "Created issue #${{ steps.create-issue.outputs.number }} ${{ steps.create-issue.outputs.url }}"
243