Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/fuzz_tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ inputs:
fuzz_time:
description: 'Maximum time in seconds to run fuzzing'
required: false
default: '180'
default: '300'
cargo_fuzz_version:
description: 'Version of cargo-fuzz to install'
required: false
Expand All @@ -26,5 +26,5 @@ runs:
- name: Run Fuzz Tests
shell: bash
working-directory: fuzz
run: cargo fuzz run ${{ inputs.fuzz_target }} --release -- -max_total_time=${{ inputs.fuzz_time }}
run: cargo fuzz run ${{ inputs.fuzz_target }} --release -- -max_total_time=${{ inputs.fuzz_time }} -ignore_ooms=1 -rss_limit_mb=0

3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ jobs:
matrix:
fuzz_target:
- fuzz_json_de
# Add more fuzz targets here as needed
- fuzz_cbor_decode
- fuzz_cbor_roundtrip
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ serde = { workspace = true }
serde_json = { workspace = true }
ctor = { version = "0.1.16", optional = true }
paste = "1.0.15"
ciborium = "0.2"
half = "2.0.0"
thiserror = "2.0.18"
zstd = "0.13"

[dev-dependencies]
mockalloc = "0.1.2"
ctor = "0.1.16"
rand = "0.8.4"
zstd = "0.13"

20 changes: 19 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ edition = "2021"
[package.metadata]
cargo-fuzz = true

[lib]
name = "ijson_fuzz"
path = "src/lib.rs"

[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1.3", features = ["derive"] }
serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

[dependencies.ijson]
Expand All @@ -22,3 +26,17 @@ path = "fuzz_targets/fuzz_json_de.rs"
test = false
doc = false
bench = false

[[bin]]
name = "fuzz_cbor_decode"
path = "fuzz_targets/fuzz_cbor_decode.rs"
test = false
doc = false
bench = false

[[bin]]
name = "fuzz_cbor_roundtrip"
path = "fuzz_targets/fuzz_cbor_roundtrip.rs"
test = false
doc = false
bench = false
8 changes: 8 additions & 0 deletions fuzz/fuzz_targets/fuzz_cbor_decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![no_main]

use ijson::cbor::decode;
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
let _ = decode(data);
});
22 changes: 22 additions & 0 deletions fuzz/fuzz_targets/fuzz_cbor_roundtrip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#![no_main]

use ijson::{cbor, IValue};
use ijson_fuzz::JsonValue;
use libfuzzer_sys::fuzz_target;
use serde::Deserialize;

fuzz_target!(|value: JsonValue| {
let json_string = value.to_json_string();
let mut deserializer = serde_json::Deserializer::from_str(&json_string);
let Ok(original) = IValue::deserialize(&mut deserializer) else {
return;
};

let encoded = cbor::encode(&original);
let decoded = cbor::decode(&encoded).expect("encode->decode round-trip must not fail");

assert_eq!(
original, decoded,
"round-trip mismatch for input: {json_string}"
);
});
44 changes: 1 addition & 43 deletions fuzz/fuzz_targets/fuzz_json_de.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,9 @@
#![no_main]

use arbitrary::Arbitrary;
use ijson::IValue;
use ijson_fuzz::JsonValue;
use libfuzzer_sys::fuzz_target;
use serde::Deserialize;
use std::collections::HashMap;

#[derive(Arbitrary, Debug)]
enum JsonValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<JsonValue>),
Object(HashMap<String, JsonValue>),
}

impl JsonValue {
fn to_json_string(&self) -> String {
match self {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => {
if n.is_finite() {
n.to_string()
} else {
"0".to_string()
}
}
JsonValue::String(s) => format!("\"{}\"", s),
JsonValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.to_json_string()).collect();
format!("[{}]", items.join(","))
}
JsonValue::Object(obj) => {
let items: Vec<String> = obj
.iter()
.map(|(k, v)| {
let key = k.clone();
format!("\"{}\":{}", key, v.to_json_string())
})
.collect();
format!("{{{}}}", items.join(","))
}
}
}
}

fuzz_target!(|value: JsonValue| {
let json_string = value.to_json_string();
Expand Down
50 changes: 50 additions & 0 deletions fuzz/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use arbitrary::Arbitrary;
use serde::{Deserialize, Serialize};

#[derive(Debug, Arbitrary, Serialize, Deserialize)]
pub enum JsonValue {
Null,
Bool(bool),
Integer(u64),
Float(f64),
Str(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}

impl JsonValue {
pub fn to_json_string(&self) -> String {
match self {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Integer(n) => n.to_string(),
JsonValue::Float(n) => {
if n.is_finite() {
n.to_string()
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why infinite is converted to 0?

"0".to_string()
}
}
JsonValue::Str(s) => {
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use raw string literals when backslashes are involved (instead of the \\, \" etc.; here and elsewhere). It makes the code more readable .

}
JsonValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.to_json_string()).collect();
format!("[{}]", items.join(","))
}
JsonValue::Object(obj) => {
let items: Vec<String> = obj
.iter()
.map(|(k, v)| {
format!(
"\"{}\":{}",
k.replace('\\', "\\\\").replace('"', "\\\""),
v.to_json_string()
)
})
.collect();
format!("{{{}}}", items.join(","))
}
}
}
}
Loading
Loading