Commit c98e10797b2 for php.net
commit c98e10797b231bd716b626e511e68f25056eca38
Author: David Carlier <devnexen@gmail.com>
Date: Fri Aug 14 15:38:43 2026 +0100
ext/sockets: CBPF reuseport CBPF filter improvements/strengthening.
A zero SO_ATTACH_REUSEPORT_CBPF value detached via SO_DETACH_BPF, an
alias of SO_DETACH_FILTER, leaving the reuseport program attached.
SO_DETACH_REUSEPORT_BPF is used and exported now, the value is
restricted to integers and the level is validated.
Close GH-23278
diff --git a/NEWS b/NEWS
index e112bfb5351..cc52980d890 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,11 @@ PHP NEWS
. Fixed bug GH-23477 (Memory leak on duplicate native Phar manifest entries).
(Weilin Du)
+- Sockets:
+ . Fixed socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a zero value,
+ which detached the classic BPF filter instead of the reuseport program.
+ (David Carlier)
+
- Standard:
. Fixed a segfault when a stream filter callback unsets StreamBucket::$data
before re-attaching the bucket. (iliaal)
diff --git a/UPGRADING b/UPGRADING
index 148cad21223..a164599c6f1 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -199,6 +199,16 @@ PHP 8.6 UPGRADE NOTES
rules" message. Code that compares the exact message may need to be
updated.
+- Sockets:
+ . socket_set_option() with SO_ATTACH_REUSEPORT_CBPF now requires an int
+ $value and a $level of SOL_SOCKET. Any other value type throws a TypeError
+ instead of being coerced, and any other level raises a warning and returns
+ false.
+ . socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a $value of 0 now
+ detaches the reuseport filter through SO_DETACH_REUSEPORT_BPF. It
+ previously used SO_DETACH_BPF, an alias of SO_DETACH_FILTER, which left the
+ reuseport program attached.
+
- Sodium:
. The password-hashing functions sodium_crypto_pwhash(),
sodium_crypto_pwhash_str(),
@@ -860,6 +870,7 @@ PHP 8.6 UPGRADE NOTES
. EAI_ALLDONE.
. EAI_INTR.
. EAI_IDN_ENCODE.
+ . SO_DETACH_REUSEPORT_BPF (Linux only).
- Standard:
. ARRAY_FILTER_USE_VALUE.
diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c
index c4c6f224dcf..66df6565798 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -2341,13 +2341,26 @@ PHP_FUNCTION(socket_set_option)
#ifdef SO_ATTACH_REUSEPORT_CBPF
case SO_ATTACH_REUSEPORT_CBPF: {
+ if (level != SOL_SOCKET) {
+ php_error_docref(NULL, E_WARNING, "Invalid level");
+ RETURN_FALSE;
+ }
+ if (Z_TYPE_P(arg4) != IS_LONG) {
+ zend_argument_type_error(4, "must be of type int when argument #3 ($option) is SO_ATTACH_REUSEPORT_CBPF, %s given", zend_zval_value_name(arg4));
+ RETURN_THROWS();
+ }
zend_long cbpf_val = zval_get_long(arg4);
if (!cbpf_val) {
+#ifdef SO_DETACH_REUSEPORT_BPF
ov = 1;
optlen = sizeof(ov);
opt_ptr = &ov;
- optname = SO_DETACH_BPF;
+ optname = SO_DETACH_REUSEPORT_BPF;
+#else
+ php_error_docref(NULL, E_WARNING, "Detaching a reuseport CBPF filter is unsupported");
+ RETURN_FALSE;
+#endif
} else {
uint32_t k = (uint32_t)cbpf_val;
diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php
index fab32628544..681d5ba6d0f 100644
--- a/ext/sockets/sockets.stub.php
+++ b/ext/sockets/sockets.stub.php
@@ -1898,6 +1898,13 @@
*/
const SO_ATTACH_REUSEPORT_CBPF = UNKNOWN;
#endif
+#if defined(SO_DETACH_REUSEPORT_BPF)
+/**
+ * @var int
+ * @cvalue SO_DETACH_REUSEPORT_BPF
+ */
+const SO_DETACH_REUSEPORT_BPF = UNKNOWN;
+#endif
#if defined(SO_DETACH_FILTER)
/**
* @var int
diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h
index cfd79224408..203c010f517 100644
Binary files a/ext/sockets/sockets_arginfo.h and b/ext/sockets/sockets_arginfo.h differ
diff --git a/ext/sockets/tests/socket_reuseport_cbpf.phpt b/ext/sockets/tests/socket_reuseport_cbpf.phpt
index 2210c4438f0..a9b8d5c731f 100644
--- a/ext/sockets/tests/socket_reuseport_cbpf.phpt
+++ b/ext/sockets/tests/socket_reuseport_cbpf.phpt
@@ -19,19 +19,18 @@
var_dump(socket_set_option( $socket, SOL_SOCKET, SO_REUSEADDR, true));
var_dump(socket_set_option( $socket, SOL_SOCKET, SO_REUSEPORT, true));
try {
- socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, array());
+ socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, []);
} catch (\TypeError $e) {
- echo $e->getMessage() . PHP_EOL;
+ echo $e::class, ': ', $e->getMessage(), "\n";
}
var_dump(socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_CPU));
var_dump(socket_bind($socket, '0.0.0.0'));
socket_listen($socket);
socket_close($socket);
?>
---EXPECTF--
+--EXPECT--
bool(true)
bool(true)
-
-Warning: socket_set_option(): Unable to set socket option [2]: No such file or directory in %s on line %d
+TypeError: socket_set_option(): Argument #4 ($value) must be of type int when argument #3 ($option) is SO_ATTACH_REUSEPORT_CBPF, array given
bool(true)
bool(true)
diff --git a/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt b/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt
new file mode 100644
index 00000000000..bd1608a132d
--- /dev/null
+++ b/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt
@@ -0,0 +1,38 @@
+--TEST--
+socket_set_option() attach/detach round trip for reuseport CBPF filters
+--EXTENSIONS--
+sockets
+--SKIPIF--
+<?php
+if (!defined("SO_ATTACH_REUSEPORT_CBPF") || !defined("SO_DETACH_REUSEPORT_BPF")) {
+ die('SKIP on platforms not supporting reuseport CBPF filters');
+}
+?>
+--FILE--
+<?php
+$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_REUSEPORT, true));
+var_dump(socket_bind($socket, '127.0.0.1', 0));
+var_dump(socket_listen($socket));
+
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_CPU));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_DETACH_REUSEPORT_BPF, 1));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_DETACH_REUSEPORT_BPF, 1));
+
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_QUEUE));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, 0));
+
+socket_close($socket);
+?>
+--EXPECTF--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+
+Warning: socket_set_option(): Unable to set socket option [%d]: %s in %s on line %d
+bool(false)
+bool(true)
+bool(true)