Skip to content

Throw in FFI::addr() when referencing temporary pointer #9601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

iluuu1994
Copy link
Member

@iluuu1994 iluuu1994 commented Sep 23, 2022

We're only disallowing pointers, as they always rely not only on the data but also the CData.ptr_holder to be there. Other unowned structures should be allowed, like structs, even if refcount == 1. Targeting master as this is an improvement rather than a bugfix.

Copy link
Member

@bwoebi bwoebi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's okay. At least I cannot imagine any possible value from taking the address of a rc=1 adress.

@iluuu1994
Copy link
Member Author

iluuu1994 commented Sep 23, 2022

This could probably be further improved by not allowing owned data with refcount=1. I'll see if I can add that check as well. Nevermind. The ownership is transferred onto the pointer, so this should be fine. This behavior was untested, so I added a test.

Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine.
The error message might be done more clear e.g. "Cannot take FFI::addr() from temporary poiner", but I'm not sure.

@iluuu1994 iluuu1994 closed this in d4ad9b7 Sep 26, 2022
andypost pushed a commit to skilld-labs/php-src that referenced this pull request Sep 29, 2022
@bwoebi
Copy link
Member

bwoebi commented Nov 20, 2023

I just tried upgrading my FFI code to PHP 8.3 and it ... blew up, thanks to this.

More precisely, my code looks like FFI::addr($cdata->struct_member)[0] = $value;. (The addr()[0] assignment works around some FFI peculiarities regarding assignment of values directly.)
The $cdata->struct_member is a RC=1 value here and thus matches the issue here.

It can be worked around by assigning $cdata->struct_member to a temp var by reference, but that's even uglier?

What this PR probably wanted to solve is catching when the owned value is destroyed. I'm not sure how to do that, but I think we may need to revert the arbitrarily restrictive behaviour of this PR?

(The irony that I initially approved this is not lost on me.)

@dstogov
Copy link
Member

dstogov commented Nov 20, 2023

@bwoebi could you please provide the complete reproduction case.

@bwoebi
Copy link
Member

bwoebi commented Nov 20, 2023

Sure @dstogov, simplified reproducer:

~/php-src/sapi/cli/php -r '$f = FFI::cdef("typedef struct { char *bar; } other;"); $t = $f->new("other"); $data = $f->new("char[2]"); $data[0] = "1"; FFI::addr($_ = &$t->bar)[0] = $f->cast("char*", FFI::addr($data)); var_dump($t);'
object(FFI\CData:struct <anonymous>)#2 (1) {
  ["bar"]=>
  object(FFI\CData:char*)#5 (1) {
    [0]=>
    string(1) "1"
  }
}
~/php-src/sapi/cli/php -r '$f = FFI::cdef("typedef struct { char *bar; } other;"); $t = $f->new("other"); $data = $f->new("char[2]"); $data[0] = "1"; FFI::addr($t->bar)[0] = $f->cast("char*", FFI::addr($data)); var_dump($t);'

Fatal error: Uncaught FFI\Exception: FFI::addr() cannot create a reference to a temporary pointer in Command line code:1
Stack trace:
#0 Command line code(1): FFI::addr(Object(FFI\CData:char*))
#1 {main}
  thrown in Command line code on line 1

Note the difference, in the latter case the &$_ = is missing

@dstogov
Copy link
Member

dstogov commented Nov 22, 2023

@bwoebi I wonder, why do you need these addr. The following code (with addr removed) works fine

$  sapi/cli/php -r '$f = FFI::cdef("typedef struct { char *bar; } other;"); $t = $f->new("other"); $data = $f->new("char[2]"); $data[0] = "1"; $t->bar = $f->cast("char*", $data); var_dump($t);'

object(FFI\CData:struct <anonymous>)#2 (1) {
  ["bar"]=>
  object(FFI\CData:char*)#4 (1) {
    [0]=>
    string(1) "1"
  }
}

This code works in PHP-7.4 and master

@bwoebi
Copy link
Member

bwoebi commented Nov 22, 2023

@dstogov I ended up with a RC=1 reference from a VAR, this was just simplified to illustrate the problem, let's say:

$f = FFI::cdef("typedef struct { char *bar; } other;");
class Container {
    public $data;
    function __construct($f) { $this->data = $f->new("other"); }
    function &getBar() { return $this->data->bar; } // return by ref to get CData instead of null
}
$container = new Container($f);
$data = $f->new("char[2]");
$data[0] = "1";
FFI::addr($container->getBar())[0] = $f->cast("char*", $data); // directly write it
var_dump($container);

Now, certainly $t = $container->getBar(); FFI::addr($t)[0] = $f->cast("char*", $data); works too, but just saying, this is where I encountered the problem in my code.
It's a weird error message to encounter in seemingly accurate code. At least I was quite baffled by the error message, before looking up the source and understanding why it triggers. And - obviously - it generally is fine, except if the returned struct member happens to be a pointer.

