Commit 808685f5763 for php

commit 808685f57633c2bc68c77001feeb4df83f97541b
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sat Oct 3 09:54:36 2026 -0400

    ext/standard: Reject incomplete sha1_file reads

    Return false after a negative read or a zero read without EOF instead of
    returning the digest of a prefix. Finalize the context and close the stream
    on the failure path.

    Closes GH-24099

diff --git a/NEWS b/NEWS
index 326a9fe7986..931bdc47686 100644
--- a/NEWS
+++ b/NEWS
@@ -199,6 +199,8 @@ PHP                                                                        NEWS
     (Ilia Alshanetsky)

 - Standard:
+  . Fixed sha1_file() returning a digest for incomplete data after a stream
+    read failure. (Ilia Alshanetsky)
   . Fixed three Windows-only proc_open() defects: an uninitialized
     PROCESS_INFORMATION, an indeterminate comspec pointer after a failed
     lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)
diff --git a/ext/standard/sha1.c b/ext/standard/sha1.c
index 95f2b54d0fd..7dd801c0151 100644
--- a/ext/standard/sha1.c
+++ b/ext/standard/sha1.c
@@ -84,10 +84,16 @@ PHP_FUNCTION(sha1_file)
 		PHP_SHA1Update(&context, buf, n);
 	}

+	bool failed = n < 0 || !php_stream_eof(stream);
+
 	PHP_SHA1Final(digest, &context);

 	php_stream_close(stream);

+	if (failed) {
+		RETURN_FALSE;
+	}
+
 	if (raw_output) {
 		RETURN_STRINGL((char *) digest, 20);
 	} else {
diff --git a/ext/standard/tests/strings/sha1_file_stream_error.phpt b/ext/standard/tests/strings/sha1_file_stream_error.phpt
new file mode 100644
index 00000000000..751cf5e7240
--- /dev/null
+++ b/ext/standard/tests/strings/sha1_file_stream_error.phpt
@@ -0,0 +1,77 @@
+--TEST--
+sha1_file() returns false when a stream read fails
+--FILE--
+<?php
+class TestStream
+{
+    public $context;
+    private string $mode;
+    private int $position = 0;
+
+    public function stream_open(string $path): bool
+    {
+        $this->mode = substr($path, strlen('test://'));
+
+        return true;
+    }
+
+    public function stream_read(int $count): string|false
+    {
+        $position = $this->position++;
+        if ($this->mode === 'error') {
+            return false;
+        }
+        if ($this->mode === 'empty' || $this->mode === 'stalled') {
+            return '';
+        }
+        if ($this->mode === 'short') {
+            return substr('prefix', $position * 2, 2);
+        }
+        if ($position === 0) {
+            return 'prefix';
+        }
+
+        return $this->mode === 'prefix-error' ? false : '';
+    }
+
+    public function stream_eof(): bool
+    {
+        return $this->mode === 'empty'
+            || ($this->mode === 'eof' && $this->position > 1)
+            || ($this->mode === 'short' && $this->position >= 3);
+    }
+}
+
+stream_wrapper_register('test', TestStream::class);
+
+foreach (['error', 'prefix-error', 'stalled', 'prefix-stalled', 'empty', 'eof', 'short'] as $mode) {
+    echo "$mode: ";
+    var_dump(sha1_file("test://$mode"));
+    echo "$mode (raw): ";
+    $result = sha1_file("test://$mode", true);
+    var_dump(is_string($result) ? bin2hex($result) : $result);
+}
+
+echo "directory: ";
+var_dump(@sha1_file(__DIR__));
+echo "directory (raw): ";
+$result = @sha1_file(__DIR__, true);
+var_dump(is_string($result) ? bin2hex($result) : $result);
+?>
+--EXPECT--
+error: bool(false)
+error (raw): bool(false)
+prefix-error: bool(false)
+prefix-error (raw): bool(false)
+stalled: bool(false)
+stalled (raw): bool(false)
+prefix-stalled: bool(false)
+prefix-stalled (raw): bool(false)
+empty: string(40) "da39a3ee5e6b4b0d3255bfef95601890afd80709"
+empty (raw): string(40) "da39a3ee5e6b4b0d3255bfef95601890afd80709"
+eof: string(40) "b4ebfe34d0fa97f0dd2bb1234fad8f59805f4e8d"
+eof (raw): string(40) "b4ebfe34d0fa97f0dd2bb1234fad8f59805f4e8d"
+short: string(40) "b4ebfe34d0fa97f0dd2bb1234fad8f59805f4e8d"
+short (raw): string(40) "b4ebfe34d0fa97f0dd2bb1234fad8f59805f4e8d"
+directory: bool(false)
+directory (raw): bool(false)