Skip to content

Commit

Permalink
Fix GH-7867: FFI::cast() from pointer to array is broken
Browse files Browse the repository at this point in the history
Casting from pointer to array is special, so we must not fall back to
the general FFI casting.  There is a particular issue regarding the
size comparison, namely that the pointer size is always 8 for 64bit
architectures, but the size of an array is determined by its
declaration, so as is casting a pointer to an array with more than 8
elements would fail, but casting to an array with less than 9 elements
succeeds, but the internal pointer would point to some arbitrary
memory.

We fix this by properly supporting the cast.  An alternative would be
to deny this kind of cast generally, since it is not necessarily safe.
However, FFI isn't necessarily safe anyway.

We also check pointer/array type compatibility when casting.

Co-authored-by: Dmitry Stogov <dmitry@zend.com>

Closes GH-7876.
  • Loading branch information
cmb69 committed Jan 10, 2022
1 parent 1f58365 commit 703cac3
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 3 deletions.
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ PHP NEWS
. Fixed bug #81430 (Attribute instantiation leaves dangling pointer).
(beberlei)

- FFI:
. Fixed bug GH-7867 (FFI::cast() from pointer to array is broken). (cmb,
dmitry)

- FPM:
. Fixed memory leak on invalid port. (David Carlier)

Expand Down
11 changes: 8 additions & 3 deletions ext/ffi/ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -3904,9 +3904,14 @@ ZEND_METHOD(FFI, cast) /* {{{ */
/* automatically dereference void* pointers ??? */
cdata->ptr = *(void**)ptr;
} else if (old_type->kind == ZEND_FFI_TYPE_ARRAY
&& type->kind == ZEND_FFI_TYPE_POINTER) {
cdata->ptr = &cdata->ptr_holder;
cdata->ptr_holder = old_cdata->ptr;
&& type->kind == ZEND_FFI_TYPE_POINTER
&& zend_ffi_is_compatible_type(ZEND_FFI_TYPE(old_type->array.type), ZEND_FFI_TYPE(type->pointer.type))) { cdata->ptr = &cdata->ptr_holder;
cdata->ptr = &cdata->ptr_holder;
cdata->ptr_holder = old_cdata->ptr;
} else if (old_type->kind == ZEND_FFI_TYPE_POINTER
&& type->kind == ZEND_FFI_TYPE_ARRAY
&& zend_ffi_is_compatible_type(ZEND_FFI_TYPE(old_type->pointer.type), ZEND_FFI_TYPE(type->array.type))) {
cdata->ptr = old_cdata->ptr_holder;
} else if (type->size > old_type->size) {
zend_object_release(&cdata->std);
zend_throw_error(zend_ffi_exception_ce, "attempt to cast to larger type");
Expand Down
75 changes: 75 additions & 0 deletions ext/ffi/tests/gh7867.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
--TEST--
GH-7867 (FFI::cast() from pointer to array is broken)
--SKIPIF--
<?php
if (!extension_loaded("ffi")) die("skip ffi extension not available");
?>
--FILE--
<?php
$value = FFI::new('char[26]');
FFI::memcpy($value, implode('', range('a', 'z')), 26);

$slice = FFI::new('char[4]');

echo 'cast from start' . PHP_EOL;
FFI::memcpy($slice, $value, 4);
var_dump($value + 0, $slice, FFI::cast('char[4]', $value));
echo PHP_EOL;

echo 'cast with offset' . PHP_EOL;
FFI::memcpy($slice, $value + 4, 4);
var_dump($value + 4, $slice, FFI::cast('char[4]', $value + 4));
echo PHP_EOL;
?>
--EXPECTF--
cast from start
object(FFI\CData:char*)#%d (1) {
[0]=>
string(1) "a"
}
object(FFI\CData:char[4])#%d (4) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
[3]=>
string(1) "d"
}
object(FFI\CData:char[4])#%d (4) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
[3]=>
string(1) "d"
}

cast with offset
object(FFI\CData:char*)#%d (1) {
[0]=>
string(1) "e"
}
object(FFI\CData:char[4])#%d (4) {
[0]=>
string(1) "e"
[1]=>
string(1) "f"
[2]=>
string(1) "g"
[3]=>
string(1) "h"
}
object(FFI\CData:char[4])#%d (4) {
[0]=>
string(1) "e"
[1]=>
string(1) "f"
[2]=>
string(1) "g"
[3]=>
string(1) "h"
}

0 comments on commit 703cac3

Please sign in to comment.