Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BrotliDecompress panics if output writer stops accepting data #213

Closed
ctz opened this issue May 23, 2024 · 4 comments
Closed

BrotliDecompress panics if output writer stops accepting data #213

ctz opened this issue May 23, 2024 · 4 comments

Comments

@ctz
Copy link

ctz commented May 23, 2024

Reproducer:

use std::io::Cursor;

fn main() {
    // this is a valid compression of [0u8; 2048];
    let compression = [27, 255, 7, 0, 36, 0, 194, 177, 64, 114, 7];
    // output buffer doesn't have enough space
    let mut output_buffer = [0u8; 2047];

    brotli::BrotliDecompress(
        &mut Cursor::new(compression),
        &mut Cursor::new(&mut output_buffer[..]),
    )
    .unwrap_err();
}

This panics for me with:

thread 'main' panicked at /home/jbp/.cargo/registry/src/index.crates.io-6f17d22bba15001f/brotli-decompressor-4.0.0/src/lib.rs:284:13:
assertion `left == right` failed
  left: true
 right: false
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I think there's a confusion there about what io::Write::write is allowed to return -- Ok(0) is valid, and in this case should be be promoted to an error.

ctz added a commit to ctz/rust-brotli-decompressor that referenced this issue May 23, 2024
Two targets to start with:

1) decompression into a growable writer,
2) decompression into a fixed-size buffer,

The latter finds dropbox/rust-brotli#213
quite quickly.
ctz added a commit to ctz/rust-brotli-decompressor that referenced this issue May 23, 2024
Two targets to start with:

1) decompression into a growable writer,
2) decompression into a fixed-size buffer,

The latter finds dropbox/rust-brotli#213
quite quickly.
danielrh pushed a commit to dropbox/rust-brotli-decompressor that referenced this issue May 27, 2024
Two targets to start with:

1) decompression into a growable writer,
2) decompression into a fixed-size buffer,

The latter finds dropbox/rust-brotli#213
quite quickly.
@danielrh
Copy link
Collaborator

Is there an analogous bug in the compressor, or did fixing the decompressor also resolve this issue?

@ctz
Copy link
Author

ctz commented May 28, 2024

Apologies, I put this issue in the wrong repo. Thanks for taking the decompressor fix and making a release.

But for the sake of completeness, here's a test with a similar case on the compressor:

diff --git a/src/enc/test.rs b/src/enc/test.rs
index fff4856..2c56f89 100644
--- a/src/enc/test.rs
+++ b/src/enc/test.rs
@@ -549,6 +549,27 @@ fn test_roundtrip_empty() {
     assert_eq!(output_offset, 0);
     assert_eq!(compressed_offset, compressed.len());
 }
+
+#[cfg(feature="std")]
+#[test]
+fn test_compress_into_short_buffer() {
+    use std::io::{Cursor, Write};
+
+    // this plaintext should compress to 11 bytes
+    let plaintext = [0u8; 2048];
+
+    // but we only provide space for 10
+    let mut output_buffer = [0u8; 10];
+    let mut output_cursor = Cursor::new(&mut output_buffer[..]);
+
+    let mut w = crate::CompressorWriter::new(&mut output_cursor,
+                                         4096, 4, 22);
+    w.write(&plaintext).unwrap_err();
+    w.into_inner();
+
+    println!("{output_buffer:?}");
+}
+
 /*

This test unfortunately does not terminate, as it is making no progress in

pub fn write_all<ErrType, W: CustomWrite<ErrType>>(
writer: &mut W,
mut buf: &[u8],
) -> Result<(), ErrType> {
while !buf.is_empty() {
match writer.write(buf) {
Ok(bytes_written) => buf = &buf[bytes_written..],
Err(e) => return Err(e),
}
}
Ok(())
}

And fixing that seems tricky without a breaking change. Ideally a parameter would be added to this function for the ErrType value to return on seeing Ok(0). For std callers that would be std::io::ErrorKind::WriteZero.into().

In our use case we compress into a Cursor over a Vec, so won't be affected by this. However happy to put together a PR that:

  • documents the shortcoming of pub fn write_all, marks it deprecated, but leaves it as-is
  • introduces pub fn write_all_until_eof (naming up for grabs)
  • moves the internal callers of write_all to write_all_until_eof
  • adds this test

@danielrh
Copy link
Collaborator

danielrh commented May 29, 2024

I would really appreciate that PR...

this might warrant a breaking change in the future, but maybe we could do a non-breaking release first with your PR and then add that WriteZero capability into the next major version on its heels?

@danielrh
Copy link
Collaborator

danielrh commented Oct 2, 2024

I think the recent commit has improved the situation--but you're right it's a breaking change--moved version to 7.0.0

@danielrh danielrh closed this as completed Oct 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants