Repository navigation
Streaming compression and decompression corruption #112
Description
Activity
I have confirmed a similar issue with
Zstd::StreamWriter.When writing greater than or equal to 131072 characters, the Zstd frame breaks.
You can confirm that the magic number28b52ffddisappeared and theUnknown frame descriptorerror as follows.
131072means 128KB (2^17).test.rb#!/usr/bin/env ruby require "zstd-ruby" require "stringio" puts "zstd-ruby: #{Zstd::VERSION}" size = ARGV[0].to_i io = StringIO.new stream = Zstd::StreamWriter.new(io) stream.write("a" * size) stream.finish io.rewind puts io.read.unpack("H*")[0] io.rewind reader = Zstd::StreamReader.new(io) decompressed = reader.read(1024) puts "Decompressed size: #{decompressed.size}" io.close
$ test.rb 131071 zstd-ruby: 1.5.7.0 28b52ffd00585400001061610100faff39c002010000 Decompressed size: 131071
$ test.rb 131072 zstd-ruby: 1.5.7.0 010000 /home/daipom/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/zstd-ruby-1.5.7.0/lib/zstd-ruby/stream_reader.rb:14:in `decompress': decompress error error code: Unknown frame descriptor (RuntimeError) from /home/daipom/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/zstd-ruby-1.5.7.0/lib/zstd-ruby/stream_reader.rb:14:in `read' from test/test.rb:21:in `<main>'
- zstd-ruby: 1.5.7.0
- Ruby 3.2.2
- OS: Ubuntu Jammy
Reacted by Takuro Ashie, Hiroshi Hatake and Spring_MTHi, I wrote a patch for fixing this: #114
And, the @daipom's repro will not hit after applying that patch:
% bundle exec ruby test.rb 131071 zstd-ruby: 2.0.0-preview1 28b52ffd00585400001061610100faff39c002010000 Decompressed size: 131071 % bundle exec ruby test.rb 131072 zstd-ruby: 2.0.0-preview1 28b52ffd00585400001061610100fbff39c002010000 Decompressed size: 131072 % bundle exec ruby test.rb 132033 zstd-ruby: 2.0.0-preview1 28b52ffd00585400001061610100fbff39c0020a1e0061010000 Decompressed size: 132033Reacted by Daijiro Fukuda and Spring_MTThank you for this PR!
I just released v1.5.7.1 and v2.0.0.
https://rubygems.org/gems/zstd-ruby/versions/1.5.7.1
https://rubygems.org/gems/zstd-ruby/versions/2.0.0Thanks for digging into this, everyone! But I think there may still be random corruption issues, so should this be re-opened, @SpringMT?
When I run my script above against either v1.5.7.1 or v2.0.0 I'm still seeing the more random corruption issues present for anything above ~16,000 bytes. It no longer consistently fails above 131,072 bytes like it did before, but I'm still seeing random corruption for things beyond 131,072 bytes (similar to the random corruption that was happening in the 16,000-131,072 range before).
Here are examples from the above script, but when I updated to pin to either v1.5.7.1 or v2.0.0:
=== Zstd::StreamingCompress.new with .compress === Content mismatch for 21548 bytes input Original: 21548 bytes, fc143dab56 checksum Zstd.decompress: 21548 bytes, 85f3326281 checksum zstd cli: 21548 bytes, fc143dab56 checksum Content mismatch for 23034 bytes input Original: 23034 bytes, 7b661dacb7 checksum Zstd.decompress: 23034 bytes, 495ec283f8 checksum zstd cli: 23034 bytes, 7b661dacb7 checksum [...] Content mismatch for 192026 bytes input Original: 192026 bytes, 328f1ca855 checksum Zstd.decompress: 192026 bytes, 8f835d9a05 checksum zstd cli: 192026 bytes, 328f1ca855 checksum Content mismatch for 192555 bytes input Original: 192555 bytes, 276b4ee705 checksum Zstd.decompress: 192555 bytes, f8965acc0f checksum zstd cli: 192555 bytes, 276b4ee705 checksum [...]=== Zstd::StreamingCompress.new with << === Content mismatch for 16538 bytes input Original: 16538 bytes, 7c8853277d checksum Zstd.decompress: 16538 bytes, 0eea7b214f checksum zstd cli: 16538 bytes, 7c8853277d checksum Content mismatch for 17333 bytes input Original: 17333 bytes, c030604ceb checksum Zstd.decompress: 17333 bytes, 290715be91 checksum zstd cli: 17333 bytes, c030604ceb checksum [...] Content mismatch for 255292 bytes input Original: 255292 bytes, c738cf3b8d checksum Zstd.decompress: 255292 bytes, f372def807 checksum zstd cli: 255292 bytes, c738cf3b8d checksum Content mismatch for 255591 bytes input Original: 255591 bytes, 3ee846d5b5 checksum Zstd.decompress: 255591 bytes, 904ddd287b checksum zstd cli: 255591 bytes, 3ee846d5b5 checksumHi I dug this issue more deeply and I found that this glitches are caused by GC compaction and other type of GC-ed pointer glitches.
#116 should fix this checksum glitches.Thanks so much for fixing this issue and releasing the new version so quickly!
The issue in Fluentd has been resolved with 1.5.7.1!I just released v2.0.1 !
In trying to use
Zstd::StreamingCompress.new, I seem to have encountered a couple different data corruption issues. These present themselves in two different ways:Zstd::StreamingCompress.newand use<<or.writeto pass in a string from ~16,000 bytes to 131,072 bytes, it will sometimes randomly generate a resulting compressed string thatZstd.decompresswill generate differing output than the input for (it doesn't fail, but the output is different and corrupt). However, the compressed string is capable of being decompressed successfully by thezstdCLI tool, and in that case it matches the input exactly, so the compression doesn't necessarily seem corrupt, butZstd.decompressitself seems to not be able to handle this string.Zstd::StreamingCompress.newand use<<or.writeto pass in a string of 131,072 bytes or more in length, thenZstd::StreamingCompresswill consistently produce corrupt content that cannot be decompressed (by either the gem or the CLI tool).Zstd::StreamingCompress.newand use.compress(instead of<<or.write) to pass in data, then it only exhibits the first issue above, and not the second issue. So data passed to.compressgreater than ~16,000 bytes will randomly generate corrupt output if passed toZstd.decompress, but it will work via thezstdCLI. However, instead of the second issue above with data greater than 131,072 consistently failing completely, these longer strings will still exhibit the first issue (mismatchedZstd.decompressoutput).I cannot reproduce these issues if I use
Zstd.compress, so this seems specific to the streaming compression.Reproduction script
I've reproduce this with both zstd-ruby 1.5.7.0 and 2.0.0.pre.preview1 on the following platforms:
Here's my attempt at a script to reproduce this if you save the following to
test_zstd.rb. Sorry it's maybe a bit convoluted to test all 3 situations, but more explanation on usage and some abbreviated output below:Reproduction script usage
ruby test_zstd.rb stream_writeto testZstd::StreamingCompress.newwith<<which should exhibit the first issue above randomly for input sizes in the ~16,000-131,072 byte range, and the second issue consistently for inputs greater than or equal to 131,072 bytes.ruby test_zstd.rb stream_compressto testZstd::StreamingCompress.newwith.compresswhich should exhibit the third issue describe above with inputs sizes greater than ~16,000 bytes randomly having issues.ruby test_zstd.rb compressto testZstd.compresswhich generates no errors for me.Reproduction script example output
For
ruby test_zstd.rb stream_writenote that given the Ruby stream compression input, thatzstdCLI actually does produce the same output as the original input, even whenZstd.decompressdoes not (this is what the checksum of the content is in the output for). However, once you get to 131,072 bytes, then all decompression starts to fail completely.For
ruby test_zstd.rb stream_compressnote it still exhibits the first issue, but it behaves the same above 131,072 bytes of input:For
ruby test_zstd.rb compresswhen not using the streaming compressor, it seems like everything works and the tests produce no output of mismatched things: