Slide 1

Slide 1 text

Still Alive and Full of Hidden Powers PHP Extensions

Slide 2

Slide 2 text

whoami

Slide 3

Slide 3 text

Why Extensions?

Slide 4

Slide 4 text

$this->prop = 'value';

Slide 5

Slide 5 text

zend_update_property_string( showcase_cartesianvector_ce, Z_OBJ_P(getThis()), ZEND_STRL("prop"), "value" );

Slide 6

Slide 6 text

Cartesian Vectors

Slide 7

Slide 7 text

ChatGPT o3-mini-high A Cartesian vector is an ordered tuple of numbers representing a vector’s components along the mutually perpendicular axes of a Cartesian coordinate system.

Slide 8

Slide 8 text

X X Y Y Z Z O O x x y y z z (x,y,z) (x,y,z)

Slide 9

Slide 9 text

final readonly class CartesianVector { public function __construct( public float $x, public float $y, public float $z, ) {} public function equals(self $vector): bool {} public function add(self $vector): self {} public function subtract(self $vector): self {} public function multiply(float $scalar): self {} public function divide(float $scalar): self {} }

Slide 10

Slide 10 text

Comparison

Slide 11

Slide 11 text

$vector = new CartesianVector(1, 2, 3); $other = new CartesianVector(1, 2, 3); var_dump($vector->equals($other)); // bool(true)

Slide 12

Slide 12 text

$vector = new CartesianVector(1, 2, 3); $other = new CartesianVector(1, 2, 3); var_dump($vector == $other); var_dump($vector === $other); // bool(true) // bool(false)

Slide 13

Slide 13 text

var_dump($vector > $other); var_dump($vector == 5); var_dump($vector > 5);

Slide 14

Slide 14 text

bool(false) Notice: Object of class CartesianVector could not be converted to int bool(false) Notice: Object of class CartesianVector could not be converted to int bool(false)

Slide 15

Slide 15 text

Comparison Operators

Slide 16

Slide 16 text

Type Casts

Slide 17

Slide 17 text

var_dump((string) $vector);

Slide 18

Slide 18 text

final readonly class CartesianVector implements Stringable { public function __toString(): string { return sprintf( '(%f, %f, %f)', $this->x, $this->y, $this->z, ); } }

Slide 19

Slide 19 text

var_dump((float) $vector);

Slide 20

Slide 20 text

Warning: Object of class CartesianVector could not be converted to float float(1)

Slide 21

Slide 21 text

Type Casts

Slide 22

Slide 22 text

Arithmetic

Slide 23

Slide 23 text

$vector = new CartesianVector(1, 2, 3); $other = new CartesianVector(2, -3, 5); var_dump($vector->add($other)); var_dump($vector->subtract($other)); var_dump($vector->multiply(2)); var_dump($vector->divide(2));

Slide 24

Slide 24 text

$vector = new CartesianVector(1, 2, 3); $other = new CartesianVector(2, -3, 5); var_dump($vector + $other); var_dump($vector - $other); var_dump($vector * 2); var_dump($vector / 2);

Slide 25

Slide 25 text

Unsupported operand types: CartesianVector + CartesianVector

Slide 26

Slide 26 text

Arithmetic Operators

Slide 27

Slide 27 text

Why Extensions?

Slide 28

Slide 28 text

struct _zend_object_handlers { // ... zend_object_cast_t cast_object; zend_object_do_operation_t do_operation; zend_object_compare_t compare; };

Slide 29

Slide 29 text

final readonly class CartesianVector { public function __construct( public float $x, public float $y, public float $z, ) {} }

Slide 30

Slide 30 text

zend_class_entry ce, *class_entry; INIT_NS_CLASS_ENTRY( ce, "Showcase\\Math", "CartesianVector", class_CartesianVector_methods ); class_entry = zend_register_internal_class_with_flags( &ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_READONLY_CLASS );

Slide 31

Slide 31 text

static const zend_function_entry class_CartesianVector_methods[] = { ZEND_ME( CartesianVector, __construct, arginfo_class_CartesianVector___construct, ZEND_ACC_PUBLIC ) ZEND_FE_END };

Slide 32

Slide 32 text

ZEND_BEGIN_ARG_INFO_EX( arginfo_class_CartesianVector___construct, 0, 0, 3 ) ZEND_ARG_TYPE_INFO(0, x, IS_DOUBLE, 0) ZEND_ARG_TYPE_INFO(0, y, IS_DOUBLE, 0) ZEND_ARG_TYPE_INFO(0, z, IS_DOUBLE, 0) ZEND_END_ARG_INFO()

Slide 33

Slide 33 text

zval property_x_default_value; ZVAL_UNDEF(&property_x_default_value); zend_string *property_x_name = zend_string_init( "x", sizeof("x") - 1, 1 ); zend_declare_typed_property( class_entry, property_x_name, &property_x_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_DOUBLE) ); zend_string_release(property_x_name);

