Skip to content

Commit

Permalink
Fix GH-10834: exif_read_data() cannot read smaller stream wrapper chu…
Browse files Browse the repository at this point in the history
…nk 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.
  • Loading branch information
nielsdos committed May 12, 2023
1 parent ad747d9 commit 7b76848
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 10 deletions.
4 changes: 4 additions & 0 deletions NEWS
Expand Up @@ -10,6 +10,10 @@ PHP NEWS
. Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash).
(Bob)

- Exif:
. Fixed bug GH-10834 (exif_read_data() cannot read smaller stream wrapper
chunk sizes). (nielsdos)

- Hash:
. Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments).
(nielsdos)
Expand Down
43 changes: 33 additions & 10 deletions ext/exif/exif.c
Expand Up @@ -215,6 +215,25 @@ zend_module_entry exif_module_entry = {
ZEND_GET_MODULE(exif)
#endif

/* php_stream_read() may return early without reading all data, depending on the chunk size
* and whether it's a URL stream or not. This helper keeps reading until the requested amount
* is read or until there is no more data available to read. */
static ssize_t exif_read_from_stream_file_looped(php_stream *stream, char *buf, size_t count)
{
ssize_t total_read = 0;
while (total_read < count) {
ssize_t ret = php_stream_read(stream, buf + total_read, count - total_read);
if (ret == -1) {
return -1;
}
if (ret == 0) {
break;
}
total_read += ret;
}
return total_read;
}

/* {{{ php_strnlen
* get length of string if buffer if less than buffer size or buffer size */
static size_t php_strnlen(char* str, size_t maxlen) {
Expand Down Expand Up @@ -3321,7 +3340,7 @@ static bool exif_process_IFD_TAG_impl(image_info_type *ImageInfo, char *dir_entr
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val);
return false;
}
fgot = php_stream_read(ImageInfo->infile, value_ptr, byte_count);
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, value_ptr, byte_count);
php_stream_seek(ImageInfo->infile, fpos, SEEK_SET);
if (fgot != byte_count) {
EFREE_IF(outside);
Expand Down Expand Up @@ -3854,7 +3873,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo)
Data[0] = (uchar)lh;
Data[1] = (uchar)ll;

got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */
got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */
if (got != itemlen-2) {
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);
return false;
Expand All @@ -3872,7 +3891,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo)
size = ImageInfo->FileSize - fpos;
sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL);
Data = ImageInfo->file.list[sn].data;
got = php_stream_read(ImageInfo->infile, (char*)Data, size);
got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)Data, size);
if (got != size) {
EXIF_ERRLOG_FILEEOF(ImageInfo)
return false;
Expand Down Expand Up @@ -4049,7 +4068,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
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);
#endif
php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */
php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2);
if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2) != 2)) {
return false;
}
num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel);
dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/;
if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) {
Expand All @@ -4059,7 +4080,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) {
return false;
}
php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2);
if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2) != dir_size - 2)) {
return false;
}
next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel);
#ifdef EXIF_DEBUG
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset);
Expand Down Expand Up @@ -4147,7 +4170,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
#ifdef EXIF_DEBUG
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);
#endif
php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size);
exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size);
#ifdef EXIF_DEBUG
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done");
#endif
Expand Down Expand Up @@ -4198,7 +4221,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
if (!ImageInfo->Thumbnail.data) {
ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
if (fgot != ImageInfo->Thumbnail.size) {
EXIF_ERRLOG_THUMBEOF(ImageInfo)
efree(ImageInfo->Thumbnail.data);
Expand Down Expand Up @@ -4238,7 +4261,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir
if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) {
ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0);
php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET);
fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size);
if (fgot != ImageInfo->Thumbnail.size) {
EXIF_ERRLOG_THUMBEOF(ImageInfo)
efree(ImageInfo->Thumbnail.data);
Expand Down Expand Up @@ -4293,7 +4316,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)

if (ImageInfo->FileSize >= 2) {
php_stream_seek(ImageInfo->infile, 0, SEEK_SET);
if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) {
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)file_header, 2) != 2) {
return false;
}
if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) {
Expand All @@ -4304,7 +4327,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file");
}
} else if (ImageInfo->FileSize >= 8) {
if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) {
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header+2), 6) != 6) {
return false;
}
if (!memcmp(file_header, "II\x2A\x00", 4)) {
Expand Down
79 changes: 79 additions & 0 deletions ext/exif/tests/gh10834.phpt
@@ -0,0 +1,79 @@
--TEST--
GH-10834 (exif_read_data() cannot read smaller stream wrapper chunk sizes)
--EXTENSIONS--
exif
--FILE--
<?php
class VariableStream {
private $data;
private $position;
public $context;

function stream_close() {
return true;
}

function stream_eof() {
return $this->position >= strlen($this->data);
}

function stream_open($path, $mode, $options, &$opened_path) {
$this->position = 0;
$this->data = file_get_contents(__DIR__.'/bug50845.jpg');
return true;
}

function stream_seek($offset, $whence) {
switch ($whence) {
case SEEK_SET:
if ($offset < strlen($this->data) && $offset >= 0) {
$this->position = $offset;
return true;
} else {
return false;
}
break;
case SEEK_CUR:
if ($offset >= 0) {
$this->position += $offset;
return true;
} else {
return false;
}
break;
case SEEK_END:
if (strlen($this->data) + $offset >= 0) {
$this->position = strlen($this->data) + $offset;
return true;
} else {
return false;
}
break;
default:
return false;
}
}

function stream_read($count) {
$ret = substr($this->data, $this->position, $count);
$this->position += strlen($ret);
return $ret;
}

function stream_tell() {
return $this->position;
}
}

stream_wrapper_register('var', 'VariableStream');

$fp = fopen('var://myvar', 'rb');

stream_set_chunk_size($fp, 10);
$headers = exif_read_data($fp);
var_dump(is_array($headers));

fclose($fp);
?>
--EXPECT--
bool(true)

0 comments on commit 7b76848

Please sign in to comment.