Skip to content

Commit 1a62bc7

Browse files
committed
fix: resolve FFI segfault caused by ext/curl symbol collision
When ext/curl is loaded, PHP has standard libcurl symbols in memory. Loading libcurl-impersonate via FFI::cdef() causes duplicate symbols (both export curl_easy_perform, etc.), leading to ABI mismatch segfaults. Fix: use dlopen() with RTLD_DEEPBIND flag on Linux, which makes libcurl-impersonate prefer its own symbols over the already-loaded standard libcurl. The dlopen handle is kept alive in FfiWrapper to prevent the library from being unloaded. Verified: FfiTransport now works with ext/curl loaded simultaneously.
1 parent e94cbd5 commit 1a62bc7

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

phpstan.neon.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ parameters:
1010
identifier: method.notFound
1111
path: src/Transport/Ffi/FfiWrapper.php
1212
message: '#^Call to an undefined method FFI::#'
13+
-
14+
identifier: property.onlyWritten
15+
path: src/Transport/Ffi/FfiWrapper.php
16+
message: '#dlHandle#'

src/Transport/Ffi/FfiWrapper.php

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,17 @@
1010

1111
class FfiWrapper
1212
{
13+
private const int RTLD_NOW = 2;
14+
15+
private const int RTLD_DEEPBIND = 8;
16+
1317
private FFI $ffi;
1418

19+
/**
20+
* @var CData|null dlopen handle — kept alive to prevent unloading
21+
*/
22+
private ?CData $dlHandle = null;
23+
1524
public function __construct(string $headerPath, string $libraryPath)
1625
{
1726
if (! extension_loaded('ffi')) {
@@ -28,7 +37,17 @@ public function __construct(string $headerPath, string $libraryPath)
2837
throw new FfiException('Could not read FFI header file: '.$headerPath);
2938
}
3039

31-
$this->ffi = FFI::cdef($header, $libraryPath);
40+
// When ext/curl is loaded, PHP already has standard libcurl symbols in memory.
41+
// Loading libcurl-impersonate via FFI::cdef() causes symbol collision (both
42+
// export curl_easy_init, curl_easy_perform, etc.), leading to segfaults.
43+
//
44+
// Fix: use dlopen() with RTLD_DEEPBIND to make libcurl-impersonate prefer
45+
// its own symbols over the already-loaded standard libcurl.
46+
if (extension_loaded('curl') && PHP_OS_FAMILY === 'Linux') {
47+
$this->loadWithDeepbind($header, $libraryPath);
48+
} else {
49+
$this->ffi = FFI::cdef($header, $libraryPath);
50+
}
3251
}
3352

3453
public function easyInit(): CData
@@ -84,4 +103,34 @@ public function slistFreeAll(?CData $list): void
84103
$this->ffi->curl_slist_free_all($list);
85104
}
86105
}
106+
107+
/**
108+
* Load libcurl-impersonate with RTLD_DEEPBIND to avoid symbol collision
109+
* with PHP's ext/curl (which loads standard libcurl).
110+
*/
111+
private function loadWithDeepbind(string $header, string $libraryPath): void
112+
{
113+
$dl = FFI::cdef('
114+
void *dlopen(const char *filename, int flags);
115+
char *dlerror(void);
116+
');
117+
118+
/** @var CData|null $handle */
119+
$handle = $dl->dlopen($libraryPath, self::RTLD_NOW | self::RTLD_DEEPBIND);
120+
121+
if ($handle === null) {
122+
/** @var CData $errorPtr */
123+
$errorPtr = $dl->dlerror();
124+
$error = FFI::string($errorPtr);
125+
126+
throw new FfiException('Failed to dlopen libcurl-impersonate: '.$error);
127+
}
128+
129+
// Keep the handle alive so the library stays loaded
130+
$this->dlHandle = $handle;
131+
132+
// Create FFI interface without a library path — symbols are already loaded
133+
// via dlopen with RTLD_DEEPBIND, so FFI resolves to the impersonate version
134+
$this->ffi = FFI::cdef($header);
135+
}
87136
}

src/Transport/TransportFactory.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public static function create(): TransportInterface
2929
return new CurlTransport;
3030
}
3131

32-
3332
throw new TransportException('No suitable transport available. Install ext-curl or curl-impersonate.');
3433
}
3534
}

0 commit comments

Comments
 (0)