Slide 34

Slide 34 text

PHP_METHOD(CartesianVector, __construct) { double x, y, z; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_DOUBLE(x) Z_PARAM_DOUBLE(y) Z_PARAM_DOUBLE(z) ZEND_PARSE_PARAMETERS_END(); cartesianvector_update_properties( Z_OBJ_P(getThis()), x, y, z ); }

Slide 35

Slide 35 text

final readonly class CartesianVector { public function __construct( public float $x, public float $y, public float $z, ) {} }

Slide 36

Slide 36 text

❯❯❯ ./build/gen_stub.php In ./src/Math/CartesianVector.stub.php: Showcase\Math\CartesianVector::__construct(): Promoted properties are not supported

Slide 37

Slide 37 text

final readonly class CartesianVector { public float $x; public float $y; public float $z; public function __construct( float $x, float $y, float $z, ) {} }

Slide 38

Slide 38 text

void cartesianvector_init_ce(INIT_FUNC_ARGS) { cartesianvector_ce = register_class_CartesianVector(); }

Slide 39

Slide 39 text

PHP_METHOD(CartesianVector, equals) { CARTESIANVECTOR_COMMON_INIT RETURN_BOOL(x == otherX && y == otherY && z == otherZ); }

Slide 40

Slide 40 text

#define CARTESIANVECTOR_COMMON_INIT \ zval* other; \ double x, y, z, otherX, otherY, otherZ; \ ZEND_PARSE_PARAMETERS_START(1, 1) \ Z_PARAM_OBJECT_OF_CLASS(other, showcase_cartesianvector_ce) \ ZEND_PARSE_PARAMETERS_END(); \ cartesianvector_read_properties(Z_OBJ_P(getThis()), &x, &y, &z); \ cartesianvector_read_properties(Z_OBJ_P(other), &otherX, &otherY, &otherZ);

Slide 41

Slide 41 text

