pjfanning opened a new issue, #3732:
URL: https://github.com/apache/pekko/issues/3732

   ### Summary
   With `pekko.stream.materializer.tls.engine = graph-stage`, `TlsGraphStage` 
fails the stream with `IllegalStateException: Stuck in unwrap loop` when 
ciphertext from the peer arrives while the `SSLEngine` needs to wrap 
(`NEED_WRAP`) and the ciphertext output has no downstream demand.
   
   ### How it was hit
   I found this while writing the real-`SSLEngine` tests for #3731. A test 
drove a plain client `SSLEngine` against a server `TlsGraphStage` (TLS 1.3, 
`TestSource`/`TestSink` on the ciphertext ports), and requested server 
ciphertext one element at a time, only when the client needed to unwrap.
   
   1. The client sends ClientHello. The server has to send its whole flight 
(ServerHello … Finished), but the test only requested one element.
   2. The client unwraps ServerHello and then sends its 6-byte 
middlebox-compatibility ChangeCipherSpec.
   3. That CCS reaches the server while the server engine is still `NEED_WRAP` 
and the ciphertext output has no demand.
   
   ```
   java.lang.IllegalStateException: Stuck in unwrap loop, bailing out, last 
handshake status [NEED_WRAP], remaining=6, out=0, 
(https://github.com/apache/pekko/issues/442)
        at 
org.apache.pekko.stream.impl.io.TlsGraphStage$$anon$1.doUnwrap(TlsGraphStage.scala:487)
        at 
org.apache.pekko.stream.impl.io.TlsGraphStage$$anon$1.doInbound(TlsGraphStage.scala:361)
        at 
org.apache.pekko.stream.impl.io.TlsGraphStage$$anon$1.runPhase(TlsGraphStage.scala:225)
        at 
org.apache.pekko.stream.impl.io.TlsGraphStage$$anon$1.pump(TlsGraphStage.scala:188)
        at 
org.apache.pekko.stream.impl.io.TlsGraphStage$$anon$1.org$apache$pekko$stream$impl$io$TlsGraphStage$$anon$$runPump(TlsGraphStage.scala:178)
        at 
org.apache.pekko.stream.impl.io.TlsGraphStage$$anon$1$TransportInputSlot.onPush(TlsGraphStage.scala:1021)
   ```
   
   (Line numbers are from 8f3d71b90e.) The test in #3731 avoids this by 
requesting some demand up front, so it is not part of that PR.
   
   ### Analysis
   In `doUnwrap`, `unwrap` returns `OK` with `NEED_WRAP` without consuming the 
record, and the stage puts the bytes back (`putBackUnreadBuffer`). Nothing can 
be wrapped without transport demand, but the inbound side still reports ready 
(transport data present and plaintext demand available). So the pump keeps 
re-running `doInbound` → `doUnwrap` until `inlineHandshakeReentries` exceeds 
`MaxInlineEngineProgressAttempts` and the stage fails. Back-pressure on the 
ciphertext output is legitimate, so this should wait for demand rather than 
fail.
   
   Over real TCP the ciphertext output usually has demand, so this needs the 
TCP write side to be back-pressured at the moment the peer sends mid-flight. It 
should be rare, but it is plausible with slow networks or large handshake 
flights.
   
   ### Suggested direction
   When the last handshake status is `NEED_WRAP` and the transport output has 
no demand, treat the inbound side as not ready (or stop the inbound loop) until 
`onPull` on the ciphertext output re-runs the pump, instead of re-unwrapping 
the same bytes.
   
   ### Legacy engine
   `TLSActor` has the same guard with a comment asking for a reproducer (#442). 
I have not checked whether the legacy engine fails with the same sequence; if 
it does, this may be a reproducer for #442.
   
   ### References
   Refs #442, #3731


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to