Skip to content

Tree borrows violation occurs when using zlib backend #392

@icmccorm

Description

@icmccorm

I've been experimenting with a version of Miri that can execute foreign functions by interpreting the LLVM bytecode that is produced during a crate's build process.

Miri found an instance of undefined behavior in flate2-rs at version 1.0.28. There seems to be a tree borrows violation in the constructors for ZlibEncoder and ZlibDecoder. I encountered this error when running several test cases for another crate, twmap, which depends on flate2. Here’s the full error message that occurs when running the test case custom_test_teeworlds07 from that crate

---- Foreign Error Trace ----

@ %75 = load i32, ptr %74, align 8, !dbg !955

libz-sys-1.1.12/src/zlib/deflate.c:1519:22
libz-sys-1.1.12/src/zlib/deflate.c:1941:13
libz-sys-1.1.12/src/zlib/deflate.c:1003:18
flate2-1.0.28/src/ffi/c.rs:316:27: 316:58
-----------------------------

error: Undefined Behavior: read access through <133277> is forbidden
   |
   = note: read access through <133277> is forbidden
   = help: the accessed tag <133277> is a child of the conflicting tag <133273>
   = help: the conflicting tag <133273> has state Disabled which forbids this child read access
help: the accessed tag <133277> was created here
  --> .../twmap/src/compression.rs:31:23
   |
31 |     let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the conflicting tag <133273> was created here, in the initial state Reserved
  --> .../twmap/src/compression.rs:31:23
   |
31 |     let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the conflicting tag <133273> later transitioned to Disabled due to a foreign write access at offsets [0x8..0xc]
  --> .../twmap/compression.rs:32:5
   |
32 |     encoder.write_all(data).unwrap();
   |     ^^^^^^^^^^^^^^^^^^^^^^^
   = help: this transition corresponds to a loss of read and write permissions

It seems like zlib creates a cyclic structure on lines 306-307 in deflate.c within the body of the function deflateInit2_.

int ZEXPORT deflateInit2_(z_streamp strm, int level, int method,
                          int windowBits, int memLevel, int strategy,
                          const char *version, int stream_size) {
    ...
    strm->state = (struct internal_state FAR *)s;
    s->strm = strm;
    ...
}

The parameter strm is a given as a mutable borrow from Rust, on line 277 of c.rs in flate2

fn make(level: Compression, zlib_header: bool, window_bits: u8) -> Self {
    unsafe {
        let mut state = StreamWrapper::default();
        let ret = mz_deflateInit2(
            &mut *state,
            ...
        );
        ...
    }
}

If state is mutated through a Deflate or Inflate object before the pointer stored in strm->state is used, then I think that this pointer would become "Disabled," leading to the invalid read.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions