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

Pre-create larger buffers when uncompressing, to reduce garbage. #3035

Closed
wants to merge 1 commit into from

Conversation

bboreham
Copy link
Contributor

Fixes #2635

The basic idea is that, instead of starting at a fixed size of 512 bytes, the buffer is created as a multiple of the size of the compressed data.

Add CompressionFactor options on call and server for the user to control this multiple.

Buffer is created as a multiple of the size of the compressed data.
Add CompressionFactor options on call and server.

Signed-off-by: Bryan Boreham <bryan@weave.works>
@@ -677,11 +698,36 @@ func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxRecei
return d, nil
}

// Copied from go/src/io/ioutil/ioutil.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be problematic, as it will have legal (license) concerns. At the least, we would need to add their license to our repo, which I would rather avoid. Is there another way to implement this?

// Read from LimitReader with limit max+1. So if the underlying
// reader is over limit, the result will be bigger than max.
d, err = ioutil.ReadAll(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1))
d, err = readAll(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1), expectedSize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If expectedSize > maxReceiveMessageSize, then there would be no reason to allocate a buffer that large.

@@ -323,6 +324,25 @@ func (o MaxSendMsgSizeCallOption) before(c *callInfo) error {
}
func (o MaxSendMsgSizeCallOption) after(c *callInfo) {}

// CallCompressionFactor returns a CallOption which sets the predicted compression factor,
// used in sizing buffers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add:

//
// This API is EXPERIMENTAL.

We should call out where this applies (i.e. it's only when decompressing), and how it works (a reasonable interpretation of compression factor could be: "0.5" indicates that compression would halve the input).

@@ -323,6 +324,25 @@ func (o MaxSendMsgSizeCallOption) before(c *callInfo) error {
}
func (o MaxSendMsgSizeCallOption) after(c *callInfo) {}

// CallCompressionFactor returns a CallOption which sets the predicted compression factor,
// used in sizing buffers
func CallCompressionFactor(f int) CallOption {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

float32/64? To allow more precise values like 1.5?

(I'm also uncertain if the proposed default of 2x is acceptable, given that it means we are now allocating 3x what the client sent, which could potentially be a vulnerability. I'll ask some others for input.)

@bboreham
Copy link
Contributor Author

Thank you for the thoughtful review.

Thinking around the problem, I realised the gzip protocol sends the uncompressed size as the last four bytes of the message, so all this guesswork is unnecessary.

Since that size value could be forged by a malicious sender, and since you mention a potential vulnerability of allocating 3x the input data, I will note that it is fairly straightforward to create a gzip message which expands 100x or more, so gRPC is already vulnerable to such attacks up to maxReceiveMessageSize.

@dfawley
Copy link
Member

dfawley commented Sep 25, 2019

gRPC is already vulnerable to such attacks up to maxReceiveMessageSize.

This is one of the reasons we have this setting, so I'm not too concerned. I think mostly I was concerned that this allocation was ignoring maxReceiveMessageSize, so as long as we're taking that into account then it should be fine.

@bboreham
Copy link
Contributor Author

Replaced by #3048

@bboreham bboreham closed this Sep 27, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Mar 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use of ioutil.ReadAll in decompression generates lots of garbage
2 participants