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