Hi!
We are a team of researchers studying memory safety in Rust. As part of our ongoing research, we tested jzon (version: 0.12.5) and found that the following code snippet is reported as undefined behavior by Miri:
#![feature(allocator_api)]
extern crate alloc;
use jzon::*;
fn main() {
let v1 = 157u16;
let mut v2 = codegen::PrettyGenerator::new(v1);
let v25: &'_ mut codegen::PrettyGenerator = &mut v2;
let v26 = 109u8;
let v27 = <codegen::PrettyGenerator as codegen::Generator>::write_char(v25, v26);
let v31: &'_ mut codegen::PrettyGenerator = &mut v2;
let v32 = 252u8;
let v33 = <codegen::PrettyGenerator as codegen::Generator>::write_char(v31, v32);
let v37 = codegen::PrettyGenerator::consume(v2);
println!("v37: {:?}",v37);
}
Command used:
MIRIFLAGS="-Zmiri-ignore-leaks -Zmiri-tree-borrows" RUSTFLAGS=-Awarnings RUST_BACKTRACE=1 cargo miri run
The corresponding miri report is:
error: Undefined Behavior: entering unreachable code
--> /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/str/validations.rs:48:23
|
48 | let y = unsafe { *bytes.next().unwrap_unchecked() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `core::str::validations::next_code_point::<'_, std::slice::Iter<'_, u8>>` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/str/validations.rs:48:23: 48:54
= note: inside `<std::str::Chars<'_> as std::iter::Iterator>::next` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/str/iter.rs:42:18: 42:49
= note: inside `<str as std::fmt::Debug>::fmt` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:2935:30: 2935:42
= note: inside `<std::string::String as std::fmt::Debug>::fmt` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/string.rs:2693:9: 2693:36
= note: inside `core::fmt::rt::Argument::<'_>::fmt` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/rt.rs:152:76: 152:95
= note: inside `std::fmt::write` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:1684:17: 1686:80
= note: inside `std::io::default_write_fmt::<std::io::StdoutLock<'_>>` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:614:11: 614:40
= note: inside `<std::io::StdoutLock<'_> as std::io::Write>::write_fmt` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:1969:13: 1969:42
= note: inside `<&std::io::Stdout as std::io::Write>::write_fmt` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:834:9: 834:36
= note: inside `<std::io::Stdout as std::io::Write>::write_fmt` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:808:9: 808:33
= note: inside `std::io::stdio::print_to::<std::io::Stdout>` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1164:21: 1164:47
= note: inside `std::io::_print` at /home/rose/.rustup/toolchains/nightly-2025-12-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1275:5: 1275:37
note: inside `main`
--> src/main.rs:14:5
|
14 | println!("v37: {:?}",v37);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
Root-Cause Hypothesis
After analyzing the Miri report and the source code, we assume the UB is rooted in:
- Suspect location: src/codegen.rs:L197-L200) and src/codegen.rs:#L246-L247)
- Invariant being broken: The in-memory generator buffers are assumed to contain valid UTF-8 when converted into String, but the safe byte-oriented write APIs can append arbitrary bytes.
- Causality chain: Safe calls into the generator (
write, write_min, or write_char) append raw bytes to Vec<u8>; consume then uses String::from_utf8_unchecked to turn that buffer into a String. When the case later prints or otherwise formats the returned String, Miri reaches core::str::validations::next_code_point and reports UB because the bytes are not valid UTF-8.
Possible Fix
Validate the buffer before constructing a String, or return Vec<u8>/Result<String, FromUtf8Error> instead of using String::from_utf8_unchecked. The public byte-oriented write methods should either enforce UTF-8 or avoid promising a String result altogether.
We would appreciate it if you could take a look and confirm whether this behavior indicates a real issue, or if it is a false positive / expected limitation of Miri.
Thank you very much for your time and for maintaining this great project!
Hi!
We are a team of researchers studying memory safety in Rust. As part of our ongoing research, we tested jzon (version: 0.12.5) and found that the following code snippet is reported as undefined behavior by Miri:
Command used:
MIRIFLAGS="-Zmiri-ignore-leaks -Zmiri-tree-borrows" RUSTFLAGS=-Awarnings RUST_BACKTRACE=1 cargo miri runThe corresponding miri report is:
Root-Cause Hypothesis
After analyzing the Miri report and the source code, we assume the UB is rooted in:
write,write_min, orwrite_char) append raw bytes toVec<u8>;consumethen usesString::from_utf8_uncheckedto turn that buffer into aString. When the case later prints or otherwise formats the returnedString, Miri reachescore::str::validations::next_code_pointand reports UB because the bytes are not valid UTF-8.Possible Fix
Validate the buffer before constructing a
String, or returnVec<u8>/Result<String, FromUtf8Error>instead of usingString::from_utf8_unchecked. The public byte-oriented write methods should either enforce UTF-8 or avoid promising aStringresult altogether.We would appreciate it if you could take a look and confirm whether this behavior indicates a real issue, or if it is a false positive / expected limitation of Miri.
Thank you very much for your time and for maintaining this great project!