Skip to content

Commit 2fa8473

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix GH-10834: exif_read_data() cannot read smaller stream wrapper chunk sizes
2 parents e0af7c3 + d369a77 commit 2fa8473

File tree

2 files changed

+112
-10
lines changed

2 files changed

+112
-10
lines changed

ext/exif/exif.c

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,25 @@ zend_module_entry exif_module_entry = {
215215
ZEND_GET_MODULE(exif)
216216
#endif
217217

218+
/* php_stream_read() may return early without reading all data, depending on the chunk size
219+
* and whether it's a URL stream or not. This helper keeps reading until the requested amount
220+
* is read or until there is no more data available to read. */
221+
static ssize_t exif_read_from_stream_file_looped(php_stream *stream, char *buf, size_t count)
222+
{
223+
ssize_t total_read = 0;
224+
while (total_read < count) {
225+
ssize_t ret = php_stream_read(stream, buf + total_read, count - total_read);
226+
if (ret == -1) {
227+
return -1;
228+
}
229+
if (ret == 0) {
230+
break;
231+
}
232+
total_read += ret;
233+
}
234+
return total_read;
235+
}
236+
218237
/* {{{ php_strnlen
219238
* get length of string if buffer if less than buffer size or buffer size */
220239
static size_t php_strnlen(char* str, size_t maxlen) {
@@ -3321,7 +3340,7 @@ static bool exif_process_IFD_TAG_impl(image_info_type *ImageInfo, char *dir_entr
33213340
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val);
33223341
return false;
33233342
}
3324-
fgot = php_stream_read(ImageInfo->infile, value_ptr, byte_count);
3343+
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, value_ptr, byte_count);
33253344
php_stream_seek(ImageInfo->infile, fpos, SEEK_SET);
33263345
if (fgot != byte_count) {
33273346
EFREE_IF(outside);
@@ -3846,7 +3865,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo)
38463865
Data[0] = (uchar)lh;
38473866
Data[1] = (uchar)ll;
38483867

3849-
got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */
3868+
got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */
38503869
if (got != itemlen-2) {
38513870
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04X(=%d) != itemlen-2=x%04X(=%d)", got, got, itemlen-2, itemlen-2);
38523871
return false;
@@ -3864,7 +3883,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo)
38643883
size = ImageInfo->FileSize - fpos;
38653884
sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL);
38663885
Data = ImageInfo->file.list[sn].data;
3867-
got = php_stream_read(ImageInfo->infile, (char*)Data, size);
3886+
got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)Data, size);
38683887
if (got != size) {
38693888
EXIF_ERRLOG_FILEEOF(ImageInfo)
38703889
return false;
@@ -4041,7 +4060,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
40414060
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2);
40424061
#endif
40434062
php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */
4044-
php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2);
4063+
if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2) != 2)) {
4064+
return false;
4065+
}
40454066
num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel);
40464067
dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/;
40474068
if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) {
@@ -4051,7 +4072,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
40514072
if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) {
40524073
return false;
40534074
}
4054-
php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2);
4075+
if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2) != dir_size - 2)) {
4076+
return false;
4077+
}
40554078
next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel);
40564079
#ifdef EXIF_DEBUG
40574080
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset);
@@ -4139,7 +4162,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
41394162
#ifdef EXIF_DEBUG
41404163
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size);
41414164
#endif
4142-
php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size);
4165+
exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size);
41434166
#ifdef EXIF_DEBUG
41444167
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done");
41454168
#endif
@@ -4190,7 +4213,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
41904213
if (!ImageInfo->Thumbnail.data) {
41914214
ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
41924215
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
4193-
fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
4216+
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
41944217
if (fgot != ImageInfo->Thumbnail.size) {
41954218
EXIF_ERRLOG_THUMBEOF(ImageInfo)
41964219
efree(ImageInfo->Thumbnail.data);
@@ -4230,7 +4253,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
42304253
if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) {
42314254
ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
42324255
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
4233-
fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
4256+
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
42344257
if (fgot != ImageInfo->Thumbnail.size) {
42354258
EXIF_ERRLOG_THUMBEOF(ImageInfo)
42364259
efree(ImageInfo->Thumbnail.data);
@@ -4285,7 +4308,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
42854308

42864309
if (ImageInfo->FileSize >= 2) {
42874310
php_stream_seek(ImageInfo->infile, 0, SEEK_SET);
4288-
if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) {
4311+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)file_header, 2) != 2) {
42894312
return false;
42904313
}
42914314
if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) {
@@ -4296,7 +4319,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
42964319
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file");
42974320
}
42984321
} else if (ImageInfo->FileSize >= 8) {
4299-
if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) {
4322+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header+2), 6) != 6) {
43004323
return false;
43014324
}
43024325
if (!memcmp(file_header, "II\x2A\x00", 4)) {

ext/exif/tests/gh10834.phpt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
--TEST--
2+
GH-10834 (exif_read_data() cannot read smaller stream wrapper chunk sizes)
3+
--EXTENSIONS--
4+
exif
5+
--FILE--
6+
<?php
7+
class VariableStream {
8+
private $data;
9+
private $position;
10+
public $context;
11+
12+
function stream_close() {
13+
return true;
14+
}
15+
16+
function stream_eof() {
17+
return $this->position >= strlen($this->data);
18+
}
19+
20+
function stream_open($path, $mode, $options, &$opened_path) {
21+
$this->position = 0;
22+
$this->data = file_get_contents(__DIR__.'/bug50845.jpg');
23+
return true;
24+
}
25+
26+
function stream_seek($offset, $whence) {
27+
switch ($whence) {
28+
case SEEK_SET:
29+
if ($offset < strlen($this->data) && $offset >= 0) {
30+
$this->position = $offset;
31+
return true;
32+
} else {
33+
return false;
34+
}
35+
break;
36+
case SEEK_CUR:
37+
if ($offset >= 0) {
38+
$this->position += $offset;
39+
return true;
40+
} else {
41+
return false;
42+
}
43+
break;
44+
case SEEK_END:
45+
if (strlen($this->data) + $offset >= 0) {
46+
$this->position = strlen($this->data) + $offset;
47+
return true;
48+
} else {
49+
return false;
50+
}
51+
break;
52+
default:
53+
return false;
54+
}
55+
}
56+
57+
function stream_read($count) {
58+
$ret = substr($this->data, $this->position, $count);
59+
$this->position += strlen($ret);
60+
return $ret;
61+
}
62+
63+
function stream_tell() {
64+
return $this->position;
65+
}
66+
}
67+
68+
stream_wrapper_register('var', 'VariableStream');
69+
70+
$fp = fopen('var://myvar', 'rb');
71+
72+
stream_set_chunk_size($fp, 10);
73+
$headers = exif_read_data($fp);
74+
var_dump(is_array($headers));
75+
76+
fclose($fp);
77+
?>
78+
--EXPECT--
79+
bool(true)

0 commit comments

Comments
 (0)