Skip to content

Commit 7b76848

Browse files
committed
Fix GH-10834: exif_read_data() cannot read smaller stream wrapper chunk sizes
php_stream_read() may return less than the requested amount of bytes by design. This patch introduces a static function for exif which reads from the stream in a loop until all the requested bytes are read. For the test: Co-authored-by: dotpointer Closes GH-10924.
1 parent ad747d9 commit 7b76848

File tree

3 files changed

+116
-10
lines changed

3 files changed

+116
-10
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ PHP NEWS
1010
. Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash).
1111
(Bob)
1212

13+
- Exif:
14+
. Fixed bug GH-10834 (exif_read_data() cannot read smaller stream wrapper
15+
chunk sizes). (nielsdos)
16+
1317
- Hash:
1418
. Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments).
1519
(nielsdos)

ext/exif/exif.c

+33-10
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);
@@ -3854,7 +3873,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo)
38543873
Data[0] = (uchar)lh;
38553874
Data[1] = (uchar)ll;
38563875

3857-
got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */
3876+
got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */
38583877
if (got != itemlen-2) {
38593878
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);
38603879
return false;
@@ -3872,7 +3891,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo)
38723891
size = ImageInfo->FileSize - fpos;
38733892
sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL);
38743893
Data = ImageInfo->file.list[sn].data;
3875-
got = php_stream_read(ImageInfo->infile, (char*)Data, size);
3894+
got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)Data, size);
38763895
if (got != size) {
38773896
EXIF_ERRLOG_FILEEOF(ImageInfo)
38783897
return false;
@@ -4049,7 +4068,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
40494068
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);
40504069
#endif
40514070
php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */
4052-
php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2);
4071+
if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2) != 2)) {
4072+
return false;
4073+
}
40534074
num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel);
40544075
dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/;
40554076
if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) {
@@ -4059,7 +4080,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
40594080
if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) {
40604081
return false;
40614082
}
4062-
php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2);
4083+
if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2) != dir_size - 2)) {
4084+
return false;
4085+
}
40634086
next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel);
40644087
#ifdef EXIF_DEBUG
40654088
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset);
@@ -4147,7 +4170,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
41474170
#ifdef EXIF_DEBUG
41484171
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);
41494172
#endif
4150-
php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size);
4173+
exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size);
41514174
#ifdef EXIF_DEBUG
41524175
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done");
41534176
#endif
@@ -4198,7 +4221,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
41984221
if (!ImageInfo->Thumbnail.data) {
41994222
ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
42004223
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
4201-
fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
4224+
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
42024225
if (fgot != ImageInfo->Thumbnail.size) {
42034226
EXIF_ERRLOG_THUMBEOF(ImageInfo)
42044227
efree(ImageInfo->Thumbnail.data);
@@ -4238,7 +4261,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
42384261
if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) {
42394262
ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
42404263
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
4241-
fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
4264+
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
42424265
if (fgot != ImageInfo->Thumbnail.size) {
42434266
EXIF_ERRLOG_THUMBEOF(ImageInfo)
42444267
efree(ImageInfo->Thumbnail.data);
@@ -4293,7 +4316,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
42934316

42944317
if (ImageInfo->FileSize >= 2) {
42954318
php_stream_seek(ImageInfo->infile, 0, SEEK_SET);
4296-
if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) {
4319+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)file_header, 2) != 2) {
42974320
return false;
42984321
}
42994322
if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) {
@@ -4304,7 +4327,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
43044327
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file");
43054328
}
43064329
} else if (ImageInfo->FileSize >= 8) {
4307-
if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) {
4330+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header+2), 6) != 6) {
43084331
return false;
43094332
}
43104333
if (!memcmp(file_header, "II\x2A\x00", 4)) {

ext/exif/tests/gh10834.phpt

+79
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)