diff --git a/core/fuzz/Cargo.toml b/core/fuzz/Cargo.toml index a092a9a7f7a7..ecf4cea1c059 100644 --- a/core/fuzz/Cargo.toml +++ b/core/fuzz/Cargo.toml @@ -85,9 +85,19 @@ arbitrary = { version = "1.4.2", features = ["derive"] } libfuzzer-sys = "0.4" log = { workspace = true } logforth = { workspace = true } -opendal = { path = "..", features = ["tests"] } +opendal = { path = "..", features = ["tests", "services-fs"] } uuid = { workspace = true, features = ["v4"] } +[[bin]] +name = "fuzz_from_uri" +path = "fuzz_from_uri.rs" +test = false + +[[bin]] +name = "fuzz_path" +path = "fuzz_path.rs" +test = false + [[bin]] name = "fuzz_reader" path = "fuzz_reader.rs" diff --git a/core/fuzz/fuzz_from_uri.rs b/core/fuzz/fuzz_from_uri.rs new file mode 100644 index 000000000000..1cafd37dcd09 --- /dev/null +++ b/core/fuzz/fuzz_from_uri.rs @@ -0,0 +1,37 @@ +// 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 libfuzzer_sys::arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; +use opendal::Operator; +use opendal::OperatorUri; + +#[derive(Debug, Clone, Arbitrary)] +struct FuzzInput { + uri: String, + options: Vec<(String, String)>, +} + +fuzz_target!(|input: FuzzInput| { + let _ = logforth::starter_log::stderr().try_apply(); + + let _ = OperatorUri::new(&input.uri, input.options.clone()); + let _ = Operator::from_uri(input.uri.as_str()); + let _ = Operator::via_iter(&input.uri, input.options); +}); diff --git a/core/fuzz/fuzz_path.rs b/core/fuzz/fuzz_path.rs new file mode 100644 index 000000000000..0a99f7a29b98 --- /dev/null +++ b/core/fuzz/fuzz_path.rs @@ -0,0 +1,65 @@ +// 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::sync::LazyLock; + +use libfuzzer_sys::arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; +use opendal::Operator; +use opendal::tests::TEST_RUNTIME; +use opendal::tests::init_test_service; + +#[derive(Debug, Clone, Arbitrary)] +struct FuzzInput { + path: String, + target: String, +} + +static OPERATOR: LazyLock = LazyLock::new(|| { + if let Some(op) = init_test_service().expect("operator init must succeed") { + return op; + } + + log::warn!("OPENDAL_TEST is not set; falling back to a temporary fs operator"); + let root = std::env::temp_dir().join(format!("opendal-fuzz-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&root).expect("create fuzz root dir must succeed"); + Operator::new(opendal::services::Fs::default().root(&root.to_string_lossy())) + .expect("operator init must succeed") + .finish() +}); + +async fn fuzz_path(op: Operator, input: FuzzInput) { + let _ = op.write(&input.path, "data".as_bytes()).await; + let _ = op.stat(&input.path).await; + let _ = op.list(&input.path).await; + let _ = op.create_dir(&input.path).await; + let _ = op.copy(&input.path, &input.target).await; + let _ = op.rename(&input.path, &input.target).await; + let _ = op.delete(&input.target).await; + let _ = op.delete(&input.path).await; +} + +fuzz_target!(|input: FuzzInput| { + let _ = logforth::starter_log::stderr().try_apply(); + + let op = OPERATOR.clone(); + TEST_RUNTIME.block_on(async { + fuzz_path(op, input.clone()).await; + }) +}); diff --git a/core/fuzz/fuzz_reader.rs b/core/fuzz/fuzz_reader.rs index f5d9c116e0e0..4ee4fc06e3b2 100644 --- a/core/fuzz/fuzz_reader.rs +++ b/core/fuzz/fuzz_reader.rs @@ -19,6 +19,7 @@ use std::fmt::Debug; use std::fmt::Formatter; +use std::sync::LazyLock; use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::arbitrary::Unstructured; @@ -88,15 +89,26 @@ async fn fuzz_reader(op: Operator, input: FuzzInput) -> Result<()> { Ok(()) } +static OPERATOR: LazyLock = LazyLock::new(|| { + if let Some(op) = init_test_service().expect("operator init must succeed") { + return op; + } + + log::warn!("OPENDAL_TEST is not set; falling back to a temporary fs operator"); + let root = std::env::temp_dir().join(format!("opendal-fuzz-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&root).expect("create fuzz root dir must succeed"); + Operator::new(opendal::services::Fs::default().root(&root.to_string_lossy())) + .expect("operator init must succeed") + .finish() +}); + fuzz_target!(|input: FuzzInput| { let _ = logforth::starter_log::stderr().try_apply(); - let op = init_test_service().expect("operator init must succeed"); - if let Some(op) = op { - TEST_RUNTIME.block_on(async { - fuzz_reader(op, input.clone()) - .await - .unwrap_or_else(|err| panic!("fuzz reader must succeed: {err:?}")); - }) - } + let op = OPERATOR.clone(); + TEST_RUNTIME.block_on(async { + fuzz_reader(op, input.clone()) + .await + .unwrap_or_else(|err| panic!("fuzz reader must succeed: {err:?}")); + }) }); diff --git a/core/fuzz/fuzz_writer.rs b/core/fuzz/fuzz_writer.rs index 266d1ec6b1f1..08712806d1ef 100644 --- a/core/fuzz/fuzz_writer.rs +++ b/core/fuzz/fuzz_writer.rs @@ -17,6 +17,8 @@ #![no_main] +use std::sync::LazyLock; + use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; @@ -104,20 +106,31 @@ async fn fuzz_writer(op: Operator, input: FuzzInput) -> Result<()> { Ok(()) } +static OPERATOR: LazyLock = LazyLock::new(|| { + if let Some(op) = init_test_service().expect("operator init must succeed") { + return op; + } + + log::warn!("OPENDAL_TEST is not set; falling back to a temporary fs operator"); + let root = std::env::temp_dir().join(format!("opendal-fuzz-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&root).expect("create fuzz root dir must succeed"); + Operator::new(opendal::services::Fs::default().root(&root.to_string_lossy())) + .expect("operator init must succeed") + .finish() +}); + fuzz_target!(|input: FuzzInput| { let _ = logforth::starter_log::stderr().try_apply(); - let op = init_test_service().expect("operator init must succeed"); - if let Some(op) = op { - if !op.info().full_capability().write_can_multi { - log::warn!("service doesn't support write multi, skip fuzzing"); - return; - } - - TEST_RUNTIME.block_on(async { - fuzz_writer(op, input.clone()) - .await - .unwrap_or_else(|err| panic!("fuzz reader must succeed: {err:?}")); - }) + let op = OPERATOR.clone(); + if !op.info().full_capability().write_can_multi { + log::warn!("service doesn't support write multi, skip fuzzing"); + return; } + + TEST_RUNTIME.block_on(async { + fuzz_writer(op, input.clone()) + .await + .unwrap_or_else(|err| panic!("fuzz writer must succeed: {err:?}")); + }) });