mirror of
https://github.com/google/brotli.git
synced 2026-07-08 17:56:58 +00:00
Non-compliant decoding according to section 4? #158
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @dsnet on GitHub (Feb 6, 2017).
I was running fuzz testing on my Brotli implementation and noticed that the following input differs when decompressed by my version compared to the canonical one here.
The input causing issues (in hex):
e2e184886a563080e0391603e430f9When decompressed by the
google/brotliC implementation:worldwhitemediafirstirstirstirstirstirstirstirstirstirstirst...When decompressed by my implementation:
worldorldorldorldorldorldorldorldorldorldorldorldorldorldorl...According to section 4 of the RFC:
However, this seems contrary to what C decoder currently does:
The logic in the C version seems contrary to the stated prose in the RFC, which makes no mention that an implicit zero reverse rotates the distance ring-buffer.
@eustas commented on GitHub (Feb 6, 2017):
This piece of code is very tricky; it allows continuation after break in decoding.
We use special value (-1) to specify that distance is not decoded yet.
If distance is decoded, we retake it from distance ring-buffer.
Please, take a look where s->dist_rb_idx is incremented and decremented. For distance == 0 it has one extra decrement, so after processing the command it remains unchanged.
@dsnet commented on GitHub (Feb 6, 2017):
I believe there is in a mismatch in the increments and decrements in the case where the implicit zero command is repeatedly used and the static dictionary case is hit. It seems that it will short-circuit the increment logic (which only occurs in the dynamic dictionary case), causing the handling of implicit zeros to accidentally decrement one too much each time.
I compiled with debugging and logging enabled and ran
bro -d. The relevant snippet is:The
IMPLICIT_ZEROmessage was a printf added after L1706 of decode.c. In the log printout, you see that the distance is sweeping through the sequences [4, 11, 15, 16, 4, 4, 4, 4, 4, ...]. Notice it how it goes in reverse order through the initial distances in the ring buffer (i.e., 4, 11, 15, 16) even though only the zero code is used? After exhausting the ring buffer, it no longer performs static dictionary lookups, and starts doing dynamic dictionary lookups, causing it to properly stay at 4 for the rest of the time.@eustas commented on GitHub (Feb 7, 2017):
Oh! I see. Thanks for the report. Going to fix that ASAP.
@eustas commented on GitHub (Feb 7, 2017):
Fixed. Again, thank you! This bug is a new "survival champion" - it was introduced about 1.5 years ago =O
@ralfjunker commented on GitHub (Feb 7, 2017):
Testing
0749d9cdoes not give me the same decompressed output as @dsnet proposes in his initial report of this issue.Decompressing hex
e2e184886a563080e0391603e430f9results inworldsmallmallmallmallmallmallmallmallmallmallmallmallma...(10000 bytes in total)instead of the expected
worldorldorldorldorldorldorldorldorldorldorldorldorldorldor...(no total given).@dsnet's "wrong" output before
0749d9cmatches mine, so I am inclined to believe that his fixed output is also correct, in which case the fix is wrong.Could @dsnet or @eustas please shed some light? Thanks!
PS: Will there be a test case?
@dsnet commented on GitHub (Feb 7, 2017):
@ralfjunker, what implementation is yours?
Mine repeats "orld" until the 10000 byte limit is hit.
@dsnet commented on GitHub (Feb 7, 2017):
My interpretation of what should happen (assuming all implementations agree about huffman decoding of commands):
There is a repeated sequence of commands of the form (length:5, dist: 0).
According to section 4, dist:0 should result in the last distance being used, which defaults to 4 (without updating the distance ring buffer; not that it matters).
Thus, this is really a repeated sequence of (length:5, dist:4) commands.
For the first command, there is no output, so it falls under the static dictionary case according to:
This copies "world" to the output.
For all subsequent commands, there is sufficient data in the output such that a distance of 4 is always satisfied by the dynamic dictionary. Thus, copying "orldo" the first time, "rldor" the second time, "ldorl" the third time, etc.
@ralfjunker commented on GitHub (Feb 8, 2017):
I am using the C implementation from this repository, in particular this check-in:
0749d9ca8b.It repeats
malluntil it reaches 10000 bytes.Did you test @eustas' fix to this C implementation?
@eustas commented on GitHub (Feb 8, 2017):
For testing the fix we have internally added 3 synthetic tests:
The first parameter of
Verifyis DSL code snippet used to generatecompressed.The last parameter is an expected output string.
@eustas commented on GitHub (Feb 8, 2017):
Ooops, missed the case when distance code is baked into command.
@eustas commented on GitHub (Feb 8, 2017):
I hope, now it is finally fixed with #506 =)