Skip to content

Commit 703cac3

Browse files
committed
Fix GH-7867: FFI::cast() from pointer to array is broken
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.
1 parent 1f58365 commit 703cac3

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ PHP NEWS
66
. Fixed bug #81430 (Attribute instantiation leaves dangling pointer).
77
(beberlei)
88

9+
- FFI:
10+
. Fixed bug GH-7867 (FFI::cast() from pointer to array is broken). (cmb,
11+
dmitry)
12+
913
- FPM:
1014
. Fixed memory leak on invalid port. (David Carlier)
1115

ext/ffi/ffi.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3904,9 +3904,14 @@ ZEND_METHOD(FFI, cast) /* {{{ */
39043904
/* automatically dereference void* pointers ??? */
39053905
cdata->ptr = *(void**)ptr;
39063906
} else if (old_type->kind == ZEND_FFI_TYPE_ARRAY
3907-
&& type->kind == ZEND_FFI_TYPE_POINTER) {
3908-
cdata->ptr = &cdata->ptr_holder;
3909-
cdata->ptr_holder = old_cdata->ptr;
3907+
&& type->kind == ZEND_FFI_TYPE_POINTER
3908+
&& zend_ffi_is_compatible_type(ZEND_FFI_TYPE(old_type->array.type), ZEND_FFI_TYPE(type->pointer.type))) { cdata->ptr = &cdata->ptr_holder;
3909+
cdata->ptr = &cdata->ptr_holder;
3910+
cdata->ptr_holder = old_cdata->ptr;
3911+
} else if (old_type->kind == ZEND_FFI_TYPE_POINTER
3912+
&& type->kind == ZEND_FFI_TYPE_ARRAY
3913+
&& zend_ffi_is_compatible_type(ZEND_FFI_TYPE(old_type->pointer.type), ZEND_FFI_TYPE(type->array.type))) {
3914+
cdata->ptr = old_cdata->ptr_holder;
39103915
} else if (type->size > old_type->size) {
39113916
zend_object_release(&cdata->std);
39123917
zend_throw_error(zend_ffi_exception_ce, "attempt to cast to larger type");

ext/ffi/tests/gh7867.phpt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
GH-7867 (FFI::cast() from pointer to array is broken)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("ffi")) die("skip ffi extension not available");
6+
?>
7+
--FILE--
8+
<?php
9+
$value = FFI::new('char[26]');
10+
FFI::memcpy($value, implode('', range('a', 'z')), 26);
11+
12+
$slice = FFI::new('char[4]');
13+
14+
echo 'cast from start' . PHP_EOL;
15+
FFI::memcpy($slice, $value, 4);
16+
var_dump($value + 0, $slice, FFI::cast('char[4]', $value));
17+
echo PHP_EOL;
18+
19+
echo 'cast with offset' . PHP_EOL;
20+
FFI::memcpy($slice, $value + 4, 4);
21+
var_dump($value + 4, $slice, FFI::cast('char[4]', $value + 4));
22+
echo PHP_EOL;
23+
?>
24+
--EXPECTF--
25+
cast from start
26+
object(FFI\CData:char*)#%d (1) {
27+
[0]=>
28+
string(1) "a"
29+
}
30+
object(FFI\CData:char[4])#%d (4) {
31+
[0]=>
32+
string(1) "a"
33+
[1]=>
34+
string(1) "b"
35+
[2]=>
36+
string(1) "c"
37+
[3]=>
38+
string(1) "d"
39+
}
40+
object(FFI\CData:char[4])#%d (4) {
41+
[0]=>
42+
string(1) "a"
43+
[1]=>
44+
string(1) "b"
45+
[2]=>
46+
string(1) "c"
47+
[3]=>
48+
string(1) "d"
49+
}
50+
51+
cast with offset
52+
object(FFI\CData:char*)#%d (1) {
53+
[0]=>
54+
string(1) "e"
55+
}
56+
object(FFI\CData:char[4])#%d (4) {
57+
[0]=>
58+
string(1) "e"
59+
[1]=>
60+
string(1) "f"
61+
[2]=>
62+
string(1) "g"
63+
[3]=>
64+
string(1) "h"
65+
}
66+
object(FFI\CData:char[4])#%d (4) {
67+
[0]=>
68+
string(1) "e"
69+
[1]=>
70+
string(1) "f"
71+
[2]=>
72+
string(1) "g"
73+
[3]=>
74+
string(1) "h"
75+
}

0 commit comments

Comments
 (0)