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

[UNDERTOW-2451] fix the information leakage issues #1668

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,6 @@ public int getReadBodyChunkSize() {
*/
public int stringLength = -1;

/**
* The current string being read
*/
public StringBuilder currentString;
Copy link
Member

Choose a reason for hiding this comment

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

You can't remove currentString here. The parser is invoked multiple times as bytes are made available and, when the last byte is read, it wil need the whole currentString to form a parsed result, it will neeed information from previous parse invocations. Otherwise, you risk losing bytes. It all depends on how many bytes the parser has received on every invocation.


/**
* when reading the first byte of an integer this stores the first value. It is set to -1 to signify that
* the first byte has not been read yet.
Expand All @@ -248,7 +243,6 @@ public void reset() {
reasonPhrase = null;
headers = new HeaderMap();
stringLength = -1;
currentString = null;
currentIntegerPart = -1;
readHeaders = 0;
}
Expand Down Expand Up @@ -300,12 +294,8 @@ protected StringHolder parseString(ByteBuffer buf, boolean header) {
this.stringLength = -1;
return new StringHolder(null, true, false);
}
StringBuilder builder = this.currentString;
final StringBuilder builder = new StringBuilder();

if (builder == null) {
builder = new StringBuilder();
this.currentString = builder;
}
int length = builder.length();
while (length < stringLength) {
if (!buf.hasRemaining()) {
Expand All @@ -323,7 +313,6 @@ protected StringHolder parseString(ByteBuffer buf, boolean header) {

if (buf.hasRemaining()) {
buf.get(); //null terminator
this.currentString = null;
this.stringLength = -1;
this.containsUrlCharacters = false;
return new StringHolder(builder.toString(), true, containsUrlCharacters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ public class HpackDecoder {

private boolean first = true;

private final StringBuilder stringBuilder = new StringBuilder();
Copy link
Member

Choose a reason for hiding this comment

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

There is no leakage in this class either. In this case, we are reusing the same builder across multiple decode invocations, but we are clearing the buffer right after we use it on every call. It just prevents us from having to create a new builder everytime we are invoked, which is a good thing.

Copy link
Author

Choose a reason for hiding this comment

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

The root cause for this leak is very similar to the vulnerability CVE-2020-17527 in Tomcat. I added additional details at https://issues.redhat.com/browse/UNDERTOW-2451.


public HpackDecoder(int maxAllowedMemorySize) {
this.specifiedMemorySize = Math.min(Hpack.DEFAULT_TABLE_SIZE, maxAllowedMemorySize);
this.maxAllowedMemorySize = maxAllowedMemorySize;
Expand Down Expand Up @@ -247,11 +245,11 @@ private String readHpackString(ByteBuffer buffer) throws HpackException {
if (huffman) {
return readHuffmanString(length, buffer);
}
StringBuilder stringBuilder = new StringBuilder(length);
BFionaccept marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < length; ++i) {
stringBuilder.append((char) (buffer.get() & 0xFF));
}
String ret = stringBuilder.toString();
stringBuilder.setLength(0);
if (ret.isEmpty()) {
//return the interned empty string, rather than allocating a new one each time
return "";
Expand All @@ -260,12 +258,12 @@ private String readHpackString(ByteBuffer buffer) throws HpackException {
}

private String readHuffmanString(int length, ByteBuffer buffer) throws HpackException {
StringBuilder stringBuilder = new StringBuilder(length);
Copy link
Contributor

Choose a reason for hiding this comment

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

final ?

Copy link
Author

Choose a reason for hiding this comment

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

In this fix, I referenced the patch for a similar vulnerability (CVE-2020-17527) in the corresponding functional module of Tomcat, as shown in this commit: apache/tomcat@d56293f#diff-98723542790b05d2e3c2dadeef7c632e641e3118b3b809660e9b1ef41686cfe0R226. I removed the final from the StringBuilder stringBuilder declaration, enhancing code flexibility and allowing for future reassignment if needed.

Certainly, I think it is also acceptable to retain the final modifier.

Copy link
Author

Choose a reason for hiding this comment

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

Oh, I’m sorry, I accidentally deleted a previous conversation.

HPackHuffman.decode(buffer, length, stringBuilder);
String ret = stringBuilder.toString();
if (ret.isEmpty()) {
return "";
}
stringBuilder.setLength(0);
return ret;
}

Expand Down
Loading