Skip to content

Commit 0dbdbdb

Browse files
joseph-isaacsclaude
andcommitted
fix: add wasmfuzz_malloc/wasmfuzz_free exports and daily cron job
- Add wasmfuzz_malloc and wasmfuzz_free exports that wasmfuzz requires for passing fuzz input data to the WASM binary - Configure daily cron job at 2 AM UTC that runs for 4 hours - Add workflow_dispatch input to configure fuzz duration - Upload corpus and crash artifacts for investigation - Only run full fuzzing on schedule/dispatch, build-only on PRs Signed-off-by: Joe Isaacs <[email protected]> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent e408562 commit 0dbdbdb

File tree

2 files changed

+113
-14
lines changed

2 files changed

+113
-14
lines changed

.github/workflows/wasm-fuzz.yml

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
name: WASM Fuzz Build
1+
name: WASM Fuzz
22

33
on:
44
pull_request:
55
paths:
66
- "fuzz/**"
77
- ".github/workflows/wasm-fuzz.yml"
8-
workflow_dispatch: {}
8+
workflow_dispatch:
9+
inputs:
10+
fuzz_duration:
11+
description: "Fuzzing duration in hours"
12+
required: false
13+
default: "4"
14+
type: string
15+
schedule:
16+
# Run daily at 2 AM UTC
17+
- cron: "0 2 * * *"
918

1019
permissions:
1120
contents: read
@@ -46,10 +55,11 @@ jobs:
4655
- name: Install wabt tools
4756
run: sudo apt-get update && sudo apt-get install -y wabt
4857

49-
- name: Verify LLVMFuzzerTestOneInput export
58+
- name: Verify WASM exports
5059
run: |
51-
wasm-objdump -x target/wasm32-wasip1/release/array_ops_wasm.wasm | grep -E "LLVMFuzzer"
52-
echo "LLVMFuzzerTestOneInput is properly exported"
60+
echo "Checking for required wasmfuzz exports..."
61+
wasm-objdump -x target/wasm32-wasip1/release/array_ops_wasm.wasm | grep -E "(LLVMFuzzerTestOneInput|wasmfuzz_malloc|wasmfuzz_free)"
62+
echo "All required exports present: LLVMFuzzerTestOneInput, wasmfuzz_malloc, wasmfuzz_free"
5363
5464
- name: Setup Wasmtime
5565
uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -69,11 +79,13 @@ jobs:
6979
retention-days: 7
7080

7181
test-wasmfuzz:
72-
name: "Test with wasmfuzz"
82+
name: "Fuzz with wasmfuzz"
7383
runs-on: ubuntu-latest
74-
timeout-minutes: 60
84+
# 4.5 hours to allow 4 hours of fuzzing + setup time
85+
timeout-minutes: 270
7586
needs: build-wasm-fuzz
76-
continue-on-error: true
87+
# Only run full fuzzing on schedule or manual dispatch, skip on PRs
88+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
7789
steps:
7890
- uses: actions/checkout@v6
7991

@@ -95,12 +107,57 @@ jobs:
95107
cargo install --git https://github.com/CISPA-SysSec/wasmfuzz --locked
96108
echo "installed=true" >> $GITHUB_OUTPUT
97109
98-
- name: Run wasmfuzz (brief test)
110+
- name: Determine fuzz duration
111+
id: duration
112+
run: |
113+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
114+
HOURS="${{ github.event.inputs.fuzz_duration }}"
115+
else
116+
# Default to 4 hours for scheduled runs
117+
HOURS="4"
118+
fi
119+
echo "hours=$HOURS" >> $GITHUB_OUTPUT
120+
echo "Fuzzing will run for $HOURS hour(s)"
121+
122+
- name: Run wasmfuzz
123+
if: steps.install-wasmfuzz.outputs.installed == 'true'
124+
run: |
125+
mkdir -p corpus crashes
126+
DURATION="${{ steps.duration.outputs.hours }}h"
127+
echo "Running wasmfuzz for $DURATION"
128+
wasmfuzz fuzz \
129+
--timeout="$DURATION" \
130+
--cores 2 \
131+
--dir corpus/ \
132+
./wasm-fuzzer/array_ops_wasm.wasm || true
133+
echo "wasmfuzz completed"
134+
135+
- name: Check for crashes
99136
if: steps.install-wasmfuzz.outputs.installed == 'true'
100137
run: |
101-
mkdir -p corpus
102-
timeout 60s wasmfuzz fuzz --cores 1 --dir corpus/ ./wasm-fuzzer/array_ops_wasm.wasm || true
103-
echo "wasmfuzz test completed"
138+
if [ -d "crashes" ] && [ "$(ls -A crashes 2>/dev/null)" ]; then
139+
echo "::error::Crashes found during fuzzing!"
140+
ls -la crashes/
141+
exit 1
142+
else
143+
echo "No crashes found"
144+
fi
145+
146+
- name: Upload corpus
147+
if: steps.install-wasmfuzz.outputs.installed == 'true'
148+
uses: actions/upload-artifact@v4
149+
with:
150+
name: fuzz-corpus
151+
path: corpus/
152+
retention-days: 30
153+
154+
- name: Upload crashes (if any)
155+
if: failure() && steps.install-wasmfuzz.outputs.installed == 'true'
156+
uses: actions/upload-artifact@v4
157+
with:
158+
name: fuzz-crashes
159+
path: crashes/
160+
retention-days: 90
104161

