Reject multipart parts missing required Content-Disposition 'name'#29
Open
daguimu wants to merge 2 commits into
Open
Reject multipart parts missing required Content-Disposition 'name'#29daguimu wants to merge 2 commits into
daguimu wants to merge 2 commits into
Conversation
When a client sends a multipart/form-data request whose Content-Disposition header has no 'name' parameter, the vintage HttpPostMultipartRequestDecoder dereferences the missing name attribute and surfaces an opaque NPE wrapped in ErrorDataDecoderException: 'Cannot invoke Attribute.getValue() because nameAttribute is null'. Operators have no hint about which part was malformed. Add an explicit null check before cleanString(nameAttribute.getValue()) in both the FIELD branch (getAttribute) and the file-upload branch (getFileUpload), throwing a descriptive ErrorDataDecoderException that names the missing parameter. Because the vintage module promises 100% behavioral compatibility with the legacy Netty 4 decoder when all quirks are enabled, the new check is gated behind a new DecoderQuirk, NPE_ON_MISSING_CONTENT_DISPOSITION_NAME. Enabling the quirk restores the legacy NPE-wrapped-in-ErrorDataDecoderException. Migrated via .enableAllQuirks(), deprecated constructors, or the comparison fuzz suite, the quirk is on and behavior is unchanged. Migrated from netty/netty#16688. Fixes netty/netty#16176.
Align the DecoderQuirk javadoc with sibling quirks ('replicates a
bug' wording used by CONSERVATIVE_LF_BACKTRACK, FORWARD_CHUNK_CR).
Split the missing-name test assertions across quirk modes: when
quirks are on, verify the wrapped cause is a NullPointerException
(confirming legacy behavior is preserved); when quirks are off,
assert the exact new message and absence of a cause. The previous
contains('name') check passed in both modes because the legacy NPE
message happens to contain 'nameAttribute', which would not catch
a regression that flipped the gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a client sends a
multipart/form-datarequest whoseContent-Dispositionheader is missing the requiredname=parameter (e.g., the attack-style payloads referenced in netty/netty#16176),HttpPostMultipartRequestDecoderin the vintage module surfaces an opaque NPE:Operators get no hint about which part of the request was malformed, and the decoder relies on
catch (NullPointerException)as its validation mechanism.Root Cause
currentFieldAttributes.get(HttpHeaderValues.NAME)returnsnullwhen the inboundContent-Dispositionhas noname=parameter. BothgetAttribute(FIELD branch) andgetFileUploaddereference the resultingAttributevianameAttribute.getValue()without first checking for null. The surroundingcatch (NullPointerException | IllegalArgumentException | IOException)aroundfactory.createAttribute(...)produces the confusing message above.Fix
Add an explicit
nameAttribute == nullcheck in bothgetAttribute()andgetFileUpload()before thecleanString(nameAttribute.getValue())dereference, throwingErrorDataDecoderException("Content-Disposition is missing required 'name' parameter").Because the
multipart-vintagemodule promises 100% behavioral compatibility with the legacy Netty 4 decoder when all quirks are enabled (verified byMultipartComparisonTest), the new check is gated behind a newDecoderQuirk:NPE_ON_MISSING_CONTENT_DISPOSITION_NAME— when enabled, preserves the legacy NPE-wrapped-in-ErrorDataDecoderException. When disabled (the new default for builder users who do not call.enableAllQuirks()), the decoder throws the descriptive exception.Users of the deprecated constructors and anyone calling
.enableAllQuirks()(including the comparison fuzz suite) see unchanged behavior. New users who construct viaHttpPostRequestDecoder.builder()get the improved error.Tests Added
getAttributeFIELD branchtestFieldWithoutNameAttributeThrowsErrorDataDecoderException— sends a multipart field withContent-Disposition: form-data(noname=) and assertsErrorDataDecoderExceptionwhose message mentions thenameparameter. Runs under bothquirk=false(clean message) andquirk=true(NPE-wrapped message).getFileUploadtestFileUploadWithoutNameAttributeThrowsErrorDataDecoderException— sends a file upload withContent-Disposition: form-data; filename="upload.txt"(noname=) and asserts the same. Runs under both quirk modes.Both tests pass in both parameterized runs. Full suite:
mvn test→ multipart-core 35/35, multipart-vintage 233/233 (2 pre-existing skips unrelated to this change), including the 58 fuzz-corpus cases inMultipartComparisonTestwhich assert 100% parity with the legacy decoder — the quirk gate keeps those green.Impact
name=are rejected with a clearErrorDataDecoderExceptionwhose message identifies the missing parameter. The exception type is unchanged, so existingcatchblocks behave the same..enableAllQuirks()or deprecated constructors): unchanged — the NPE-wrapped-in-ErrorDataDecoderExceptionis still produced, preserving the drop-in-replacement promise.DecoderQuirkfollowing the existing per-bug pattern (e.g.,CONSERVATIVE_LF_BACKTRACK,DISABLE_EARLY_MIXED_END).HttpPostMultipartRequestDecoderin the vintage module plus the oneDecoderQuirkenum addition;HttpPostStandardRequestDecoder,UrlEncodedDecoder, and other paths are untouched.Background
Migrated from netty/netty#16688 per @yawkat's recommendation in that thread. Fixes netty/netty#16176.