{"record":{"id":"be86c70980d936f6","repo":"RustPython/RustPython","slug":"readexactly-size-can-not-be-less-than-zero","errorCode":null,"errorMessage":"readexactly size can not be less than zero","messagePattern":"readexactly size can not be less than zero","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/streams.py","lineNumber":755,"sourceCode":"        return data\n\n    async def readexactly(self, n):\n        \"\"\"Read exactly `n` bytes.\n\n        Raise an IncompleteReadError if EOF is reached before `n` bytes can be\n        read. The IncompleteReadError.partial attribute of the exception will\n        contain the partial read bytes.\n\n        if n is zero, return empty bytes object.\n\n        Returned value is not limited with limit, configured at stream\n        creation.\n\n        If stream was paused, this function will automatically resume it if\n        needed.\n        \"\"\"\n        if n < 0:\n            raise ValueError('readexactly size can not be less than zero')\n\n        if self._exception is not None:\n            raise self._exception\n\n        if n == 0:\n            return b''\n\n        while len(self._buffer) < n:\n            if self._eof:\n                incomplete = bytes(self._buffer)\n                self._buffer.clear()\n                raise exceptions.IncompleteReadError(incomplete, n)\n\n            await self._wait_for_data('readexactly')\n\n        if len(self._buffer) == n:\n            data = bytes(self._buffer)\n            self._buffer.clear()","sourceCodeStart":737,"sourceCodeEnd":773,"githubUrl":"https://github.com/RustPython/RustPython/blob/aaeab4f754b4f40efc0c8ab39cf7c4a3c35a8cfd/Lib/asyncio/streams.py#L737-L773","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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)."],"exampleFix":"# before\nn = struct.unpack('<i', await reader.readexactly(4))[0]  # signed -> -1 possible\ndata = await reader.readexactly(n)                      # ValueError\n\n# after\nn = struct.unpack('<I', await reader.readexactly(4))[0]  # unsigned\nif n > MAX_MSG:\n    raise ProtocolError(f'frame too large: {n}')\ndata = await reader.readexactly(n)","handlingStrategy":"validation","validationCode":"def parse_frame_length(raw: bytes) -> int:\n    n = struct.unpack('<I', raw)[0]  # unsigned\n    if n > MAX_MSG:\n        raise ProtocolError(f'frame too large: {n}')\n    return n\n\nn = parse_frame_length(await reader.readexactly(4))\nassert n >= 0  # guaranteed by '<I'\ndata = await reader.readexactly(n)","typeGuard":null,"tryCatchPattern":"try:\n    data = await reader.readexactly(n)\nexcept ValueError as e:\n    if 'less than zero' in str(e):\n        raise ProtocolError(f'peer sent negative frame length {n}') from e\n    raise","preventionTips":["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."],"tags":["asyncio","stream","readexactly","protocol","validation"],"backgroundTag":"negative-read-size","analyzedSha":"aaeab4f754b4f40efc0c8ab39cf7c4a3c35a8cfd","analyzedAt":"2026-08-17T00:37:52.100Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}