105162
- name: Report wasmfuzz status
106163
if: steps.install-wasmfuzz.outputs.installed != 'true'

fuzz/fuzz_targets/array_ops_wasm.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@
33

44
//! WASM-compatible fuzz target for array operations.
55
//!
6-
//! This target is designed to work with wasmfuzz and exports the
7-
//! `LLVMFuzzerTestOneInput` function that wasmfuzz expects.
6+
//! This target is designed to work with wasmfuzz and exports:
7+
//! - `LLVMFuzzerTestOneInput` - the fuzzing entry point
8+
//! - `wasmfuzz_malloc` / `wasmfuzz_free` - memory allocation for wasmfuzz to pass input data
9+
//!
10+
//! wasmfuzz looks for `wasmfuzz_malloc`/`wasmfuzz_free` or `malloc`/`free` exports.
11+
//! We provide `wasmfuzz_malloc`/`wasmfuzz_free` using Rust's global allocator.
812
913
#![allow(clippy::unwrap_used, clippy::result_large_err)]
1014

15+
use std::alloc::Layout;
16+
use std::alloc::alloc;
17+
use std::alloc::dealloc;
18+
1119
use arbitrary::Arbitrary;
1220
use arbitrary::Unstructured;
1321
use vortex_array::Array;
@@ -38,6 +46,40 @@ use vortex_fuzz::error::VortexFuzzResult;
3846
use vortex_fuzz::sort_canonical_array;
3947
use vortex_scalar::Scalar;
4048

49+
/// Allocate memory for wasmfuzz to pass input data.
50+
///
51+
/// wasmfuzz requires a `wasmfuzz_malloc` or `malloc` export to allocate space for fuzz inputs.
52+
///
53+
/// # Safety
54+
///
55+
/// Returns a pointer to allocated memory of at least `size` bytes, or null on failure.
56+
#[unsafe(no_mangle)]
57+
pub unsafe extern "C" fn wasmfuzz_malloc(size: usize) -> *mut u8 {
58+
if size == 0 {
59+
return std::ptr::null_mut();
60+
}
61+
let layout = match Layout::from_size_align(size, 8) {
62+
Ok(layout) => layout,
63+
Err(_) => return std::ptr::null_mut(),
64+
};
65+
unsafe { alloc(layout) }
66+
}
67+
68+
/// Free memory allocated by wasmfuzz_malloc.
69+
///
70+
/// # Safety
71+
///
72+
/// The pointer must have been allocated by wasmfuzz_malloc with the given size.
73+
#[unsafe(no_mangle)]
74+
pub unsafe extern "C" fn wasmfuzz_free(ptr: *mut u8, size: usize) {
75+
if ptr.is_null() || size == 0 {
76+
return;
77+
}
78+
if let Ok(layout) = Layout::from_size_align(size, 8) {
79+
unsafe { dealloc(ptr, layout) };
80+
}
81+
}
82+
4183
/// The entry point for wasmfuzz.
4284
///
4385
/// This function is called by wasmfuzz with fuzzer-generated input data.

0 commit comments

Comments
 (0)