const zend_object_handlers std_object_handlers = { // ... zend_std_cast_object_tostring, /* cast_object */ NULL, /* do_operation */ zend_std_compare_objects, /* compare */ };

Slide 42

Slide 42 text

Arithmetic

Slide 43

Slide 43 text

void cartesianvector_init_ce(INIT_FUNC_ARGS) { cartesianvector_ce = register_class_CartesianVector(); // ... cartesianvector_object_handlers.do_operation = cartesianvector_object_do_operation; }

Slide 44

Slide 44 text

static int cartesianvector_object_do_operation( zend_uchar opcode, zval *result, zval *op1, zval *op2 ) { switch (opcode) { case ZEND_ADD: return cartesianvector_add(result, op1, op2); // ... default: return FAILURE; } }

Slide 45

Slide 45 text

var_dump($vector + $other); var_dump($vector + 2); var_dump(2 + $vector);

Slide 46

Slide 46 text

if (Z_TYPE_P(op1) != IS_OBJECT || Z_TYPE_P(op2) != IS_OBJECT || Z_OBJCE_P(op1) != cartesianvector_ce || Z_OBJCE_P(op2) != cartesianvector_ce) { return FAILURE; }

Slide 47

Slide 47 text

object(Showcase\Math\CartesianVector)#3 (3) { ["x"]=> float(3) ["y"]=> float(5) ["z"]=> float(7) } Unsupported operand types: Showcase\Math\CartesianVector + int

Slide 48

Slide 48 text

Type Casts

Slide 49

Slide 49 text

var_dump((float) $vector);

Slide 50

Slide 50 text

static zend_result cartesianvector_cast_object( zend_object *readobj, zval *retval, int type ) { double magnitude; CARTESIANVECTOR_READ_PROPERTY(readobj, "magnitude", &magnitude); if (type == IS_DOUBLE) { ZVAL_DOUBLE(retval, magnitude); return SUCCESS; } return zend_std_cast_object_tostring( readobj, retval, type ); }

Slide 51

Slide 51 text

$vector = new CartesianVector(2, 3, 6); var_dump((float) $vector); // float(7)

Slide 52

Slide 52 text

Comparison

Slide 53

Slide 53 text

if (Z_TYPE_P(op1) == IS_OBJECT) { return Z_OBJ_HANDLER_P(op1, compare)(op1, op2); } else if (Z_TYPE_P(op2) == IS_OBJECT) { return Z_OBJ_HANDLER_P(op2, compare)(op1, op2); }

Slide 54

Slide 54 text

$vector = new CartesianVector(1, 2, 2); $other = new CartesianVector(0, 2, 4); var_dump($vector == $other); // bool(false) var_dump($vector > $other); // bool(true)

Slide 55

Slide 55 text

for (i = 0; i < zobj1->ce->default_properties_count; i++) { zval *p1, *p2; int ret; info = zobj1->ce->properties_info_table[i]; p1 = OBJ_PROP(zobj1, info->offset); p2 = OBJ_PROP(zobj2, info->offset); ret = zend_compare(p1, p2); if (ret != 0) { return ret; } } return 0;

Slide 56

Slide 56 text

$vector = new CartesianVector(1, 2, 2); var_dump($vector == 3); var_dump($vector == 3.0);

Slide 57

Slide 57 text

Notice: Object of class CartesianVector could not be converted to int bool(false) bool(true)

Slide 58

Slide 58 text

ZEND_ASSERT(Z_TYPE_P(value) != IS_OBJECT); uint8_t target_type = Z_TYPE_P(value); if (Z_OBJ_HT_P(object)->cast_object(...) == FAILURE) { // TODO: Less crazy.

Slide 59

Slide 59 text

double magnitude1, magnitude2; magnitude1 = cartesianvector_get_magnitude(object1); magnitude2 = cartesianvector_get_magnitude(object2); return magnitude1 < magnitude2 ? -1 : (magnitude1 > magnitude2 ? 1 : 0 );

Slide 60

Slide 60 text

$vector = new CartesianVector(1, 2, 2); $other = new CartesianVector(0, 2, 4); var_dump($vector == $other); // bool(false) var_dump($vector > $other); // bool(false)

Slide 61

Slide 61 text

$vector = new CartesianVector(1, 2, 2); $other = new CartesianVector(2, 1, 2); var_dump($vector == $other); // bool(true)

Slide 62

Slide 62 text

if (cartesianvector_coordinates_are_equal(...) { return 0; } // If magnitudes differ, accept that comparison int compare_magnitudes = cartesianvector_compare_magnitudes(object1, object2); if (compare_magnitudes != 0) { return compare_magnitudes; } // Fall back to property comparison for other vectors return zend_std_compare_objects(object1, object2);

Slide 63

Slide 63 text

!

Slide 64

Slide 64 text

Type Safety

Slide 65

Slide 65 text

function returnInt(): int { return 'foo'; }

Slide 66

Slide 66 text

returnInt(): Return value must be of type int, string returned

Slide 67

Slide 67 text

PHP_FUNCTION(Shenanigans_returnInt) { RETURN_STRING("foo"); }

Slide 68

Slide 68 text

string(3) "foo"

Slide 69

Slide 69 text

❯❯❯ php --version PHP 8.4.3 (cli) (built: Jan 15 2025 01:03:17) (NTS)

Slide 70

Slide 70 text

❯❯❯ php --version PHP 8.4.3 (cli) (built: Jan 15 2025 01:03:17) (NTS ) DEBUG

Slide 71

Slide 71 text

Shenanigans::returnInt(): Return value must be of type int, string returned

Slide 72

Slide 72 text

zend_result zend_call_function(...) { // ... #if ZEND_DEBUG if (!EG(exception) && call->func) { ZEND_ASSERT(zend_verify_internal_return_type(...)); } #endif // ... }

Slide 73

Slide 73 text

function getStrangeValue() { // REDACTED } var_dump(getStrangeValue()); // UNKNOWN:0

Slide 74

Slide 74 text

$test = getStrangeValue(); var_dump($test); //Warning: Undefined variable $test ...

Slide 75

Slide 75 text

PHP_FUNCTION(Shenanigans_getStrangeValue) { ZVAL_UNDEF(return_value); }

Slide 76

Slide 76 text

Internal Functions

Slide 77

Slide 77 text

namespace Foo; var_dump(hash('sha256', 'test'));

Slide 78

Slide 78 text

namespace Foo { function hash(...): string { return $data; } }

Slide 79

Slide 79 text

namespace Foo; var_dump( hash('sha256', 'test')); \

Slide 80

Slide 80 text

static void showcase_overload_functions(void) { zend_function *orig_function; orig_function = zend_hash_str_find_ptr( CG(function_table), ZEND_STRL("hash") ); if (orig_function != NULL) { orig_function->internal_function.handler = showcase_compromised_hash; } }

Slide 81

Slide 81 text

Userland Functions

Slide 82

Slide 82 text

function foo() { return 'foo'; } var_dump(foo());

Slide 83

Slide 83 text

static void showcase_execute_ex( zend_execute_data *execute_data ) { zend_error(E_NOTICE, "Executing function: %s", ...)); orig_zend_execute_ex(execute_data); } static void showcase_replace_execute(void) { orig_zend_execute_ex = zend_execute_ex; zend_execute_ex = showcase_execute_ex; }

Slide 84

Slide 84 text

Notice: Executing function: foo ... string(3) "foo"

Slide 85

Slide 85 text

orig_zend_execute_ex(execute_data); if (execute_data->return_value && rand() % 10000 == 0) { ZVAL_UNDEF(execute_data->return_value); }

Slide 86

Slide 86 text

Questions @alcaeus