diff --git a/ext/fileinfo/fileinfo.c b/ext/fileinfo/fileinfo.c index 36247b7196f4..704d3024d415 100644 --- a/ext/fileinfo/fileinfo.c +++ b/ext/fileinfo/fileinfo.c @@ -229,9 +229,9 @@ PHP_FUNCTION(finfo_set_flags) } /* }}} */ -static const char* php_fileinfo_from_path(struct magic_set *magic, const zend_string *path, php_stream_context *context) +static const char* php_fileinfo_from_path(struct magic_set *magic, const zval *self, const zend_string *path, php_stream_context *context) { - ZEND_ASSERT(magic != NULL); + ZEND_ASSERT((magic != NULL) ^ (self != NULL)); ZEND_ASSERT(path); ZEND_ASSERT(ZSTR_LEN(path) != 0); ZEND_ASSERT(!zend_str_has_nul_byte(path)); @@ -266,6 +266,9 @@ static const char* php_fileinfo_from_path(struct magic_set *magic, const zend_st } } if (!ret_val) { + if (magic == NULL) { + magic = Z_FINFO_P(self)->magic; + } ret_val = magic_stream(magic, stream); if (UNEXPECTED(ret_val == NULL)) { php_error_docref(NULL, E_WARNING, "Failed identify data %d:%s", magic_errno(magic), magic_error(magic)); @@ -313,7 +316,13 @@ PHP_FUNCTION(finfo_file) magic_setflags(magic, options); } - const char *ret_val = php_fileinfo_from_path(magic, path, context); + const char *ret_val = php_fileinfo_from_path(NULL, self, path, context); + + if (!Z_FINFO_P(self)->magic) { + zend_throw_error(NULL, "Invalid finfo object"); + RETURN_THROWS(); + } + magic = Z_FINFO_P(self)->magic; /* Restore options */ if (options) { @@ -425,7 +434,7 @@ PHP_FUNCTION(mime_content_type) const char *ret_val; if (path) { php_stream_context *context = php_stream_context_get_default(false); - ret_val = php_fileinfo_from_path(magic, path, context); + ret_val = php_fileinfo_from_path(magic, NULL, path, context); } else { /* remember stream position for restoration */ zend_off_t current_stream_pos = php_stream_tell(stream); diff --git a/ext/fileinfo/tests/fileinfo-controlled-free.phpt b/ext/fileinfo/tests/fileinfo-controlled-free.phpt new file mode 100644 index 000000000000..399b9e6a421f --- /dev/null +++ b/ext/fileinfo/tests/fileinfo-controlled-free.phpt @@ -0,0 +1,79 @@ +--TEST-- +Controlled free with re-entrant file info constructor +--EXTENSIONS-- +fileinfo +--CREDITS-- +Calif.io +--FILE-- +__construct( + FILEINFO_MIME_TYPE, + dirname(__DIR__) . '/fixtures/does-not-exist.magic', + ); + } catch (Throwable $error) { + echo "constructor-failed\n"; + } + + for ($i = 0; $i < 128; ++$i) { + $GLOBALS['spray'][] = str_repeat('A', 279); + } + echo "same-bin-spray-complete\n"; + } + return true; + } + + public function stream_stat(): false + { + return false; + } + + public function url_stat(string $path, int $flags): array|false { + return false; + } + + public function stream_read(int $count): string + { + $data = "%PDF-1.7\n1 0 obj\n<<>>\nendobj\n"; + $chunk = substr($data, $this->offset, $count); + $this->offset += strlen($chunk); + return $chunk; + } + + public function stream_eof(): bool + { + return $this->offset >= 32; + } +} + +stream_wrapper_register('auditcontrolled', AuditFileinfoControlledStream::class); + +$spray = []; +$finfo = new finfo(FILEINFO_MIME_TYPE); +var_dump($finfo->file('auditcontrolled://attacker-controlled-upload')); + +?> +--EXPECTF-- +stream-open-release +constructor-failed +same-bin-spray-complete + +Warning: finfo::file(): Failed identify data 22:Magic database is not open in %s on line %d + +Fatal error: Uncaught Error: Invalid finfo object in %s:%d +Stack trace: +#0 %s(%d): finfo->file('auditcontrolled...') +#1 {main} + thrown in %s on line %d diff --git a/ext/fileinfo/tests/fileinfo-http-notification-reconstruct-uaf.phpt b/ext/fileinfo/tests/fileinfo-http-notification-reconstruct-uaf.phpt new file mode 100644 index 000000000000..85782f6a123c --- /dev/null +++ b/ext/fileinfo/tests/fileinfo-http-notification-reconstruct-uaf.phpt @@ -0,0 +1,67 @@ +--TEST-- +Fileinfo reconstruction UAF with re-entrant file info constructor (HTTP notification) +--EXTENSIONS-- +fileinfo +--CREDITS-- +Calif.io +--SKIPIF-- + +--FILE-- + static function ( + int $notificationCode, + int $severity, + ?string $message, + int $messageCode, + int $bytesTransferred, + int $bytesMax, + ) use (&$reentered, &$spray, $finfo): void { + if ($reentered) { + return; + } + $reentered = true; + echo "http-notification-reconstruct:$notificationCode\n"; + try { + $finfo->__construct( + FILEINFO_MIME_TYPE, + dirname(__DIR__) . '/fixtures/does-not-exist.magic', + ); + } catch (Throwable $error) { + echo "constructor-failed\n"; + } + for ($i = 0; $i < 128; ++$i) { + $spray[] = str_repeat('A', 279); + } + echo "same-bin-spray-complete\n"; + }, +]); + +php_cli_server_start(); +$url = "http://" . PHP_CLI_SERVER_ADDRESS; +var_dump($finfo->file($url, 0, $context)); + +?> +--EXPECTF-- +http-notification-reconstruct:2 +constructor-failed +same-bin-spray-complete + +Warning: finfo::file(): Failed identify data 22:Magic database is not open in %s on line %d + +Fatal error: Uncaught Error: Invalid finfo object in %s:%d +Stack trace: +#0 %s(%d): finfo->file('http://localhos...', 0, Resource id #%d) +#1 {main} + thrown in %s on line %d diff --git a/ext/fileinfo/tests/fileinfo-no-stat-stream-reconstruct-uaf.phpt b/ext/fileinfo/tests/fileinfo-no-stat-stream-reconstruct-uaf.phpt new file mode 100644 index 000000000000..cfed52e056fd --- /dev/null +++ b/ext/fileinfo/tests/fileinfo-no-stat-stream-reconstruct-uaf.phpt @@ -0,0 +1,62 @@ +--TEST-- +Fileinfo reconstruction UAF with re-entrant file info constructor (stream stat) +--EXTENSIONS-- +fileinfo +--CREDITS-- +Calif.io +--ENV-- +USE_ZEND_ALLOC=0 +--FILE-- +__construct(FILEINFO_MIME_TYPE); + } + return true; + } + + public function stream_stat(): false + { + return false; + } + + public function url_stat(string $path, int $flags): array|false { + return false; + } + + public function stream_read(int $count): string + { + $data = "%PDF-1.7\n1 0 obj\n<<>>\nendobj\n"; + $chunk = substr($data, $this->offset, $count); + $this->offset += strlen($chunk); + return $chunk; + } + + public function stream_eof(): bool + { + return $this->offset >= 32; + } + +} + +stream_wrapper_register('auditfinfo', AuditFileinfoStream::class); + +$finfo = new finfo(FILEINFO_MIME_TYPE); +var_dump($finfo->file('auditfinfo://attacker-controlled-upload')); + +?> +--EXPECTF-- +stream-open-reconstruct + +Warning: finfo::file(): AuditFileinfoStream::stream_cast is not implemented! in %s on line %d +string(15) "application/pdf"