-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
DeflateStream truncation detection is buggy #72726
Comments
Tagging subscribers to this area: @dotnet/area-system-io-compression Issue DetailsDescriptionAfter #61768, Noticed this as .NET 7 can no longer communicate with Discord servers after updating the preview. Reproduction StepsI seems like it can't even roundtrip: using System.IO.Compression;
using System.Text;
string input = "foo";
var compressed = new MemoryStream();
var compressor = new DeflateStream(compressed, CompressionLevel.Optimal);
compressor.Write(Encoding.ASCII.GetBytes(input));
compressor.Flush();
compressed.Position = 0;
var decompressor = new DeflateStream(compressed, CompressionMode.Decompress);
var decompressed = new MemoryStream();
decompressor.CopyTo(decompressed); // Blows up in 7.0 preview 5 or newer
Console.WriteLine(Encoding.ASCII.GetString(decompressed.ToArray())); Expected behavior
Actual behavior
Regression?Works in 7.0 preview 4. Known WorkaroundsN/A ConfigurationNo response Other informationNo response
|
DeflateStream writes a footer as part of closing the stream; Flush'ing doesn't generate such a footer because there might be additional data written afterwards. The shared repro isn't closing the stream, hence the resulting data lacks the footer and technically is invalid. If you change the repro to pass That said, I think we should revert #61768. It's marked as a breaking change, so possible breaks were recognized, but if it's actually preventing us from communicating with real-world servers, it doesn't seem worth it, and we should instead take another run at enabling such validation in an opt-in manner for .NET 8. @MihaZupan, can you share an example that talks to a server that results in such failures? |
This example will connect to and read one message from the Discord WebSocket gateway. It should print something along the lines of {"t":null,"s":null,"op":10,"d":{"heartbeat_interval":41250,"_trace":["[\"gateway-prd-main-2vsv\",{\"micros\":0.0}]"]}} but will instead throw at the using System.Diagnostics;
using System.IO.Compression;
using System.Net.WebSockets;
using System.Text;
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("wss://gateway.discord.gg/?v=10&encoding=json&compress=zlib-stream"), CancellationToken.None);
var compressed = new MemoryStream();
var decompressor = new DeflateStream(compressed, CompressionMode.Decompress);
var decompressed = new MemoryStream();
var buffer = new byte[1024];
var result = await ws.ReceiveAsync(buffer, CancellationToken.None);
Debug.Assert(result.Count > 2 && buffer[0] == 0x78 && result.EndOfMessage); // This is just a POC
compressed.Write(buffer.AsSpan(2, result.Count - 2));
compressed.Position = 0;
decompressor.CopyTo(decompressed);
Console.WriteLine(Encoding.ASCII.GetString(decompressed.ToArray()));
// Further messages would require auth |
If you know the exact size that is present, ReadExactly will still throw (in this case, we're aware it's a partial stream but only care for say, the first frame.) ReadAtLeast when passing |
Noted, thanks for letting me know. Do revert and I will put together a new PR with the truncation behavior under a constructor opt-in flag. |
There are other libraries (like SharpZibLib) which would also fail on the Discord repro, e.g.:
throws:
Apparently Discord server doesn't send the finishing sequence ( There is an argument for reverting #61768 because it was a breaking change. But there is also an argument for not reverting it. Mainly, because stricter checks on decompression prevent from much more serious problems, like when the incomplete data is silently decompressed without errors. |
Yup, this is the same issue I noted above with the repro:
The server is in the wrong here. However, I think the cons of the break outweigh the benefits. The only thing this helps with is the missing footer, and until that point, data will happily be decompressed. The client can just as easily stop reading and not know about such silent missing data from the server, and there are a myriad other ways the server could miss sending some data, even with a footer. In this case, the server is taking an explicit action (e.g. specifying payload length) to signal a complete data payload. At this point if we want that codition to be an error, it should be opt-in in some fashion, such as when constructing the stream or as a property you can query after use to see whether the footer was received. |
@mfkl we would need an API proposal before the PR, so please consider filing one using the template from https://github.com/dotnet/runtime/issues/new/choose. |
Description
After #61768,
DeflateStream
is no longer able to decompress (seemingly valid) input.Noticed this as .NET 7 can no longer communicate with Discord servers after updating the preview.
Reproduction Steps
It seems like it can't even roundtrip:
Expected behavior
DeflateStream
is able to roundtrip.Actual behavior
Regression?
Works in 7.0 preview 4.
Throws in 7.0 preview 5 or newer (#61768).
Known Workarounds
N/A
Configuration
No response
Other information
No response
The text was updated successfully, but these errors were encountered: