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)