Skip to content

[3.13] gh-122179: Fix hashlib.file_digest and non-blocking I/O #132787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Doc/library/hashlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,10 @@ a file or file-like object.
*fileobj* must be a file-like object opened for reading in binary mode.
It accepts file objects from builtin :func:`open`, :class:`~io.BytesIO`
instances, SocketIO objects from :meth:`socket.socket.makefile`, and
similar. The function may bypass Python's I/O and use the file descriptor
similar. *fileobj* must be opened in blocking mode, otherwise a
:exc:`BlockingIOError` may be raised.

The function may bypass Python's I/O and use the file descriptor
from :meth:`~io.IOBase.fileno` directly. *fileobj* must be assumed to be
in an unknown state after this function returns or raises. It is up to
the caller to close *fileobj*.
Expand Down Expand Up @@ -299,6 +302,10 @@ a file or file-like object.

.. versionadded:: 3.11

.. versionchanged:: next
Now raises a :exc:`BlockingIOError` if the file is opened in blocking
mode. Previously, spurious null bytes were added to the digest.


Key derivation
--------------
Expand Down
2 changes: 2 additions & 0 deletions Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
view = memoryview(buf)
while True:
size = fileobj.readinto(buf)
if size is None:
raise BlockingIOError("I/O operation would block.")
if size == 0:
break # EOF
digestobj.update(view[:size])
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,15 @@ def test_file_digest(self):
with open(os_helper.TESTFN, "wb") as f:
hashlib.file_digest(f, "sha256")

class NonBlocking:
def readinto(self, buf):
return None
def readable(self):
return True

with self.assertRaises(BlockingIOError):
hashlib.file_digest(NonBlocking(), hashlib.sha256)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:func:`hashlib.file_digest` now raises :exc:`BlockingIOError` when no data
is available during non-blocking I/O. Before, it added spurious null bytes
to the digest.
Loading