[ 
https://issues.apache.org/jira/browse/CAMEL-26306?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Work on CAMEL-26306 started by shashank.
----------------------------------------
> camel-as2 - a CRLF whose CR is the last byte of an 8 KB read of the line 
> reader loses its CR: the received EDI message changes and its signature fails
> ------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26306
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26306
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-as2
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>
> {{AS2SessionInputBuffer.readLine}} (camel-as2-api) reports a line as ending 
> with CRLF ({{isLastLineReadEnrichedByCarriageReturn()}}) only when the CR is 
> in the read buffer right before the LF: {{if (i > 0 && this.buffer[i - 1] == 
> CR)}} (main 72a1bff606a3, line 189). When the CR is the last byte of one read 
> and the LF the first byte of the next read, the CR is already in the line 
> buffer: the line itself is right ({{lineFromLineBuffer}} strips the CR), but 
> the line is reported as ending with a bare LF.
> {{EntityParser.parseBodyPartText}} rebuilds the content of a body part from 
> the lines and these two flags (since CAMEL-15111, Camel 3.18.5 and 4.0, so 
> that LF and CRLF line endings are kept as received; the flag and its check 
> came with the same change). So that CRLF becomes LF in the content: the EDI 
> message of an AS2 message ({{parseEDIEntityBody}} -> {{parseBodyPartBytes}} 
> -> {{parseBodyPartText}}), a text/plain part (the human readable part of an 
> MDN), and the other parts read through {{parseBodyPartBytes}}. When that CRLF 
> is the one before a delimiter line, {{parseBodyPartText}} removes two 
> characters for it ({{setLength(length - 2)}}), so the last byte of the 
> content is lost instead.
> *When*: the line reader reads the entity content 8 KB at a time 
> ({{doParseAS2MessageEntity}}: {{new AS2SessionInputBuffer(..., 8 * 1024)}}). 
> The AS2 server parses the received body from a {{ByteArrayInputStream}} 
> ({{AS2BHttpServerConnection.receiveRequestEntity}}), and the decrypted or 
> decompressed content of an encrypted or compressed message is parsed the same 
> way ({{EntityParser.parseEntity}}). So every CRLF whose CR is the last byte 
> of an 8 KB read (offset 8191, 16383, ... of the body, or of the decrypted 
> content) loses its CR. With 80 byte lines about one read in 80 ends that way, 
> so an EDI file of 1 MB (128 reads) is more likely than not to be changed. The 
> client parses an MDN response straight from the connection, where a read can 
> end at any byte (not tested).
> *Effect*: for a signed message the signature is verified over the parsed 
> content ({{SigningUtils.isValid}}), so verification fails and the message is 
> rejected ("Failed to validate the signature") when 
> {{validateSigningCertificateChain}} is set. For an unsigned message the route 
> gets the changed content; the MIC in the MDN is computed over the changed 
> content too ({{MicUtils.createReceivedContentMic}}), so it does not match the 
> sender's MIC (reasoned from the code, not tested).
> *Reproduced* (camel-as2-api, main): an unsigned {{application/edifact}} 
> request through {{EntityParser.parseAS2MessageEntity}} (the call the server 
> connection makes) whose first line is 8191 bytes long: 
> {{ApplicationEntity.getEdiMessage()}} is one byte shorter, the CR at offset 
> 8191 is missing. With a first line of 8190 or 8192 bytes the content is 
> unchanged. A {{multipart/signed}} EDI message whose body has a CR at offset 
> 8191 (inside the EDI content, or before the delimiter line after it): 
> {{SigningUtils.isValid}} on the parsed message returns false.
> *Fix*: when the LF is the first byte of the read buffer, take the CR from the 
> end of the line buffer; otherwise the byte before the LF must be in the read 
> buffer of this line ({{i > bufferpos}}), so a CR consumed before the line by 
> {{read(InputStream)}} is no longer taken for its line ending either.
> *Tests*: new {{AS2SessionInputBufferTest}} (a CRLF split between two reads of 
> a chunked stream, a CR that is the last byte of a full read buffer, a CR 
> consumed by {{read}} before an empty line), 
> {{EntityParserContentLineEndingTest.parseEdiMessageKeepsACrLfSplitByTheReadBuffer}}
>  and {{signedEdiMessageWithACrLfSplitByTheReadBufferKeepsItsSignature}} (2 
> cases; the reproducers above): all six fail on main and pass with the fix. 
> camel-as2-api 117 tests and camel-as2-component 157 tests (including the 
> in-JVM *IT classes; 1 skipped) pass.
> *How it was found*: an OSS-Fuzz-style harness for the MIME boundary handling 
> Camel does itself ({{EntityParser.parseBodyPartText}} on 
> {{AS2SessionInputBuffer}}; property: the text before the first delimiter 
> line, whatever the read chunking), run on HotSpot with seeded random and 
> grammar-aware inputs (no Jazzer), found it in the first second (minimised: a 
> body of CR and LF in two reads gives LF). The Rocq JVM model (a JVM semantics 
> in Rocq that executes the real class files) then states it as kernel-checked 
> theorems on the class files of main and of the fix: 
> {{main_split_crlf_reported_as_lf}} ("a CR LF b CR LF" read as "a CR" + "LF b 
> CR LF": the first line is reported with a bare LF) and 
> {{fix_split_crlf_reported_as_crlf}}. HotSpot gives the same results on the 
> same class files.
> *Affected*: main, camel-4.22.x, camel-4.18.x, camel-4.14.x and camel-4.10.x 
> have the same check (read, not run); on 4.18.x and older the server parses 
> the body straight from the connection, so there a read can end at any byte. 
> The fix is local to {{readLine}} and changes no option or default, so it can 
> be backported.
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to