MahathirMohammadShuvo opened a new pull request, #23273:
URL: https://github.com/apache/kafka/pull/23273

   `unsignedIntToShort` in `AbstractApiMessageSerde` checked only the upper 
bound:
   
   ```java
   if (val > Short.MAX_VALUE) {
       throw new MetadataParseException("Value for " + entity + " was too 
large.");
   }
   return (short) val;
   ```
   
   `ByteUtils.readUnsignedVarint` decodes an unsigned 32-bit value into a signed
   `int` (`result |= (tmp = buffer.get()) << 28`), so an encoded value in
   `[2^31, 2^32)` comes back negative. A negative value is not `> 
Short.MAX_VALUE`,
   so it passes the guard and is then truncated to its low 16 bits by the cast.
   
   Measured against a transcription of `readUnsignedVarint`:
   
   | bytes | decodes to | before | after |
   | --- | --- | --- | --- |
   | `81 80 80 80 08` | -2147483647 | returns `1` | rejected |
   | `85 80 80 80 08` | -2147483643 | returns `5` | rejected |
   | `ff ff ff ff 0f` | -1 | returns `-1` | rejected |
   | `ff ff ff 7f` | 268435455 | rejected | rejected |
   | `01` | 1 | returns `1` | returns `1` |
   
   `DEFAULT_FRAME_VERSION` is `1`, so the first row is accepted as the default
   frame version and parsing continues against a record that never declared one.
   The same bytes in the type field make the record deserialize as a different
   `apiKey`.
   
   ### Scope
   
   The guard now rejects a varint that decodes negative. It does not attempt to
   reject every encoding above `Integer.MAX_VALUE` — `81 80 80 80 10` encodes
   4294967297, and `16 << 28` wraps to `0`, so it decodes to a positive `1` and 
is
   still accepted. That is a property of the primitive rather than of this 
check.
   
   The fix is deliberately in `unsignedIntToShort` and not in 
`readUnsignedVarint`.
   `readVarint` is built on the unsigned reader and zigzag-decodes its result, 
so
   the primitive **must** be able to return negative — `81 80 80 80 08` is the
   legitimate encoding of `readVarint(-1073741825)`. Rejecting it there would 
break
   record-batch lengths, offset deltas and every compact string, tagged field 
and
   generated `read()`. The bytes are a valid varint; only their interpretation 
as an
   unsigned short-range field is wrong.
   
   No compatibility concern: `write()` only ever emits `DEFAULT_FRAME_VERSION`,
   `apiKey()` and `version()`, all small non-negative shorts, so no compliant 
writer
   can produce an encoding the new guard rejects.
   
   ### Tests
   
   `testParsingVersionEncodedAboveIntMax` added next to the existing
   `testParsingVersionTooLarge`, which uses a four-byte varint (`ff ff ff 7f`) 
that
   stays positive — which is why the negative path was never covered.
   
   - `./gradlew :metadata:test --tests 
"org.apache.kafka.metadata.MetadataRecordSerdeTest"` —
     **10 passed, 0 failed**.
   - With only `AbstractApiMessageSerde.java` reverted to `trunk`, the new test 
is
     the **only** failure; the other nine still pass.
   - Weakening the guard to `val < 0` alone makes `testParsingVersionTooLarge` 
fail,
     so both halves of the condition are independently pinned.
   - `./gradlew :server-common:checkstyleMain :metadata:checkstyleTest` — clean.
   


-- 
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]

Reply via email to