Mykyta Bozhenko created KAFKA-21280:
---------------------------------------
Summary: SslTransportLayer.transferFrom sends stale buffer
contents after a failed file read
Key: KAFKA-21280
URL: https://issues.apache.org/jira/browse/KAFKA-21280
Project: Kafka
Issue Type: Bug
Components: network
Affects Versions: 4.3.1, 2.5.0
Reporter: Mykyta Bozhenko
Assignee: Mykyta Bozhenko
When TLS is enabled, {{SslTransportLayer.transferFrom}} copies records from the
segment into {{fileChannelBuffer}} and writes that buffer to the TLS stream.
The buffer is cleared before each read:
{code:java}
if (!fileChannelBuffer.hasRemaining()) {
fileChannelBuffer.clear();
...
int bytesRead = fileChannel.read(fileChannelBuffer, pos);
if (bytesRead <= 0)
break;
fileChannelBuffer.flip();
}
int networkBytesWritten = write(fileChannelBuffer);
{code}
If the read throws, or returns no bytes, the buffer is left cleared instead of
empty: {{hasRemaining()}} is true and the buffer still holds the previous
chunk. When something was already written in the same call, the {{IOException}}
is swallowed ({{if (totalBytesWritten > 0) return totalBytesWritten}}). The
next call then skips the read, writes the stale buffer to the socket as the
next chunk of the file, advances the position, and fails on the following read
the same way. A read error on the segment never surfaces; the rest of the
requested region is sent as copies of an earlier chunk.
Reproduced on trunk (03e6606430): one controller and three brokers, JDK 17,
Linux 6.8, the leader's log directory on {{dm-dust}}. The tail of a 10 MiB
segment is made unreadable while its head stays in the page cache, then a
consumer reads the partition from the beginning over the SSL listener. On the
broker's network thread:
{code}
pread64(176, ..., 32768, 2850816) = 16384
pread64(176, ..., 32768, 2867200) = -1 EIO (Input/output error)
{code}
followed by 168 more {{-1 EIO}} at offsets advancing by 32768, covering the
whole 5.5 MiB region. The broker logs nothing about it: no exception, no
disconnect, the log directory stays online and the leader is unchanged. The
consumer gets 5456 of 20000 records, then:
{code}
Record batch for partition t1-0 at offset 5456 is invalid, cause: Record is
corrupt (stored crc = 1243940787, computed crc = 114489509)
{code}
The same scenario over the PLAINTEXT listener surfaces the error: {{sendfile}}
returns {{EIO}} and the broker closes the connection with
{{java.io.IOException: Input/output error}}.
Impact:
* Clients and followers on a TLS listener receive bytes that are not in the
file. The CRC check catches it, so a readable record on healthy replicas is
reported as corrupt, and the partition cannot be consumed past it from this
leader.
* Disk read errors on TLS listeners never reach the code that handles I/O
errors, so KAFKA-16304 cannot work for TLS until this is fixed.
* The stale bytes come from an earlier transfer on the same connection, so
nothing from another connection is exposed.
The {{bytesRead <= 0}} branch leaves the buffer in the same state;
{{FileRecords.writeTo}}'s size check usually rejects a truncated file before
that is reached.
Proposed fix: leave the buffer empty when a read does not succeed (for example
set its limit to 0 when the read throws or returns no bytes), so the next call
reads again and the original {{IOException}} propagates.
Disclosure per the ASF generative tooling guidance: the reproduction and the
analysis were produced with AI assistance; I have reviewed and am responsible
for all of it.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)