RustPython/RustPython · error · ValueError
readexactly size can not be less than zero
Error message
readexactly size can not be less than zero
What it means
StreamReader.readexactly(n) requires a non-negative byte count; n < 0 raises ValueError('readexactly size can not be less than zero') before any I/O happens. n == 0 is legal and immediately returns b''. The guard protects the buffer arithmetic (which slices and compares against n) from nonsensical sizes.
Source
Thrown at Lib/asyncio/streams.py:755
return data
async def readexactly(self, n):
"""Read exactly `n` bytes.
Raise an IncompleteReadError if EOF is reached before `n` bytes can be
read. The IncompleteReadError.partial attribute of the exception will
contain the partial read bytes.
if n is zero, return empty bytes object.
Returned value is not limited with limit, configured at stream
creation.
If stream was paused, this function will automatically resume it if
needed.
"""
if n < 0:
raise ValueError('readexactly size can not be less than zero')
if self._exception is not None:
raise self._exception
if n == 0:
return b''
while len(self._buffer) < n:
if self._eof:
incomplete = bytes(self._buffer)
self._buffer.clear()
raise exceptions.IncompleteReadError(incomplete, n)
await self._wait_for_data('readexactly')
if len(self._buffer) == n:
data = bytes(self._buffer)
self._buffer.clear()View on GitHub (pinned to aaeab4f754)
Solutions
- Validate the parsed length immediately: if n < 0 raise a protocol error naming the field and the raw bytes decoded.
- Use unsigned struct formats ('<I', '<Q') for on-wire lengths so negative values cannot be decoded.
- Add an upper bound too: reject n above a max message size before waiting, which also caps memory.
- If 'read the rest' semantics are wanted, use reader.read(-1), not readexactly(-1).
Example fix
# before
n = struct.unpack('<i', await reader.readexactly(4))[0] # signed -> -1 possible
data = await reader.readexactly(n) # ValueError
# after
n = struct.unpack('<I', await reader.readexactly(4))[0] # unsigned
if n > MAX_MSG:
raise ProtocolError(f'frame too large: {n}')
data = await reader.readexactly(n) Defensive patterns
Strategy: validation
Validate before calling
def parse_frame_length(raw: bytes) -> int:
n = struct.unpack('<I', raw)[0] # unsigned
if n > MAX_MSG:
raise ProtocolError(f'frame too large: {n}')
return n
n = parse_frame_length(await reader.readexactly(4))
assert n >= 0 # guaranteed by '<I'
data = await reader.readexactly(n) Try / catch
try:
data = await reader.readexactly(n)
except ValueError as e:
if 'less than zero' in str(e):
raise ProtocolError(f'peer sent negative frame length {n}') from e
raise Prevention
- Use unsigned struct formats ('<I', '<Q') for wire length fields.
- Validate parsed lengths immediately and cap them with a max-message bound.
- Reserve read(-1) for 'rest of stream'; never pass -1 to readexactly.
When it happens
Trigger: await reader.readexactly(-1); length prefixes decoded from a wire protocol into a negative number (signed vs unsigned struct mismatch like '<i' vs '<I'); computed sizes such as remaining = total - already_read going negative due to bad accounting; sentinel -1 passed to mean 'rest of stream'.
Common situations: Binary protocol clients misparsing headers (sign bit, endianness); fuzzed or malicious input crafting negative lengths; peers lying about frame size; unit tests using -1 sentinels; refactors where read() semantics (-1 allowed) were copied onto readexactly.
Related errors
- Limit cannot be <= 0
- Separator should contain at least one element
- Separator should be at least one-byte string
- 3rd arg must be a dict
- task factory must be a callable or None
AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17).
Data as JSON: /api/errors/be86c70980d936f6.
Report an issue: GitHub.