Andrey Yarovoy created HDDS-17094:
-------------------------------------

             Summary: ECBlockInputStream serial read can return wrong data on a 
retry after a failed read
                 Key: HDDS-17094
                 URL: https://issues.apache.org/jira/browse/HDDS-17094
             Project: Apache Ozone
          Issue Type: Bug
          Components: EC Client
            Reporter: Andrey Yarovoy


h3. Problem

`ECBlockInputStream` keeps its own `position` for the block group and one inner 
block stream per data index. In the common case without seeks it trusts that 
each inner stream is already at the right offset: `seekStreamIfNecessary` only 
checks and re-seeks inner streams after `seeked` has been set by `seek()` or 
`closeStreams()`.

`readWithStrategy` advances `position` only after a cell has been read fully. 
If the inner stream fails partway through a cell, it has already moved forward 
by the bytes it returned before failing, but `position` has not. If `seeked` is 
still false, the next read on the same `ECBlockInputStream` reads that index 
from the stream's current offset, not the offset `position` implies, and 
silently returns the wrong data.

Reproduced in a unit test with rs-3-2, 100-byte cells, serial read:
# read 101 bytes: the index 2 stream is at offset 1, `position` is 101
# index 2 fails after 49 more bytes: `BadDataLocationException`, `position` 
stays 101, the index 2 stream is at 50
# clear the error and read 199 bytes without a seek: the first byte read does 
not match the block data at offset 101

h3. Impact

The main read path is not affected:
* every `ECBlockInputStream` is created by `ECBlockInputStreamFactoryImpl` 
behind `ECBlockInputStreamProxy`
* `readWithStrategy` wraps each `IOException` in a `BadDataLocationException`, 
and on that exception the proxy closes the reader and fails over to 
reconstruction, so the stream with the stale offset is never read again

The exposure is limited to an unchecked exception thrown by an inner block 
stream. `readWithStrategy` does not catch it, and the proxy rethrows it but 
keeps the same reader. A caller that catches it and reads again from the same 
stream would get wrong data, with no error. It is not known whether any inner 
stream throws unchecked exceptions on a read in practice. This is a latent 
correctness hazard rather than an observed failure.

h3. Proposed fix

When `readWithStrategy` exits with any exception, set `seeked = true`, so that 
the next read checks and re-seeks the inner streams, as after a `seek()`:

{code:java}
boolean completed = false;
try {
... // existing loop
completed = true;
} finally {
if (!completed) {
seeked = true;
}
}
{code}

h3. Testing

Add a `TestECBlockInputStream` case: a cell fails partway 
(`TestBlockInputStream#setShouldError(true, position, error)`), the error is 
cleared, and a read on the same stream without a seek must return the correct 
bytes. Cover both an `IOException` and an unchecked exception from the inner 
stream.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to