@bwoebi
Copy link
Member

bwoebi commented Nov 22, 2023

But I generally need the addr trick to assign the contents directly to a variable, if it's a struct or array member, then the dim&prop write handlers handle it, but obviously $cdata = $otherCdata; does not assign the value to the cdata, but just replaces the variable content.

@dstogov
Copy link
Member

dstogov commented Nov 22, 2023

I see. This probably may be fixed by the following patch

diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index f91092ab03b..daca017f69f 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -4290,7 +4290,8 @@ ZEND_METHOD(FFI, addr) /* {{{ */
        cdata = (zend_ffi_cdata*)Z_OBJ_P(zv);
        type = ZEND_FFI_TYPE(cdata->type);
 
-       if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1 && type->kind == ZEND_FFI_TYPE_POINTER) {
+       if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1 && type->kind == ZEND_FFI_TYPE_POINTER
+        && cdata->ptr_holder) {
                zend_throw_error(zend_ffi_exception_ce, "FFI::addr() cannot create a reference to a temporary pointer");
                RETURN_THROWS();
        }

@iluuu1994 what do you think?

@iluuu1994
Copy link
Member Author

@dstogov Your patch doesn't fix Bobs example for me. TBH I'm not very familiar with FFI. I remember a use-after-free for a Symfony test that used FFI:

$struct = $ffi->new('Foo');
$structPtrPtr = \FFI::addr(\FFI::addr($struct));
var_dump($structPtrPtr);

I don't know how to fix Bobs test, so I'm ok with just reverting this change. FFI is inherently unsafe, so users are responsible for making sure the data survives.

@dstogov
Copy link
Member

dstogov commented Nov 22, 2023

@dstogov Your patch doesn't fix Bobs example for me

Are you sure? I just verified this again and it works.

<?php
$f = FFI::cdef("typedef struct { char *bar; } other;");
class Container {
    public $data;
    function __construct($f) { $this->data = $f->new("other"); }
    function &getBar() { return $this->data->bar; } // return by ref to get CData instead of null
}
$container = new Container($f);
$data = $f->new("char[2]");
$data[0] = "1";
FFI::addr($container->getBar())[0] = $f->cast("char*", $data); // directly write it
var_dump($container);

I remember a use-after-free for a Symfony test that used FFI: $structPtrPtr = \FFI::addr(\FFI::addr($struct));

My patch doesn't affect \FFI::addr(\FFI::addr()) case.

@dstogov
Copy link
Member

dstogov commented Nov 22, 2023

I see, this doesn't fix the other case:

~/php-src/sapi/cli/php -r '$f = FFI::cdef("typedef struct { char *bar; } other;"); $t = $f->new("other"); $data = $f->new("char[2]"); $data[0] = "1"; FFI::addr($t->bar)[0] = $f->cast("char*", FFI::addr($data)); var_dump($t);'

@iluuu1994
Copy link
Member Author

I just verified this again and it works.

🤔 Yes, it fails for the code you just posted below for me.

I see, this doesn't fix the other case:

I didn't even try this one.

@dstogov
Copy link
Member

dstogov commented Nov 22, 2023

@bwoebi will the patch posted above help you? Or it's not enough anyway.
As I showed, you didn't have to use FFI::addr() in the first case.

@bwoebi
Copy link
Member

bwoebi commented Nov 22, 2023

@dstogov Yes, this helps me. The other case just happened as part of the reproducer.

@iluuu1994
Copy link
Member Author

Well, that patch still doesn't work for me ^^

<?php
$f = FFI::cdef("typedef struct { char *bar; } other;");
class Container {
    public $data;
    function __construct($f) { $this->data = $f->new("other"); }
    function &getBar() { return $this->data->bar; } // return by ref to get CData instead of null
}
$container = new Container($f);
$data = $f->new("char[2]");
$data[0] = "1";
FFI::addr($container->getBar())[0] = $f->cast("char*", $data); // directly write it
var_dump($container);
?>
Fatal error: Uncaught FFI\Exception: FFI::addr() cannot create a reference to a temporary pointer in %s:%d
Stack trace:
#0 %s(%d): FFI::addr(Object(FFI\CData:char*))
#1 {main}
  thrown in %s on line %d

I tried on PHP-8.3 and master. But if it works for you then great 🤷‍♂️ 😄

@bwoebi
Copy link
Member

bwoebi commented Nov 22, 2023

No, I meant, it's enough if this fixes the latter case. I can test later.

dstogov added a commit that referenced this pull request Nov 22, 2023
dstogov added a commit that referenced this pull request Nov 22, 2023
* PHP-8.3:
  Fixed regression introduced by #9601
