Skip to content

UB found by miri in DumpGenerator and PrettyGenerator #20

Description

@yilin0518

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions