From 83d8b8427fe9335119b61cda0a5544241bc50ef8 Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 13:39:07 +0800 Subject: [PATCH 1/7] refactor: Polish fuzz build time Signed-off-by: Xuanwo --- .github/workflows/fuzz_test.yml | 31 +++- Cargo.lock | 1 + core/fuzz/Cargo.toml | 13 +- core/fuzz/fuzz_range_reader.rs | 252 -------------------------------- core/fuzz/fuzz_reader.rs | 190 +++++++++++++----------- core/fuzz/fuzz_writer.rs | 128 ++++++++-------- core/fuzz/utils.rs | 29 ++-- 7 files changed, 205 insertions(+), 439 deletions(-) delete mode 100644 core/fuzz/fuzz_range_reader.rs diff --git a/.github/workflows/fuzz_test.yml b/.github/workflows/fuzz_test.yml index 1a11e06aa490..b13b12148a0e 100644 --- a/.github/workflows/fuzz_test.yml +++ b/.github/workflows/fuzz_test.yml @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. - name: Fuzz Test on: @@ -39,12 +38,16 @@ concurrency: cancel-in-progress: true jobs: - fuzz-test-build-target: + fuzz-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Rust toolchain uses: ./.github/actions/setup + - name: Install libfuzz + shell: bash + run: sudo apt-get install -y libfuzzer-14-dev + - name: Install cargo fuzz shell: bash run: rustup install nightly && cargo +nightly install cargo-fuzz @@ -52,12 +55,16 @@ jobs: shell: bash working-directory: core/fuzz run: cargo +nightly fuzz build + env: + CUSTOM_LIBFUZZER_PATH: /usr/lib/llvm-14/lib + - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: fuzz_targets path: ./target/x86_64-unknown-linux-gnu/release/fuzz_* - fuzz-test-run-s3: + + fuzz-test-s3: runs-on: ubuntu-latest needs: fuzz-test-build-target services: @@ -71,8 +78,10 @@ jobs: strategy: fail-fast: true matrix: - fuzz-targets: [ fuzz_reader, fuzz_range_reader, fuzz_writer ] + fuzz-targets: [ fuzz_reader, fuzz_writer ] steps: + - name: Install libfuzzer + run: sudo apt-get install -y libfuzzer-14-dev - name: Setup Test Bucket env: AWS_ACCESS_KEY_ID: "minioadmin" @@ -102,14 +111,17 @@ jobs: with: name: crash_s3_${{ matrix.fuzz-targets }}_${{ github.event_name }}_${{ github.run_attempt }}_${{ github.sha }} path: ./crash* - fuzz-test-run-fs: + + fuzz-test-fs: runs-on: ubuntu-latest needs: fuzz-test-build-target strategy: fail-fast: true matrix: - fuzz-targets: [ fuzz_reader, fuzz_range_reader, fuzz_writer ] + fuzz-targets: [ fuzz_reader, fuzz_writer ] steps: + - name: Install libfuzzer + run: sudo apt-get install -y libfuzzer-14-dev - name: Download Fuzz Targets uses: actions/download-artifact@v3 with: @@ -130,14 +142,17 @@ jobs: with: name: crash_fs_${{ matrix.fuzz-targets }}_${{ github.event_name }}_${{ github.run_attempt }}_${{ github.sha }} path: ./crash* - fuzz-test-run-memory: + + fuzz-test-memory: runs-on: ubuntu-latest needs: fuzz-test-build-target strategy: fail-fast: true matrix: - fuzz-targets: [ fuzz_reader, fuzz_range_reader, fuzz_writer ] + fuzz-targets: [ fuzz_reader, fuzz_writer ] steps: + - name: Install libfuzzer + run: sudo apt-get install -y libfuzzer-14-dev - name: Download Fuzz Targets uses: actions/download-artifact@v3 with: diff --git a/Cargo.lock b/Cargo.lock index cb0b29ed6f58..29292833f158 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3194,6 +3194,7 @@ dependencies = [ "dotenvy", "libfuzzer-sys", "opendal", + "rand 0.8.5", "sha2", "tokio", "uuid", diff --git a/core/fuzz/Cargo.toml b/core/fuzz/Cargo.toml index 37184ff5a5d5..8020dfaf49c3 100644 --- a/core/fuzz/Cargo.toml +++ b/core/fuzz/Cargo.toml @@ -31,24 +31,15 @@ bytes = "1.2" dotenvy = "0.15.6" libfuzzer-sys = "0.4" opendal = { path = ".." } +rand = "0.8" sha2 = { version = "0.10.6" } tokio = { version = "1", features = ["full"] } -uuid = { version = "1.3.0", features = ["v4"] } +uuid = { version = "1", features = ["v4"] } [[bin]] -doc = false name = "fuzz_reader" path = "fuzz_reader.rs" -test = false [[bin]] -doc = false name = "fuzz_writer" path = "fuzz_writer.rs" -test = false - -[[bin]] -doc = false -name = "fuzz_range_reader" -path = "fuzz_range_reader.rs" -test = false diff --git a/core/fuzz/fuzz_range_reader.rs b/core/fuzz/fuzz_range_reader.rs deleted file mode 100644 index ba7ade0433a1..000000000000 --- a/core/fuzz/fuzz_range_reader.rs +++ /dev/null @@ -1,252 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -#![no_main] - -use std::io::SeekFrom; - -use bytes::Bytes; -use libfuzzer_sys::arbitrary::Arbitrary; -use libfuzzer_sys::arbitrary::Result; -use libfuzzer_sys::arbitrary::Unstructured; -use libfuzzer_sys::fuzz_target; -use sha2::Digest; -use sha2::Sha256; - -use opendal::raw::oio::ReadExt; -use opendal::Operator; - -mod utils; - -const MAX_DATA_SIZE: usize = 16 * 1024 * 1024; - -#[derive(Debug, Clone)] -enum ReaderAction { - Read { size: usize }, - Seek(SeekFrom), - Next, -} - -#[derive(Debug, Clone)] -struct FuzzInput { - actions: Vec, - data: Vec, - - range: (u64, u64), -} - -impl Arbitrary<'_> for FuzzInput { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { - let data_len = u.int_in_range(1..=MAX_DATA_SIZE)?; - let data: Vec = u.bytes(data_len)?.to_vec(); - - let range_start = u.int_in_range(0..=data_len as u64 - 1)?; - let range_end = u.int_in_range(range_start + 1..=data_len as u64)?; - - let range = (range_start, range_end); - - let mut actions = vec![]; - let mut action_count = u.int_in_range(128..=1024)?; - - while action_count != 0 { - action_count -= 1; - match u.int_in_range(0..=2)? { - 0 => { - let size = u.int_in_range(0..=data_len * 2)?; - actions.push(ReaderAction::Read { size }); - } - 1 => { - let offset: i64 = u.int_in_range(-(data_len as i64)..=(data_len as i64))?; - let seek_from = match u.int_in_range(0..=2)? { - 0 => SeekFrom::Start(offset.unsigned_abs()), - 1 => SeekFrom::End(offset), - _ => SeekFrom::Current(offset), - }; - actions.push(ReaderAction::Seek(seek_from)); - } - _ => actions.push(ReaderAction::Next), - } - } - Ok(FuzzInput { - actions, - data, - range, - }) - } -} - -struct ReaderFuzzerChecker { - data: Vec, - size: usize, - cur: usize, - start: usize, -} - -impl ReaderFuzzerChecker { - fn new(data: Vec, start: usize, end: usize) -> Self { - Self { - size: end - start, - data, - cur: 0, - start, - } - } - - fn check_read(&mut self, n: usize, output: &[u8]) { - if n == 0 { - return; - } - - let current = self.cur + self.start; - let expected = &self.data[current..current + n]; - - // Check the read result - assert_eq!( - format!("{:x}", Sha256::digest(output)), - format!("{:x}", Sha256::digest(expected)), - "check read failed: output bs is different with expected bs", - ); - - // Update the current position - self.cur += n; - } - - fn check_seek(&mut self, seek_from: SeekFrom, output: opendal::Result) { - let expected = match seek_from { - SeekFrom::Start(offset) => offset as i64, - SeekFrom::End(offset) => self.size as i64 + offset, - SeekFrom::Current(offset) => self.cur as i64 + offset, - }; - - if expected < 0 { - assert!(output.is_err(), "check seek failed: seek should fail"); - assert_eq!( - output.unwrap_err().kind(), - opendal::ErrorKind::InvalidInput, - "check seek failed: seek result is different with expected result", - ); - } else { - assert_eq!( - output.unwrap(), - expected as u64, - "check seek failed: seek result is different with expected result", - ); - - self.cur = expected as usize; - } - } - - fn check_next(&mut self, output: Option) { - if let Some(output) = output { - assert!( - self.cur + output.len() <= self.size, - "check next failed: output bs is larger than remaining bs", - ); - - let current = self.cur + self.start; - let expected = &self.data[current..current + output.len()]; - - assert_eq!( - format!("{:x}", Sha256::digest(&output)), - format!("{:x}", Sha256::digest(expected)), - "check next failed: output bs is different with expected bs", - ); - - // update the current position - self.cur += output.len(); - } else { - assert!( - self.cur >= self.size, - "check next failed: output bs is None, we still have bytes to read", - ) - } - } -} - -async fn fuzz_range_reader_process(input: FuzzInput, op: &Operator, name: &str) -> Result<()> { - let path = uuid::Uuid::new_v4().to_string(); - - let mut checker = ReaderFuzzerChecker::new( - input.data.clone(), - input.range.0 as usize, - input.range.1 as usize, - ); - - op.write(&path, input.data) - .await - .unwrap_or_else(|_| panic!("{} write must succeed", name)); - - let mut o = op - .range_reader(&path, input.range.0..input.range.1) - .await - .unwrap_or_else(|_| panic!("{} init reader must succeed", name)); - - for action in input.actions { - match action { - ReaderAction::Read { size } => { - let mut buf = vec![0; size]; - let n = o - .read(&mut buf) - .await - .unwrap_or_else(|_| panic!("{} read must succeed", name)); - checker.check_read(n, &buf[..n]); - } - - ReaderAction::Seek(seek_from) => { - let res = o.seek(seek_from).await; - checker.check_seek(seek_from, res); - } - - ReaderAction::Next => { - let res = o - .next() - .await - .map(|v| v.unwrap_or_else(|_| panic!("{} next should not return error", name))); - checker.check_next(res); - } - } - } - - op.delete(&path) - .await - .unwrap_or_else(|_| panic!("{} delete must succeed", name)); - Ok(()) -} - -fn fuzz_reader(name: &str, op: &Operator, input: FuzzInput) { - let runtime = tokio::runtime::Runtime::new().unwrap(); - - runtime.block_on(async { - fuzz_range_reader_process(input, op, name) - .await - .unwrap_or_else(|_| panic!("{} fuzz range reader must succeed", name)); - }); -} - -fuzz_target!(|input: FuzzInput| { - let _ = dotenvy::dotenv(); - - for service in utils::init_services() { - if service.1.is_none() { - continue; - } - - let op = service.1.unwrap(); - - fuzz_reader(service.0, &op, input.clone()); - } -}); diff --git a/core/fuzz/fuzz_reader.rs b/core/fuzz/fuzz_reader.rs index 8ec87f5912fa..26b6323fc06a 100644 --- a/core/fuzz/fuzz_reader.rs +++ b/core/fuzz/fuzz_reader.rs @@ -21,21 +21,23 @@ use std::io::SeekFrom; use bytes::Bytes; use libfuzzer_sys::arbitrary::Arbitrary; -use libfuzzer_sys::arbitrary::Result; use libfuzzer_sys::arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; +use rand::prelude::*; use sha2::Digest; use sha2::Sha256; use opendal::raw::oio::ReadExt; +use opendal::raw::BytesRange; use opendal::Operator; +use opendal::Result; mod utils; const MAX_DATA_SIZE: usize = 16 * 1024 * 1024; #[derive(Debug, Clone)] -enum ReaderAction { +enum ReadAction { Read { size: usize }, Seek(SeekFrom), Next, @@ -43,58 +45,103 @@ enum ReaderAction { #[derive(Debug, Clone)] struct FuzzInput { - actions: Vec, - data: Vec, + size: usize, + range: BytesRange, + actions: Vec, } impl Arbitrary<'_> for FuzzInput { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { - let data_len = u.int_in_range(1..=MAX_DATA_SIZE)?; - let data: Vec = u.bytes(data_len)?.to_vec(); + fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result { + let total_size = u.int_in_range(1..=MAX_DATA_SIZE)?; + + // TODO: it's valid that size is larger than total_size. + let (offset, size) = match u.int_in_range(0..=3)? { + // Full range + 0 => (None, None), + 1 => { + let offset = u.int_in_range(0..=total_size as u64 - 1)?; + (Some(offset), None) + } + 2 => { + let size = u.int_in_range(1..=total_size as u64)?; + (None, Some(size)) + } + 3 => { + let offset = u.int_in_range(0..=total_size as u64 - 1)?; + let size = u.int_in_range(1..=total_size as u64 - offset)?; + (Some(offset), Some(size)) + } + _ => unreachable!("invalid int generated by arbitrary"), + }; + let range = BytesRange::new(offset, size); + let count = u.int_in_range(128..=1024)?; let mut actions = vec![]; - let mut action_count = u.int_in_range(128..=1024)?; - while action_count != 0 { - action_count -= 1; - match u.int_in_range(0..=2)? { + for _ in 0..count { + let action = match u.int_in_range(0..=4)? { + // Read 0 => { - let size = u.int_in_range(0..=data_len * 2)?; - actions.push(ReaderAction::Read { size }); + let size = u.int_in_range(0..=total_size * 2)?; + ReadAction::Read { size } } - 1 => { - let offset: i64 = u.int_in_range(-(data_len as i64)..=(data_len as i64))?; - let seek_from = match u.int_in_range(0..=2)? { - 0 => SeekFrom::Start(offset.unsigned_abs()), - 1 => SeekFrom::End(offset), - _ => SeekFrom::Current(offset), - }; - actions.push(ReaderAction::Seek(seek_from)); + // Next + 1 => ReadAction::Next, + // Seek Start + 2 => { + // NOTE: seek out of the end of file is valid. + let offset = u.int_in_range(0..=total_size * 2)?; + ReadAction::Seek(SeekFrom::Start(offset as u64)) } - _ => actions.push(ReaderAction::Next), - } + // Seek Current + 3 => { + let offset = u.int_in_range(-(total_size as i64)..=(total_size as i64))?; + ReadAction::Seek(SeekFrom::Current(offset)) + } + // Seek End + 4 => { + let offset = u.int_in_range(-(total_size as i64)..=(total_size as i64))?; + ReadAction::Seek(SeekFrom::End(offset)) + } + _ => unreachable!("invalid int generated by arbitrary"), + }; + + actions.push(action); } - Ok(FuzzInput { actions, data }) + + Ok(FuzzInput { + size: total_size, + range, + actions, + }) } } -struct ReaderFuzzerChecker { - data: Vec, +struct ReadChecker { size: usize, + + data: Bytes, cur: usize, } -impl ReaderFuzzerChecker { - fn new(data: Vec) -> Self { - Self { - size: data.len(), - data, - cur: 0, - } +impl ReadChecker { + fn new(size: usize, range: BytesRange) -> Self { + let mut rng = thread_rng(); + let mut data = vec![0; size]; + rng.fill_bytes(&mut data); + + let data = range.apply_on_bytes(Bytes::from(data)); + + Self { size, data, cur: 0 } } fn check_read(&mut self, n: usize, output: &[u8]) { if n == 0 { + assert_eq!( + output.len(), + 0, + "check read failed: output bs is not empty when read size is 0" + ); return; } @@ -111,7 +158,7 @@ impl ReaderFuzzerChecker { self.cur += n; } - fn check_seek(&mut self, seek_from: SeekFrom, output: opendal::Result) { + fn check_seek(&mut self, seek_from: SeekFrom, output: Result) { let expected = match seek_from { SeekFrom::Start(offset) => offset as i64, SeekFrom::End(offset) => self.size as i64 + offset, @@ -125,16 +172,18 @@ impl ReaderFuzzerChecker { opendal::ErrorKind::InvalidInput, "check seek failed: seek result is different with expected result" ); - } else { - assert_eq!( - output.unwrap(), - expected as u64, - "check seek failed: seek result is different with expected result", - ); - // only update the current position when seek succeed - self.cur = expected as usize; + return; } + + assert_eq!( + output.unwrap(), + expected as u64, + "check seek failed: seek result is different with expected result", + ); + + // only update the current position when seek succeed + self.cur = expected as usize; } fn check_next(&mut self, output: Option) { @@ -164,71 +213,48 @@ impl ReaderFuzzerChecker { } } -async fn fuzz_reader_process(input: FuzzInput, op: &Operator, name: &str) -> Result<()> { +async fn fuzz_reader(op: Operator, input: FuzzInput) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); - let mut checker = ReaderFuzzerChecker::new(input.data.clone()); - op.write(&path, input.data) - .await - .unwrap_or_else(|_| panic!("{} write must succeed", name)); + let mut checker = ReadChecker::new(input.size, input.range); + op.write(&path, checker.data.clone()).await?; - let mut o = op - .reader(&path) - .await - .unwrap_or_else(|_| panic!("{} init reader must succeed", name)); + let mut o = op.range_reader(&path, input.range.to_range()).await?; for action in input.actions { match action { - ReaderAction::Read { size } => { + ReadAction::Read { size } => { let mut buf = vec![0; size]; - let n = o - .read(&mut buf) - .await - .unwrap_or_else(|_| panic!("{} read must succeed", name)); + let n = o.read(&mut buf).await?; checker.check_read(n, &buf[..n]); } - ReaderAction::Seek(seek_from) => { + ReadAction::Seek(seek_from) => { let res = o.seek(seek_from).await; checker.check_seek(seek_from, res); } - ReaderAction::Next => { - let res = o - .next() - .await - .map(|v| v.unwrap_or_else(|_| panic!("{} next should not return error", name))); + ReadAction::Next => { + let res = o.next().await.transpose()?; checker.check_next(res); } } } - op.delete(&path) - .await - .unwrap_or_else(|_| panic!("{} delete must succeed", name)); + op.delete(&path).await?; Ok(()) } -fn fuzz_reader(name: &str, op: &Operator, input: FuzzInput) { - let runtime = tokio::runtime::Runtime::new().unwrap(); - - runtime.block_on(async { - fuzz_reader_process(input, op, name) - .await - .unwrap_or_else(|_| panic!("{} fuzz reader must succeed", name)); - }); -} - fuzz_target!(|input: FuzzInput| { let _ = dotenvy::dotenv(); - for service in utils::init_services() { - if service.1.is_none() { - continue; - } - - let op = service.1.unwrap(); + let runtime = tokio::runtime::Runtime::new().expect("init runtime must succeed"); - fuzz_reader(service.0, &op, input.clone()); + for op in utils::init_services() { + runtime.block_on(async { + fuzz_reader(op, input.clone()) + .await + .unwrap_or_else(|_| panic!("fuzz reader must succeed")); + }) } }); diff --git a/core/fuzz/fuzz_writer.rs b/core/fuzz/fuzz_writer.rs index 261ffedf5020..63c3ebb3a7db 100644 --- a/core/fuzz/fuzz_writer.rs +++ b/core/fuzz/fuzz_writer.rs @@ -17,15 +17,16 @@ #![no_main] -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use libfuzzer_sys::arbitrary::Arbitrary; -use libfuzzer_sys::arbitrary::Result; use libfuzzer_sys::arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; +use rand::prelude::*; use sha2::Digest; use sha2::Sha256; use opendal::Operator; +use opendal::Result; mod utils; @@ -33,7 +34,7 @@ const MAX_DATA_SIZE: usize = 16 * 1024 * 1024; #[derive(Debug, Clone)] enum WriterAction { - Write { data: Bytes }, + Write { size: usize }, } #[derive(Debug, Clone)] @@ -42,40 +43,46 @@ struct FuzzInput { } impl Arbitrary<'_> for FuzzInput { - fn arbitrary(u: &mut Unstructured<'_>) -> Result { + fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result { let mut actions = vec![]; - let mut action_count = u.int_in_range(128..=1024)?; - - while action_count != 0 { - action_count -= 1; - let data_len = u.int_in_range(1..=MAX_DATA_SIZE)?; - let data: Vec = u.bytes(data_len)?.to_vec(); - actions.push(WriterAction::Write { - data: Bytes::from(data), - }); + + let count = u.int_in_range(128..=1024)?; + + for _ in 0..count { + let size = u.int_in_range(1..=MAX_DATA_SIZE)?; + actions.push(WriterAction::Write { size }); } Ok(FuzzInput { actions }) } } -struct WriterFuzzChecker { - data: Vec, +struct WriteChecker { + chunks: Vec, + data: Bytes, } -impl WriterFuzzChecker { - fn new(input: FuzzInput) -> Self { - let mut data = vec![]; +impl WriteChecker { + fn new(size: Vec) -> Self { + let mut rng = thread_rng(); - for action in input.actions { - match action { - WriterAction::Write { data: d } => { - data.extend_from_slice(&d); - } - } + let mut chunks = Vec::with_capacity(size.len()); + + for i in size { + let mut bs = vec![0u8; i]; + rng.fill_bytes(&mut bs); + chunks.push(Bytes::from(bs)); } - WriterFuzzChecker { data } + let data = chunks.iter().fold(BytesMut::new(), |mut acc, x| { + acc.extend_from_slice(x); + acc + }); + + WriteChecker { + chunks, + data: data.freeze(), + } } fn check(&self, actual: &[u8]) { @@ -87,64 +94,45 @@ impl WriterFuzzChecker { } } -async fn fuzz_writer_process(input: FuzzInput, op: &Operator, name: &str) -> Result<()> { +async fn fuzz_writer(op: Operator, input: FuzzInput) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); - let checker = WriterFuzzChecker::new(input.clone()); - - let mut writer = op - .writer(&path) - .await - .unwrap_or_else(|_| panic!("{} create must succeed", name)); - - for action in input.actions { - match action { - WriterAction::Write { data } => { - writer - .write(data) - .await - .unwrap_or_else(|_| panic!("{} write must succeed", name)); - } - } + let total_size = input + .actions + .iter() + .map(|a| match a { + WriterAction::Write { size } => *size, + }) + .collect(); + + let checker = WriteChecker::new(total_size); + + let mut writer = op.writer(&path).await?; + + for chunk in &checker.chunks { + writer.write(chunk.clone()).await?; } - writer - .close() - .await - .unwrap_or_else(|_| panic!("{} close must succeed", name)); - let result = op - .read(&path) - .await - .unwrap_or_else(|_| panic!("{} read must succeed", name)); + writer.close().await?; + + let result = op.read(&path).await?; checker.check(&result); - op.delete(&path) - .await - .unwrap_or_else(|_| panic!("{} delete must succeed", name)); + op.delete(&path).await?; Ok(()) } -fn fuzz_writer(name: &str, op: &Operator, input: FuzzInput) { - let runtime = tokio::runtime::Runtime::new().unwrap(); - - runtime.block_on(async { - fuzz_writer_process(input, op, name) - .await - .unwrap_or_else(|_| panic!("{} fuzz writer must succeed", name)); - }); -} - fuzz_target!(|input: FuzzInput| { let _ = dotenvy::dotenv(); - for service in utils::init_services() { - if service.1.is_none() { - continue; - } - - let op = service.1.unwrap(); + let runtime = tokio::runtime::Runtime::new().expect("init runtime must succeed"); - fuzz_writer(service.0, &op, input.clone()); + for op in utils::init_services() { + runtime.block_on(async { + fuzz_writer(op, input.clone()) + .await + .unwrap_or_else(|_| panic!("fuzz reader must succeed")); + }) } }); diff --git a/core/fuzz/utils.rs b/core/fuzz/utils.rs index 83b708a9cf90..adc301557cc8 100644 --- a/core/fuzz/utils.rs +++ b/core/fuzz/utils.rs @@ -17,17 +17,16 @@ use std::env; -use opendal::services; -use opendal::Builder; use opendal::Operator; +use opendal::Scheme; -fn service() -> Option { - let test_key = format!("opendal_{}_test", B::SCHEME).to_uppercase(); +fn service(scheme: Scheme) -> Option { + let test_key = format!("opendal_{}_test", scheme).to_uppercase(); if env::var(test_key).unwrap_or_default() != "on" { return None; } - let prefix = format!("opendal_{}_", B::SCHEME); + let prefix = format!("opendal_{}_", scheme); let envs = env::vars() .filter_map(move |(k, v)| { k.to_lowercase() @@ -36,17 +35,15 @@ fn service() -> Option { }) .collect(); - Some( - Operator::from_map::(envs) - .unwrap_or_else(|_| panic!("init {} must succeed", B::SCHEME)) - .finish(), - ) + Some(Operator::via_map(scheme, envs).unwrap_or_else(|_| panic!("init {} must succeed", scheme))) } -pub fn init_services() -> Vec<(&'static str, Option)> { - vec![ - ("fs", service::()), - ("memory", service::()), - ("s3", service::()), - ] +pub fn init_services() -> Vec { + let ops = vec![ + service(Scheme::Memory), + service(Scheme::Fs), + service(Scheme::S3), + ]; + + ops.into_iter().flatten().collect() } From f629f75dcf996efa2c94b8d8df899bec43e49815 Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 13:41:45 +0800 Subject: [PATCH 2/7] Fix build Signed-off-by: Xuanwo --- .github/workflows/fuzz_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/fuzz_test.yml b/.github/workflows/fuzz_test.yml index b13b12148a0e..8043521c189b 100644 --- a/.github/workflows/fuzz_test.yml +++ b/.github/workflows/fuzz_test.yml @@ -66,7 +66,7 @@ jobs: fuzz-test-s3: runs-on: ubuntu-latest - needs: fuzz-test-build-target + needs: fuzz-build services: minio: image: wktk/minio-server @@ -114,7 +114,7 @@ jobs: fuzz-test-fs: runs-on: ubuntu-latest - needs: fuzz-test-build-target + needs: fuzz-build strategy: fail-fast: true matrix: @@ -145,7 +145,7 @@ jobs: fuzz-test-memory: runs-on: ubuntu-latest - needs: fuzz-test-build-target + needs: fuzz-build strategy: fail-fast: true matrix: From 52ef9eae9b75aaa1237365b70f576773ed2bdd06 Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 13:50:09 +0800 Subject: [PATCH 3/7] try fix build Signed-off-by: Xuanwo --- .github/workflows/fuzz_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fuzz_test.yml b/.github/workflows/fuzz_test.yml index 8043521c189b..6eb804217f71 100644 --- a/.github/workflows/fuzz_test.yml +++ b/.github/workflows/fuzz_test.yml @@ -56,7 +56,7 @@ jobs: working-directory: core/fuzz run: cargo +nightly fuzz build env: - CUSTOM_LIBFUZZER_PATH: /usr/lib/llvm-14/lib + CUSTOM_LIBFUZZER_PATH: /usr/lib/llvm-14/lib/libFuzzer.a - name: Upload build artifacts uses: actions/upload-artifact@v3 From b41b765d5c30972daff34f4568aa531578ae8572 Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 14:11:33 +0800 Subject: [PATCH 4/7] Don't upload all files Signed-off-by: Xuanwo --- .github/workflows/fuzz_test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fuzz_test.yml b/.github/workflows/fuzz_test.yml index 6eb804217f71..bec92153b333 100644 --- a/.github/workflows/fuzz_test.yml +++ b/.github/workflows/fuzz_test.yml @@ -62,7 +62,9 @@ jobs: uses: actions/upload-artifact@v3 with: name: fuzz_targets - path: ./target/x86_64-unknown-linux-gnu/release/fuzz_* + path: | + ./target/x86_64-unknown-linux-gnu/release/fuzz_reader + ./target/x86_64-unknown-linux-gnu/release/fuzz_writer fuzz-test-s3: runs-on: ubuntu-latest From 9245d67bf7938f7a53fc0c384a07aa2dc75b2fd4 Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 14:54:27 +0800 Subject: [PATCH 5/7] Use bash64 is enough Signed-off-by: Xuanwo --- .github/workflows/fuzz_test.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/fuzz_test.yml b/.github/workflows/fuzz_test.yml index bec92153b333..7feaf3969a32 100644 --- a/.github/workflows/fuzz_test.yml +++ b/.github/workflows/fuzz_test.yml @@ -107,12 +107,6 @@ jobs: OPENDAL_S3_ENDPOINT: "http://127.0.0.1:9000" OPENDAL_S3_ACCESS_KEY_ID: minioadmin OPENDAL_S3_SECRET_ACCESS_KEY: minioadmin - - name: Upload Crash Files - if: ${{ failure() }} - uses: actions/upload-artifact@v3 - with: - name: crash_s3_${{ matrix.fuzz-targets }}_${{ github.event_name }}_${{ github.run_attempt }}_${{ github.sha }} - path: ./crash* fuzz-test-fs: runs-on: ubuntu-latest @@ -138,12 +132,6 @@ jobs: env: OPENDAL_FS_TEST: on OPENDAL_FS_ROOT: ${{ runner.temp }}/ - - name: Upload Crash Files - uses: actions/upload-artifact@v3 - if: ${{ failure() }} - with: - name: crash_fs_${{ matrix.fuzz-targets }}_${{ github.event_name }}_${{ github.run_attempt }}_${{ github.sha }} - path: ./crash* fuzz-test-memory: runs-on: ubuntu-latest @@ -168,9 +156,3 @@ jobs: run: ./target/${{ matrix.fuzz-targets }} -max_total_time=120 env: OPENDAL_MEMORY_TEST: on - - name: Upload Crash Files - if: ${{ failure() }} - uses: actions/upload-artifact@v3 - with: - name: crash_memory_${{ matrix.fuzz-targets }}_${{ github.event_name }}_${{ github.run_attempt }}_${{ github.sha }} - path: ./crash* From a5ca47fa617af21ad9926269260c7b33883521dc Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 15:16:54 +0800 Subject: [PATCH 6/7] Fix fuzz Signed-off-by: Xuanwo --- core/fuzz/fuzz_reader.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/core/fuzz/fuzz_reader.rs b/core/fuzz/fuzz_reader.rs index 26b6323fc06a..64bf69e3b625 100644 --- a/core/fuzz/fuzz_reader.rs +++ b/core/fuzz/fuzz_reader.rs @@ -120,7 +120,11 @@ impl Arbitrary<'_> for FuzzInput { struct ReadChecker { size: usize, - data: Bytes, + /// Raw Data is the data we write to the storage. + raw_data: Bytes, + /// Ranged Data is the data that we read from the storage. + ranged_data: Bytes, + cur: usize, } @@ -130,9 +134,16 @@ impl ReadChecker { let mut data = vec![0; size]; rng.fill_bytes(&mut data); - let data = range.apply_on_bytes(Bytes::from(data)); + let raw_data = Bytes::from(data); + let ranged_data = range.apply_on_bytes(raw_data.clone()); + + Self { + size, + raw_data, + ranged_data, - Self { size, data, cur: 0 } + cur: 0, + } } fn check_read(&mut self, n: usize, output: &[u8]) { @@ -145,7 +156,7 @@ impl ReadChecker { return; } - let expected = &self.data[self.cur..self.cur + n]; + let expected = &self.ranged_data[self.cur..self.cur + n]; // Check the read result assert_eq!( @@ -197,7 +208,7 @@ impl ReadChecker { format!("{:x}", Sha256::digest(&output)), format!( "{:x}", - Sha256::digest(&self.data[self.cur..self.cur + output.len()]) + Sha256::digest(&self.ranged_data[self.cur..self.cur + output.len()]) ), "check next failed: output bs is different with expected bs", ); @@ -217,7 +228,7 @@ async fn fuzz_reader(op: Operator, input: FuzzInput) -> Result<()> { let path = uuid::Uuid::new_v4().to_string(); let mut checker = ReadChecker::new(input.size, input.range); - op.write(&path, checker.data.clone()).await?; + op.write(&path, checker.raw_data.clone()).await?; let mut o = op.range_reader(&path, input.range.to_range()).await?; From a28953e85a8924327c3865111453e0be3cdae56f Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Thu, 27 Jul 2023 15:39:04 +0800 Subject: [PATCH 7/7] fix Signed-off-by: Xuanwo --- core/fuzz/fuzz_reader.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/core/fuzz/fuzz_reader.rs b/core/fuzz/fuzz_reader.rs index 64bf69e3b625..4cd8a68ff03e 100644 --- a/core/fuzz/fuzz_reader.rs +++ b/core/fuzz/fuzz_reader.rs @@ -75,7 +75,7 @@ impl Arbitrary<'_> for FuzzInput { }; let range = BytesRange::new(offset, size); - let count = u.int_in_range(128..=1024)?; + let count = u.int_in_range(1..=1024)?; let mut actions = vec![]; for _ in 0..count { @@ -118,8 +118,6 @@ impl Arbitrary<'_> for FuzzInput { } struct ReadChecker { - size: usize, - /// Raw Data is the data we write to the storage. raw_data: Bytes, /// Ranged Data is the data that we read from the storage. @@ -138,7 +136,6 @@ impl ReadChecker { let ranged_data = range.apply_on_bytes(raw_data.clone()); Self { - size, raw_data, ranged_data, @@ -172,7 +169,7 @@ impl ReadChecker { fn check_seek(&mut self, seek_from: SeekFrom, output: Result) { let expected = match seek_from { SeekFrom::Start(offset) => offset as i64, - SeekFrom::End(offset) => self.size as i64 + offset, + SeekFrom::End(offset) => self.ranged_data.len() as i64 + offset, SeekFrom::Current(offset) => self.cur as i64 + offset, }; @@ -200,7 +197,7 @@ impl ReadChecker { fn check_next(&mut self, output: Option) { if let Some(output) = output { assert!( - self.cur + output.len() <= self.size, + self.cur + output.len() <= self.ranged_data.len(), "check next failed: output bs is larger than remaining bs", ); @@ -217,7 +214,7 @@ impl ReadChecker { self.cur += output.len(); } else { assert!( - self.cur >= self.size, + self.cur >= self.ranged_data.len(), "check next failed: output bs is None, we still have bytes to read", ) }