@dstogov
Copy link
Member

dstogov commented Nov 22, 2023

@bwoebi the problem should be fixed now

clrpackages pushed a commit to clearlinux-pkgs/php that referenced this pull request Dec 22, 2023
Alex Dowad (1):
      Return value of mb_get_info can be NULL

Ben Ramsey (1):
      Merge changes to CertificateGenerator.inc from PHP-8.2

Bob Weinand (2):
      Add NEWS entry for GH-12768
      USE_ZEND_ALLOC=1 in tests with zend_test.observe_opline_in_zendmm=1

Derick Rethans (2):
      Import timelib 2022.10
      Update NEWS

Dmitry Stogov (7):
      Fixed GH-12747: Function JIT returns invalid error message for coalesce operator on invalid offset
      Fixed GH-12748: Function JIT emits "could not convert to int" warning at the same time as invalid offset Error
      Fixed regression introduced by php/php-src#9601
      Fixed GH-12748: Function JIT emits "could not convert to int" warning at the same time as invalid offset Error
      Fixed GH-12812: Integer string in variable used as offset produces wrong undefined array key warning (#12817)
      Fixed GH-8251: Narrowing occurred during type inference of ZEND_FETCH_DIM_W
      Fixed type inference

Florian Engelhardt (1):
      Fix invalid opline in OOM handlers within ZEND_FUNC_GET_ARGS and ZEND_BIND_STATIC (#12768)

Gina Peter Banyard (5):
      ext/standard: Fix GH-9316
      Mention correct bug number
      jit: fixed "Uninitialized string offset" warning being emitted at the same time as invalid offset Error
      jit: fixed JIT "Attempt to assign property of non-object" warning emitted at the same time as Error is being thrown
      Align error messages between normal VM and JIT for RW when using object as array (#12799)

Ilija Tovilo (16):
      Fix undeclared variable in stat tests
      Disable -fsanitize=function on Clang 17
      Fix astat imperciseness excemption in test
      [skip ci] Further increase allowable atime deviation
      Automatically mark tests as flaky
      Fix file test race condition
      Retry tests on deadlock
      [skip ci] Fix more test tmp file conflicts
      Temporarily disable failing zlib tests on travis (#10738)
      Fix use-after-free of name in var-var with malicious error handler
      Fix in-place modification of filename in php_message_handler_for_zend
      Reduce parallelism on frequently crashing jobs
      [skip ci] Skip resource intensive tidy test on GA
      Fix travis_wait
      Fix leak of call->extra_named_params on internal __call
      Fix compilation of ftp without openssl

Jakub Zelenka (5):
      PHP 8.3 is now 8.3.1-dev
      Skip slow tests on Travis
      Fix bug #79945: Stream wrappers in imagecreatefrompng causes segfault
      Fix stream fclose_stdiocast_flush_in_progress type
      Fix #50713: openssl_pkcs7_verify() may ignore untrusted CAs

Levi Morrison (1):
      test: allow other zend extensions to not fail the test

Mikhail Galanin (1):
      Avoid using uninitialised struct

Muhammad Moinur Rahman (2):
      Add host_cpu type for FreeBSD
      Add host_cpu type for FreeBSD

Máté Kocsis (1):
      Fix the default value of $fetchMode in PDO::pgsqlGetNotify()

Niels Dossche (15):
      Fix GH-12635: Test bug69398.phpt fails with ICU 74.1
      Fix GH-12621: browscap segmentation fault when configured in the vhost
      Fix GH-12655: proc_open() does not take into account references in the descriptor array
      Fix GH-12675: MEMORY_LEAK in phpdbg_prompt.c
      Use __DIR__-relative path in tests
      Fix GH-12702: libxml2 2.12.0 issue building from src
      Fix GH-12616: DOM: Removing XMLNS namespace node results in invalid default: prefix
      Fix GH-12791: Possible dereference of NULL in MySQLnd debug code
      Test fixes for libxml2 2.12.0
      Add missing NULL pointer checks related to the previous call frame
      Add missing NULL checks for spl autoload table
      Fix GH-12838: [SOAP] Temporary WSDL cache files not being deleted
      Fix GH-12826: Weird pointers issue in nested loops
      Fix libxml2 2.12 build due to API breaks
      Fix GH-9348: FTP & SSL session reuse

Patrick Allaert (1):
      PHP-8.1 is now for PHP 8.1.28-dev

Patrick Prasse (1):
      Fix bug GH-12705: Segmentation fault in fpm_status_export_to_zval

Pierrick Charron (1):
      Prepare PHP 8.3.1

Remi Collet (1):
      zip: use index to avoid search by name

ddv (1):
      Fix phpGH-12763: PGSQL pg_untrace(): Argument #1 ($connection) must be of type resource or null, PgSql\Connection given.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants