diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 4582de4bc8ecb..aebb856060094 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1497,7 +1497,7 @@ PHP_FUNCTION(socket_send) /* {{{ Receives data from a socket, connected or not */ PHP_FUNCTION(socket_recvfrom) { - zval *arg1, *arg2, *arg5, *arg6 = NULL; + zval *zsocket, *zdata, *zaddr, *zport = NULL; php_socket *php_sock; struct sockaddr_un s_un; struct sockaddr_in sin; @@ -1505,36 +1505,36 @@ PHP_FUNCTION(socket_recvfrom) struct sockaddr_in6 sin6; #endif #ifdef AF_PACKET - //struct sockaddr_ll sll; + struct sockaddr_ll sll; #endif char addrbuf[INET6_ADDRSTRLEN]; socklen_t slen; int retval; - zend_long arg3, arg4; + zend_long length, flags; const char *address; zend_string *recv_buf; ZEND_PARSE_PARAMETERS_START(5, 6) - Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce) - Z_PARAM_ZVAL(arg2) - Z_PARAM_LONG(arg3) - Z_PARAM_LONG(arg4) - Z_PARAM_ZVAL(arg5) + Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce) + Z_PARAM_ZVAL(zdata) + Z_PARAM_LONG(length) + Z_PARAM_LONG(flags) + Z_PARAM_ZVAL(zaddr) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(arg6) + Z_PARAM_ZVAL(zport) ZEND_PARSE_PARAMETERS_END(); - php_sock = Z_SOCKET_P(arg1); + php_sock = Z_SOCKET_P(zsocket); ENSURE_SOCKET_VALID(php_sock); /* overflow check */ /* Shouldthrow ? */ - if (arg3 <= 0 || arg3 > ZEND_LONG_MAX - 1) { + if (length <= 0 || length > ZEND_LONG_MAX - 1) { RETURN_FALSE; } - recv_buf = zend_string_alloc(arg3 + 1, 0); + recv_buf = zend_string_alloc(length + 1, 0); switch (php_sock->type) { case AF_UNIX: @@ -1542,7 +1542,7 @@ PHP_FUNCTION(socket_recvfrom) memset(&s_un, 0, slen); s_un.sun_family = AF_UNIX; - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&s_un, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&s_un, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "Unable to recvfrom", errno); @@ -1552,8 +1552,8 @@ PHP_FUNCTION(socket_recvfrom) ZSTR_LEN(recv_buf) = retval; ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, s_un.sun_path); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, s_un.sun_path); break; case AF_INET: @@ -1561,7 +1561,7 @@ PHP_FUNCTION(socket_recvfrom) memset(&sin, 0, slen); sin.sin_family = AF_INET; - if (arg6 == NULL) { + if (zport == NULL) { zend_string_efree(recv_buf); zend_throw_exception( zend_ce_argument_count_error, @@ -1570,7 +1570,7 @@ PHP_FUNCTION(socket_recvfrom) RETURN_THROWS(); } - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sin, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "Unable to recvfrom", errno); @@ -1582,9 +1582,9 @@ PHP_FUNCTION(socket_recvfrom) address = inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf)); - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, address ? address : "0.0.0.0"); - ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin.sin_port)); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, address ? address : "0.0.0.0"); + ZEND_TRY_ASSIGN_REF_LONG(zport, ntohs(sin.sin_port)); break; #ifdef HAVE_IPV6 case AF_INET6: @@ -1592,7 +1592,7 @@ PHP_FUNCTION(socket_recvfrom) memset(&sin6, 0, slen); sin6.sin6_family = AF_INET6; - if (arg6 == NULL) { + if (zport == NULL) { zend_string_efree(recv_buf); zend_throw_exception( zend_ce_argument_count_error, @@ -1601,7 +1601,7 @@ PHP_FUNCTION(socket_recvfrom) RETURN_THROWS(); } - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin6, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sin6, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno); @@ -1613,22 +1613,20 @@ PHP_FUNCTION(socket_recvfrom) inet_ntop(AF_INET6, &sin6.sin6_addr, addrbuf, sizeof(addrbuf)); - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, addrbuf[0] ? addrbuf : "::"); - ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin6.sin6_port)); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, addrbuf[0] ? addrbuf : "::"); + ZEND_TRY_ASSIGN_REF_LONG(zport, ntohs(sin6.sin6_port)); break; #endif #ifdef AF_PACKET - /* - case AF_PACKET: - // TODO expose and use proper ethernet frame type instead i.e. src mac, dst mac and payload to userland - // ditto for socket_sendto + case AF_PACKET: { + char ifrname[IFNAMSIZ]; + slen = sizeof(sll); - memset(&sll, 0, sizeof(sll)); + memset(&sll, 0, slen); sll.sll_family = AF_PACKET; - char ifrname[IFNAMSIZ]; - retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sll, (socklen_t *)&slen); + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&sll, (socklen_t *)&slen); if (retval < 0) { PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno); @@ -1644,14 +1642,17 @@ PHP_FUNCTION(socket_recvfrom) RETURN_FALSE; } - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); - ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); - ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(zaddr, ifrname); + + if (zport) { + ZEND_TRY_ASSIGN_REF_LONG(zport, sll.sll_ifindex); + } break; - */ + } #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -1662,7 +1663,7 @@ PHP_FUNCTION(socket_recvfrom) /* {{{ Sends a message to a socket, whether it is connected or not */ PHP_FUNCTION(socket_sendto) { - zval *arg1; + zval *zsocket; php_socket *php_sock; struct sockaddr_un s_un; struct sockaddr_in sin; @@ -1670,7 +1671,7 @@ PHP_FUNCTION(socket_sendto) struct sockaddr_in6 sin6; #endif #ifdef AF_PACKET - //struct sockaddr_ll sll; + struct sockaddr_ll sll; #endif int retval; size_t buf_len; @@ -1680,7 +1681,7 @@ PHP_FUNCTION(socket_sendto) zend_string *addr; ZEND_PARSE_PARAMETERS_START(5, 6) - Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce) + Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce) Z_PARAM_STRING(buf, buf_len) Z_PARAM_LONG(len) Z_PARAM_LONG(flags) @@ -1689,14 +1690,19 @@ PHP_FUNCTION(socket_sendto) Z_PARAM_LONG_OR_NULL(port, port_is_null) ZEND_PARSE_PARAMETERS_END(); - php_sock = Z_SOCKET_P(arg1); + php_sock = Z_SOCKET_P(zsocket); ENSURE_SOCKET_VALID(php_sock); - if (port < 0 || port > USHRT_MAX) { - zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX); - RETURN_THROWS(); +#ifdef AF_PACKET + if (php_sock->type != AF_PACKET) { +#endif + if (port < 0 || port > USHRT_MAX) { + zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX); + RETURN_THROWS(); + } +#ifdef AF_PACKET } - +#endif if (len < 0) { zend_argument_value_error(3, "must be greater than or equal to 0"); @@ -1753,7 +1759,6 @@ PHP_FUNCTION(socket_sendto) break; #endif #ifdef AF_PACKET - /* case AF_PACKET: if (port_is_null) { zend_argument_value_error(6, "cannot be null when the socket type is AF_PACKET"); @@ -1762,14 +1767,13 @@ PHP_FUNCTION(socket_sendto) memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; - sll.sll_ifindex = port; + sll.sll_ifindex = (int)port; - retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin)); + retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *)&sll, sizeof(sll)); break; - */ #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); RETURN_THROWS(); } diff --git a/ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt b/ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt new file mode 100644 index 0000000000000..a66398c3a0e13 --- /dev/null +++ b/ext/sockets/tests/socket_recvfrom_afpacket_no_port.phpt @@ -0,0 +1,44 @@ +--TEST-- +AF_PACKET socket_recvfrom() without optional port argument +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- += 60); +var_dump($addr === 'lo'); + +socket_close($s_send); +socket_close($s_recv); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/sockets/tests/socket_sendto_recvfrom_afpacket.phpt b/ext/sockets/tests/socket_sendto_recvfrom_afpacket.phpt new file mode 100644 index 0000000000000..cc499704a8f37 --- /dev/null +++ b/ext/sockets/tests/socket_sendto_recvfrom_afpacket.phpt @@ -0,0 +1,114 @@ +--TEST-- +Test if socket_recvfrom() receives raw data sent by socket_sendto() via AF_PACKET +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- += 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +var_dump(is_string($buf)); +var_dump($addr === 'lo'); +var_dump(str_contains($buf, "ETH_P_ALL test")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- ETH_P_LOOP send and receive ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +$frame = build_frame($dst_mac, $src_mac, ETH_P_LOOP, "loopback payload"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent >= 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +// Verify ETH_P_LOOP ethertype at offset 12-13. +var_dump(unpack("n", $buf, 12)[1] === ETH_P_LOOP); +var_dump(str_contains($buf, "loopback payload")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Large payload ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +$payload = random_bytes(1024); +$frame = build_frame($dst_mac, $src_mac, 0x9000, $payload); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent >= strlen($frame)); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr, $port); +var_dump($bytes >= strlen($frame)); +var_dump(is_int($port)); +// Verify the payload is intact in the raw buffer. +var_dump(str_contains($buf, $payload)); + +socket_close($s_send); +socket_close($s_recv); +?> +--EXPECT-- +--- ETH_P_ALL send and receive --- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +--- ETH_P_LOOP send and receive --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Large payload --- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/sockets/tests/socket_sendto_recvfrom_afpacket_errors.phpt b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_errors.phpt new file mode 100644 index 0000000000000..bd8581b20c1b1 --- /dev/null +++ b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_errors.phpt @@ -0,0 +1,56 @@ +--TEST-- +AF_PACKET socket_sendto() and socket_recvfrom() error cases +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- +getMessage(), PHP_EOL; +} +socket_close($s); + +echo "--- sendto with invalid interface name ---\n"; +$s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s, 'lo'); + +$ret = @socket_sendto($s, str_repeat("\x00", 60), 60, 0, "lo", 999999); +var_dump($ret === false); +socket_close($s); + +echo "--- recvfrom on non-blocking socket with no data ---\n"; +$s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s, 'lo'); +socket_set_nonblock($s); + +$ret = @socket_recvfrom($s, $buf, 65536, 0, $addr); +var_dump($ret === false); +socket_close($s); + +?> +--EXPECT-- +--- sendto without port (ifindex) --- +socket_sendto(): Argument #6 ($port) cannot be null when the socket type is AF_PACKET +--- sendto with invalid interface name --- +bool(true) +--- recvfrom on non-blocking socket with no data --- +bool(true) diff --git a/ext/sockets/tests/socket_sendto_recvfrom_afpacket_malformed.phpt b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_malformed.phpt new file mode 100644 index 0000000000000..4f87074ed0479 --- /dev/null +++ b/ext/sockets/tests/socket_sendto_recvfrom_afpacket_malformed.phpt @@ -0,0 +1,213 @@ +--TEST-- +AF_PACKET socket_sendto/socket_recvfrom with malformed and edge-case frames +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- += 60); +// The raw buffer is just padding after the header. +var_dump(strlen($buf) >= 60); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Bogus ethertype ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +// Use a made-up ethertype (0xBEEF). Kernel delivers it fine on loopback. +$frame = str_pad($dst_mac . $src_mac . pack("n", 0xBEEF) . "bogus", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +// Ethertype bytes should be in the raw buffer at offset 12-13. +var_dump(unpack("n", $buf, 12)[1] === 0xBEEF); +var_dump(str_contains($buf, "bogus")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Garbage payload with custom ethertype ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +// Use a non-standard ethertype (0x88B5, reserved for local experimental use) +// with garbage payload. Avoids kernel IP/IPv6 stack interception. +$frame = str_pad($dst_mac . $src_mac . pack("n", 0x88B5) . "\xDE\xAD\xBE\xEF", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +// Raw buffer is delivered as-is — PHP doesn't parse, so no crash. +var_dump(str_contains($buf, "\xDE\xAD\xBE\xEF")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Another garbage payload with experimental ethertype ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +// Use 0x88B6, another local experimental ethertype. +$frame = str_pad($dst_mac . $src_mac . pack("n", 0x88B6) . "\xCA\xFE", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +// Delivered raw — no parsing, no crash. +var_dump(str_contains($buf, "\xCA\xFE")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Invalid ethertype 0x0000 ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +$frame = str_pad($dst_mac . $src_mac . pack("n", 0x0000) . "zerotype", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +var_dump(unpack("n", $buf, 12)[1] === 0x0000); +var_dump(str_contains($buf, "zerotype")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Invalid ethertype 0xFFFF ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +$frame = str_pad($dst_mac . $src_mac . pack("n", 0xFFFF) . "maxtype", 60, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 60); + +$bytes = socket_recvfrom($s_recv, $buf, 65536, 0, $addr); +var_dump($bytes >= 60); +var_dump(unpack("n", $buf, 12)[1] === 0xFFFF); +var_dump(str_contains($buf, "maxtype")); + +socket_close($s_send); +socket_close($s_recv); + +echo "--- Small receive buffer (truncation) ---\n"; +$s_send = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +$s_recv = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); +socket_bind($s_send, 'lo'); +socket_bind($s_recv, 'lo'); +drain_socket($s_recv); + +$payload = str_repeat("X", 200); +$frame = str_pad($dst_mac . $src_mac . pack("n", 0x9000) . $payload, 214, "\x00"); +$sent = socket_sendto($s_send, $frame, strlen($frame), 0, "lo", 1); +var_dump($sent === 214); + +// Request only 30 bytes — less than the frame. Kernel truncates. +$bytes = socket_recvfrom($s_recv, $buf, 30, 0, $addr); +var_dump($bytes === 30); +var_dump(strlen($buf) === 30); + +socket_close($s_send); +socket_close($s_recv); +?> +--EXPECT-- +--- Undersized frame (below 14-byte ethernet header) --- +bool(true) +--- Zero-length payload (header only, padded to 60) --- +bool(true) +bool(true) +bool(true) +--- Bogus ethertype --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Garbage payload with custom ethertype --- +bool(true) +bool(true) +bool(true) +--- Another garbage payload with experimental ethertype --- +bool(true) +bool(true) +bool(true) +--- Invalid ethertype 0x0000 --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Invalid ethertype 0xFFFF --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Small receive buffer (truncation) --- +bool(true) +bool(true) +bool(true)