From 5adf51cfe0f64bfb037b73b223aa5e861da3d7fe Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 3 Jan 2022 16:30:59 +0100 Subject: [PATCH 1/2] 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. --- ext/ffi/ffi.c | 3 ++ ext/ffi/tests/gh7867.phpt | 75 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 ext/ffi/tests/gh7867.phpt diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index e3da06698f659..7ace6dc60c882 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -3907,6 +3907,9 @@ ZEND_METHOD(FFI, cast) /* {{{ */ && type->kind == ZEND_FFI_TYPE_POINTER) { 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) { + 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"); diff --git a/ext/ffi/tests/gh7867.phpt b/ext/ffi/tests/gh7867.phpt new file mode 100644 index 0000000000000..7728d15b1c616 --- /dev/null +++ b/ext/ffi/tests/gh7867.phpt @@ -0,0 +1,75 @@ +--TEST-- +GH-7867 (FFI::cast() from pointer to array is broken) +--SKIPIF-- + +--FILE-- + +--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" +} From 70382d2cd9c16b938c70e83e5f1b7a3f6ed9417d Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 10 Jan 2022 17:57:46 +0100 Subject: [PATCH 2/2] Check pointer/array type compatibility when casting Co-authored-by: Dmitry Stogov --- ext/ffi/ffi.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 7ace6dc60c882..8f4a25b0d6227 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -3904,11 +3904,13 @@ 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; - } else if (old_type->kind== ZEND_FFI_TYPE_POINTER - && type->kind == ZEND_FFI_TYPE_ARRAY) { + && 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);