tink-crypto / tink

Tink is a multi-language, cross-platform, open source library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.
https://developers.google.com/tink
Apache License 2.0
13.5k stars 1.18k forks source link

Go: Streaming AEAD decryption keeps a full copy of the stream's contents in memory #594

Closed vonhollen closed 2 years ago

vonhollen commented 2 years ago

Describe the bug:

The reader returned by calling NewDecryptingReader(in) on the result of streamingaead.New(handle) keeps the entire contents of in in-memory until the reader is garbage collected.

This happens because the ciphertext stream is tee'd to a buffer here, and that's what's passed to the primitive's real NewDecryptionReader implementation: https://github.com/google/tink/blob/645c17cbd121b9db7c6c336521c2c37717003332/go/streamingaead/decrypt_reader.go#L67-L71

That buffer makes it possible to rewind the stream and try another key if the first one fails, but the buffer isn't just used while finding the correct key. It's still referenced by the io.TeeReader implementation, so reading a 1GiB stream will allocate more than 1GiB of memory.

Going around streamingaead.New(handle) by turning the primary primitive into a tink.StreamingAEAD and calling NewDecryptingReader(...) on that works around the problem and the process consumes megabytes of memory instead of gigabytes on large streams.

What was the expected behavior?

Streaming AEAD memory usage during decryption should not increase linearly with the stream length.

One fix is to implement a reader that replaces the tee+buffer with a buffer that can be deactivated once the matching key is found.

How can we reproduce the bug?

Example of the problem and the workaround that shows ideal/expected memory usage: https://gist.github.com/vonhollen/64d51c56bd9d2294428da57a552ed457

Do you have any debugging information?

When running the example above, you'll see the tee'd buffer in the heap summary near the end:

Top in-use heap objects:
#1 size=1023.05MiB objects=1
      bytes.makeSlice buffer.go:229
      bytes.(*Buffer).grow buffer.go:142
      bytes.(*Buffer).Write buffer.go:172
      io.(*teeReader).Read io.go:574
      io.ReadAtLeast io.go:331
      io.ReadFull io.go:350
      tink/go/streamingaead/subtle/noncebased/noncebased.(*Reader).Read tink/go/streamingaead/subtle/noncebased/noncebased.go:280
      tink/go/streamingaead/streamingaead.(*decryptReader).Read tink/go/streamingaead/decrypt_reader.go:46
      main.main streammemoryusage.go:70

Provide your version information: