php-src/ext/zend_test/test.stub.php
Niels Dossche bc558bf7a3
Fix GH-11078: PHP Fatal error triggers pointer being freed was not allocated and malloc: double free for ptr errors
Although the issue was demonstrated using Curl, the issue is purely in
the streams layer of PHP.

Full analysis is written in GH-11078 [1], but here is the brief version:
Here's what actually happens:
1) We're creating a FILE handle from a stream using the casting mechanism.
   This will create a cookie-based FILE handle using funopen.
2) We're reading stream data using fread from the userspace stream. This will
   temporarily set a buffer into a field _bf.base [2]. This buffer is now equal
   to the upload buffer that Curl allocated and note that that buffer is owned
   by Curl.
3) The fatal error occurs and we bail out from the fread function, notice how
   the reset code is never executed and so the buffer will still point to
   Curl's upload buffer instead of FILE's own buffer [3].
4) The resources are destroyed, this includes our opened stream and because the
   FILE handle is cached, it gets destroyed as well.
   In fact, the stream code calls through fclose on purpose in this case.
5) The fclose code frees the _bs.base buffer [4].
   However, this is not the buffer that FILE owns but the one that Curl owns
   because it isn't reset properly due to the bailout!
6) The objects are getting destroyed, and so the curl free logic is invoked.
   When Curl tries to gracefully clean up, it tries to free the buffer.
   But that buffer is actually already freed mistakingly by the C library!

This also explains why we can't reproduce it on Linux: this bizarre buffer
swapping only happens on macOS and BSD, not on Linux.

To solve this, we switch to an unbuffered mode for cookie-based FILEs.
This avoids any stateful problems related to buffers especially when the
bailout mechanism triggers. As streams have their own buffering
mechanism, I don't expect this to impact performance.

[1] https://github.com/php/php-src/issues/11078#issuecomment-2155616843
[2] 5e566be7a7/stdio/FreeBSD/fread.c (L102-L103)
[3] 5e566be7a7/stdio/FreeBSD/fread.c (L117)
[4] 5e566be7a7/stdio/FreeBSD/fclose.c (L66-L67)

Closes GH-14524.
2024-06-10 19:38:21 +02:00

266 lines
7 KiB
PHP

<?php
/**
* @generate-class-entries static
* @generate-legacy-arginfo 80000
* @undocumentable
*/
namespace {
/**
* @var int
* @deprecated
*/
const ZEND_TEST_DEPRECATED = 42;
/** @var string */
const ZEND_CONSTANT_A = "global";
require "Zend/zend_attributes.stub.php";
interface _ZendTestInterface
{
/** @var int */
public const DUMMY = 0;
}
/** @alias _ZendTestClassAlias */
class _ZendTestClass implements _ZendTestInterface {
/** @var mixed */
public static $_StaticProp;
public static int $staticIntProp = 123;
public int $intProp = 123;
public ?stdClass $classProp = null;
public stdClass|Iterator|null $classUnionProp = null;
public Traversable&Countable $classIntersectionProp;
public readonly int $readonlyProp;
public static function is_object(): int {}
/** @deprecated */
public function __toString(): string {}
public function returnsStatic(): static {}
public function returnsThrowable(): Throwable {}
static public function variadicTest(string|Iterator ...$elements) : static {}
}
class _ZendTestMagicCall
{
public function __call(string $name, array $args): mixed {}
}
class _ZendTestChildClass extends _ZendTestClass
{
public function returnsThrowable(): Exception {}
}
trait _ZendTestTrait {
/** @var mixed */
public $testProp;
public Traversable|Countable $classUnionProp;
public function testMethod(): bool {}
}
#[Attribute(Attribute::TARGET_ALL)]
final class ZendTestAttribute {
}
#[Attribute(Attribute::TARGET_PARAMETER)]
final class ZendTestParameterAttribute {
public string $parameter;
public function __construct(string $parameter) {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
final class ZendTestPropertyAttribute {
public string $parameter;
public function __construct(string $parameter) {}
}
class ZendTestClassWithMethodWithParameterAttribute {
final public function no_override(string $parameter): int {}
public function override(string $parameter): int {}
}
class ZendTestChildClassWithMethodWithParameterAttribute extends ZendTestClassWithMethodWithParameterAttribute {
public function override(string $parameter): int {}
}
final class ZendTestForbidDynamicCall {
public function call(): void {}
public static function callStatic(): void {}
}
enum ZendTestUnitEnum {
case Foo;
case Bar;
}
enum ZendTestStringEnum: string {
case Foo = "Test1";
case Bar = "Test2";
case Baz = "Test2\\a";
case FortyTwo = "42";
}
enum ZendTestIntEnum: int {
case Foo = 1;
case Bar = 3;
case Baz = -1;
}
function zend_test_array_return(): array {}
function zend_test_nullable_array_return(): ?array {}
function zend_test_void_return(): void {}
function zend_test_compile_string(string $source_string, string $filename, int $position): void {}
/** @deprecated */
function zend_test_deprecated(mixed $arg = null): void {}
/** @alias zend_test_void_return */
function zend_test_aliased(): void {}
/**
* @deprecated
* @alias zend_test_void_return
*/
function zend_test_deprecated_aliased(): void {}
function zend_create_unterminated_string(string $str): string {}
function zend_terminate_string(string &$str): void {}
function zend_leak_variable(mixed $variable): void {}
function zend_leak_bytes(int $bytes = 3): void {}
function zend_string_or_object(object|string $param): object|string {}
function zend_string_or_object_or_null(object|string|null $param): object|string|null {}
/** @param stdClass|string $param */
function zend_string_or_stdclass($param): stdClass|string {}
/** @param stdClass|string|null $param */
function zend_string_or_stdclass_or_null($param): stdClass|string|null {}
function zend_iterable(iterable $arg1, ?iterable $arg2 = null): void {}
function zend_weakmap_attach(object $object, mixed $value): bool {}
function zend_weakmap_remove(object $object): bool {}
function zend_weakmap_dump(): array {}
function zend_get_unit_enum(): ZendTestUnitEnum {}
function zend_test_parameter_with_attribute(string $parameter): int {}
function zend_get_current_func_name(): string {}
function zend_call_method(object|string $obj_or_class, string $method, mixed $arg1 = UNKNOWN, mixed $arg2 = UNKNOWN): mixed {}
function zend_test_zend_ini_parse_quantity(string $str): int {}
function zend_test_zend_ini_parse_uquantity(string $str): int {}
function zend_test_zend_ini_str(): string {}
function zend_get_map_ptr_last(): int {}
function zend_test_crash(?string $message = null): void {}
#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
function zend_test_override_libxml_global_state(): void {}
#endif
function zend_test_is_pcre_bundled(): bool {}
#if defined(PHP_WIN32)
function zend_test_set_fmode(bool $binary): void {}
#endif
/** @param resource $stream */
function zend_test_cast_fread($stream): void {}
}
namespace ZendTestNS {
class Foo {
/** @tentative-return-type */
public function method(): int {}
}
class UnlikelyCompileError {
/* This method signature would create a compile error due to the string
* "ZendTestNS\UnlikelyCompileError" in the generated macro call */
public function method(): ?UnlikelyCompileError {}
}
class NotUnlikelyCompileError {
/* This method signature would create a compile error due to the string
* "ZendTestNS\NotUnlikelyCompileError" in the generated macro call */
public function method(): ?NotUnlikelyCompileError {}
}
}
namespace ZendTestNS2 {
/**
* @var string
* @cvalue ZEND_TEST_NS_CONSTANT_A
*/
const ZEND_CONSTANT_A = UNKNOWN;
class Foo {
public ZendSubNS\Foo $foo;
public function method(): void {}
}
function namespaced_func(): bool {}
/** @deprecated */
function namespaced_deprecated_func(): void {}
/** @alias zend_test_void_return */
function namespaced_aliased_func(): void {}
/**
* @deprecated
* @alias zend_test_void_return
*/
function namespaced_deprecated_aliased_func(): void {}
}
namespace ZendTestNS2\ZendSubNS {
/** @var string */
const ZEND_CONSTANT_A = \ZendTestNS2\ZEND_CONSTANT_A;
// Reference another namespaced constant.
class Foo {
public function method(): void {}
}
function namespaced_func(): bool {}
/** @deprecated */
function namespaced_deprecated_func(): void {}
/** @alias zend_test_void_return */
function namespaced_aliased_func(): void {}
/**
* @deprecated
* @alias zend_test_void_return
*/
function namespaced_deprecated_aliased_func(): void {}
}